#include "mrilib.h" #include "cs_playsound.c" #include "despike_inc.c" #define SRATE 16000 /* sampling rate of output audio file */ void usage_1dsound(int detail) { printf( "\n" "Usage: 1dsound [options] tsfile\n" "\n" "Program to create a sound file from a 1D file (column of numbers).\n" "\n" "Is this program useful? Probably not, but it can be fun.\n" "\n" "-------\n" "OPTIONS\n" "-------\n" "\n" " ===== output filename =====\n" "\n" " -prefix ppp = Output filename will be ppp.au\n" " [Sun audio format https://en.wikipedia.org/wiki/Au_file_format]\n" " + If you don't use '-prefix', the output is file 'sound.au'.\n" " + If 'ppp' ends in '.au', this program won't add another '.au.\n" "\n" " ===== encoding details =====\n" "\n" " -16PCM = Output in 16-bit linear PCM encoding (uncompressed)\n" " + Less quantization noise (audible hiss) :)\n" " + Takes twice as much disk space for output as 8-bit output :(\n" " +++ This is the default method now!\n" " + https://en.wikipedia.org/wiki/Pulse-code_modulation\n" "\n" " -8PCM = Output in 8-bit linear PCM encoding\n" " + There is no good reason to use this option.\n" "\n" " -8ulaw = Output in 8-bit mu-law encoding.\n" " + Provides a little better quality than -8PCM,\n" " but still has audible quantization noise hiss.\n" " + https://en.wikipedia.org/wiki/M-law_algorithm\n" "\n" " -tper X = X seconds of sound per time point in 'tsfile'.\n" " -TR X Allowed range for 'X' is 0.01 to 1.0 (inclusive).\n" " -dt X [default time step is 0.2 s]\n" " You can use '-tper', '-dt', or '-TR', as you like.\n" "\n" " ===== how the sound timeseries is produced from the data timeseries =====\n" "\n" " -FM = Output sound is frequency modulated between 110 and 1760 Hz\n" " from min to max in the input 1D file.\n" " + Usually 'sounds terrible'.\n" " + The only reason this is here is that it was the first method\n" " I implemented, and I kept it for the sake of nostalgia.\n" "\n" " -notes = Output sound is a sequence of notes, low to high pitch\n" " based on min to max in the input 1D file.\n" " +++ This is the default method of operation.\n" " + A pentatonic scale is used, which usually 'sounds nice':\n" " https://en.wikipedia.org/wiki/Pentatonic_scale\n" "\n" " -notewave W = Selects the shape of the notes used. 'W' is one of these:\n" " -waveform W sine = pure sine wave (sounds simplistic)\n" #if 0 " h2sine = sine wave with some second harmonic\n" #endif " sqsine = square root of sine wave (a little harsh and loud)\n" " square = square wave (a lot harsh and loud)\n" " triangle = triangle wave [the default waveform]\n" #if 0 /** hidden - doesn't do much **/ "\n" " -noADSR = turn off the note 'envelope' to make sound more continuous.\n" " + The envelope is used to ramp each note's sound up and\n" " then back down over the '-tper' interval, making the\n" " notes sound somewhat discrete.\n" " + ADSR stands for Attack, Decay, Sustain, Release, which\n" " are the components of the envelope shape that modulates\n" " a note's pure waveform.\n" " + At this time, you cannot set the ADSR parameters;\n" " you can only turn the ADSR envelope off.\n" #endif "\n" " -despike = apply a simple despiking algorithm, to avoid the artifact\n" " of one very large or small value making all the other notes\n" " end up being the same.\n" "\n" " ===== Notes about notes =====\n" "\n" " ** At this time, the default production method is '-notes', **\n" " ** using the triangle waveform (I like this best). **\n" "\n" " ** With '-notes', up to 6 columns of the input file will be used **\n" " ** to produce a polyphonic sound (in a single channel). **\n" " ** (Any columns past the 6th in the input 'tsfile' are ignored.) **\n" "\n" " ===== hear the sound right away! =====\n" "\n" " -play = Plays the sound file after it is written.\n" " On this computer: %s %s\n" " ===>> Playing sound on a remote computer is\n" " annoying, pointless, and likely to get you punched.\n" , (pprog != NULL) ? "uses program" : "can't find any sound playing program" , (pprog != NULL) ? pprog : " :(" ) ; printf( "\n" "--------\n" "EXAMPLES\n" "--------\n" "The first 2 examples are purely synthetic, using 'data' files created\n" "on the command line. The third example uses a data file that was written\n" "out of an AFNI graph viewer using the 'w' keystroke.\n" "\n" " 1dsound -prefix A1 '1D: 0 1 2 1 0 1 2 0 1 2'\n" "\n" " 1deval -num 100 -expr 'sin(x+0.01*x*x)' | 1dsound -tper 0.1 -prefix A2 1D:stdin\n" "\n" " 1dsound -prefix -tper 0.1 A3 028_044_003.1D\n" "\n" "-----\n" "NOTES\n" "-----\n" "* File can be played with the 'sox' audio package command\n" " play A1.au gain -5\n" " + Here 'gain -5' turns the volume down :)\n" " + sox is not provided with AFNI :(\n" " + To see if sox is on your system, type the command 'which sox'\n" " + If you have sox, you can add 'reverb 99' at the end of the\n" " 'play' command line, and have some extra fun.\n" " + Many other effects are available with sox 'play',\n" " and they can also be used to produce edited sound files:\n" " http://sox.sourceforge.net/sox.html#EFFECTS\n" " + You can convert the .au file produced from here to other\n" " formats using sox; for example:\n" " sox Bob.au Cox.au BobCox.aiff\n" " combines the 2 .au input files to a 2-channel (stereo)\n" " Apple .aiff output file. See this for more information:\n" " http://sox.sourceforge.net/soxformat.html\n" "\n" "* Creation of the file does not depend on sox, so if you have\n" " another way to play .au files, you can use that.\n" " * Mac OS X: Quicktime (GUI) or afplay (command line) programs.\n" " + sox can be installed by first installing 'brew'\n" " -- see https://brew.sh/ -- and then using command\n" " 'brew install sox'.\n" " * Linux: Getting sox is probably the simplest thing to do.\n" " + Or install the mplayer package (which also does videos).\n" " + Another possibility is the aplay program.\n" "\n" "* The audio output file is sampled at 16K bytes per second.\n" " For example, a 30 second file will be 960K bytes in size,\n" " at 16 bits per sample.\n" "\n" "* The auditory effect varies significantly with the '-tper'\n" " parameter X; '-tper 0.02' is very different than '-tper 0.4'.\n" "\n" "--- Quick hack for experimentation and fun - RWCox - Aug 2018 ---\n" "\n" ) ; return; } /*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ #define CODE_FM 1 #define CODE_NOTES 2 #define ENCODE_8ULAW 1 #define ENCODE_8PCM 2 #define ENCODE_16PCM 3 int main( int argc , char *argv[] ) { int iarg ; char *prefix = "sound.au" ; char fname[1024] ; MRI_IMAGE *inim , *phim ; float *far ; int encoding=ENCODE_16PCM ; int do_play=0 ; float tper=0.2f ; int nsper ; int opcode = CODE_NOTES ; int do_despike = 0 ; /*---------- find a sound playing program ----------*/ pprog = get_sound_player() ; /*----- immediate help and quit? -----*/ if( argc < 2 ){ usage_1dsound(1) ; exit(0) ; } /*---------- startup bureaucracy ----------*/ mainENTRY("1dsound main"); machdep(); sound_set_note_ADSR(1) ; sound_set_note_waveform(SOUND_WAVEFORM_TRIANGLE) ; /*------------ scan arguments that X11 didn't eat ------------*/ iarg = 1 ; while( iarg < argc && argv[iarg][0] == '-' ){ /*-- help? --*/ if( strcasecmp(argv[iarg],"-help") == 0 || strcasecmp(argv[iarg],"-h") == 0 ){ usage_1dsound(strlen(argv[iarg])>3?2:1); exit(0) ; } /*-----*/ if( strcasecmp(argv[iarg],"-prefix") == 0 ){ if( iarg >= argc-1 ) ERROR_exit("need arg after %s",argv[iarg]) ; prefix = strdup(argv[++iarg]) ; iarg++ ; continue ; } /*-----*/ if( strcasecmp(argv[iarg],"-8PCM") == 0 ){ encoding = ENCODE_8PCM ; iarg++ ; continue ; } if( strcasecmp(argv[iarg],"-16PCM") == 0 ){ encoding = ENCODE_16PCM ; iarg++ ; continue ; } if( strcasecmp(argv[iarg],"-8ulaw") == 0 ){ encoding = ENCODE_8ULAW ; iarg++ ; continue ; } /*-----*/ if( strcasecmp(argv[iarg],"-play") == 0 ){ if( pprog == NULL ){ WARNING_message("No external program available for playing sound :(") ; } else if( getenv("SSH_CLIENT") != NULL ){ WARNING_message("You are logged in remotely: -play is not allowed!") ; } else { do_play = 1 ; } iarg++ ; continue ; } /*-----*/ if( strcasecmp(argv[iarg],"-despike") == 0 ){ do_despike = 1 ; iarg++ ; continue ; } /*-----*/ if( strcasecmp(argv[iarg],"-FM") == 0 ){ opcode = CODE_FM ; iarg++ ; continue ; } if( strcasecmp(argv[iarg],"-NOTES") == 0 ){ opcode = CODE_NOTES ; iarg++ ; continue ; } if( strcasecmp(argv[iarg],"-noADSR") == 0 || /* hidden option */ strcasecmp(argv[iarg],"-noENV" ) == 0 ){ sound_set_note_ADSR(0) ; iarg++ ; continue ; } if( strcasecmp (argv[iarg],"-notewave") == 0 || strncasecmp(argv[iarg],"-waveform",5) == 0 ){ if( iarg >= argc-1 ) ERROR_exit("need arg after %s",argv[iarg]) ; iarg++ ; if( strncasecmp(argv[iarg],"sine",3) == 0 ) sound_set_note_waveform(SOUND_WAVEFORM_SINE) ; else if( strncasecmp(argv[iarg],"h2sine",3) == 0 ) sound_set_note_waveform(SOUND_WAVEFORM_H2SINE) ; else if( strncasecmp(argv[iarg],"sqsine",3) == 0 ) sound_set_note_waveform(SOUND_WAVEFORM_SQSINE) ; else if( strncasecmp(argv[iarg],"square",3) == 0 ) sound_set_note_waveform(SOUND_WAVEFORM_SQUARE) ; else if( strncasecmp(argv[iarg],"boxcar",3) == 0 ) sound_set_note_waveform(SOUND_WAVEFORM_SQUARE) ; else if( strncasecmp(argv[iarg],"triangle",3) == 0 ) sound_set_note_waveform(SOUND_WAVEFORM_TRIANGLE) ; else WARNING_message("unknown note waveform '%s'",argv[iarg]) ; iarg++ ; continue ; } /*-----*/ if( strcasecmp(argv[iarg],"-tper") == 0 || strcasecmp(argv[iarg],"-dt") == 0 || strcasecmp(argv[iarg],"-TR") == 0 ){ if( iarg >= argc-1 ) ERROR_exit("need arg after %s",argv[iarg]) ; tper = (float)strtod(argv[++iarg],NULL) ; if( tper < 0.01f || tper > 1.00f ) ERROR_exit("1dsound: %s %s is out of range 0.01..1.0", argv[iarg-1],argv[iarg]) ; iarg++ ; continue ; } /*--- symplectically stoopid user ---*/ ERROR_message("Unknown option: %s\n",argv[iarg]) ; suggest_best_prog_option(argv[0], argv[iarg]); exit(1); } /*--------- end of scan over command line args ----------*/ if( argc < 2 ){ usage_1dsound(0); exit(0) ; } if( iarg >= argc ) ERROR_exit("No time series file on command line!\n") ; /*----- read input data file -----*/ inim = mri_read_1D( argv[iarg] ) ; if( inim == NULL ) ERROR_exit("Can't read input file '%s' iarg=%d\n",argv[iarg],iarg) ; if( do_despike ){ int nx = inim->nx, ny = inim->ny, jj , nspike=0 ; float *iar = MRI_FLOAT_PTR(inim), *far ; if( ny > 6 ) ny = 6 ; for( jj=0 ; jj < ny ; jj++ ){ nspike += DES_despike25( nx , iar+jj*nx , NULL ) ; } INFO_message( "%d spike%s squashed from %d input column%s" , nspike , (nspike!=1)?"s were":" was" , ny , (ny !=1)?"s" :"\n" ) ; } /*-- samples per time point --*/ nsper = (int)rintf( SRATE * tper ) ; /*-- create float time series of sound (cs_playsound.c) --*/ switch( opcode ){ case CODE_FM: phim = mri_sound_1D_to_FM( inim , 0.0f , 0.0f , SRATE , nsper ) ; if( phim == NULL ) ERROR_exit("mri_sound_1D_to_FM fails") ; break ; default: case CODE_NOTES: phim = mri_sound_1D_to_notes( inim , SRATE , nsper , 6,0,0 ) ; if( phim == NULL ) ERROR_exit("mri_sound_1D_to_notes fails") ; break ; } /*-- create filename from prefix --*/ if( STRING_HAS_SUFFIX(prefix,".au") ) strcpy(fname,prefix) ; else sprintf(fname,"%s.au",prefix) ; /*-- write .au file out (cs_playsound.c) --*/ switch( encoding ){ default: case ENCODE_8ULAW: sound_write_au_ulaw( fname, phim->nx, MRI_FLOAT_PTR(phim), SRATE, 0.2f ); break ; case ENCODE_8PCM: sound_write_au_8PCM( fname, phim->nx, MRI_FLOAT_PTR(phim), SRATE, 0.2f ); break ; case ENCODE_16PCM: sound_write_au_16PCM( fname, phim->nx, MRI_FLOAT_PTR(phim), SRATE, 0.2f ); break ; } INFO_message ("output sound file %s = %s bytes", fname , commaized_integer_string(THD_filesize(fname)) ) ; ININFO_message(" %.1f s of audio" , phim->nx/(float)SRATE ) ; mri_free(phim) ; /*----- play the sound as well? -----*/ if( pprog != NULL && do_play ){ char cmd[2048] ; sprintf(cmd,"tcsh -c \'%s %s >& /dev/null \'&",pprog,fname) ; ININFO_message(" running command %s",cmd) ; ININFO_message(" to stop it early: killall %s",pprog_name) ; system(cmd) ; } exit(0) ; }