1 #include "mrilib.h"
2 
3 #include "cs_playsound.c"
4 #include "despike_inc.c"
5 
6 #define SRATE 16000  /* sampling rate of output audio file */
7 
usage_1dsound(int detail)8 void usage_1dsound(int detail)
9 {
10    printf(
11      "\n"
12      "Usage: 1dsound [options] tsfile\n"
13      "\n"
14      "Program to create a sound file from a 1D file (column of numbers).\n"
15      "\n"
16      "Is this program useful? Probably not, but it can be fun.\n"
17      "\n"
18      "-------\n"
19      "OPTIONS\n"
20      "-------\n"
21      "\n"
22      " ===== output filename =====\n"
23      "\n"
24      " -prefix ppp  = Output filename will be ppp.au\n"
25      "                [Sun audio format https://en.wikipedia.org/wiki/Au_file_format]\n"
26      "                + If you don't use '-prefix', the output is file 'sound.au'.\n"
27      "                + If 'ppp' ends in '.au', this program won't add another '.au.\n"
28      "\n"
29      " ===== encoding details =====\n"
30      "\n"
31      " -16PCM       = Output in 16-bit linear PCM encoding (uncompressed)\n"
32      "                + Less quantization noise (audible hiss)            :)\n"
33      "                + Takes twice as much disk space for output as 8-bit output :(\n"
34      "              +++ This is the default method now!\n"
35      "                + https://en.wikipedia.org/wiki/Pulse-code_modulation\n"
36      "\n"
37      " -8PCM        = Output in 8-bit linear PCM encoding\n"
38      "                + There is no good reason to use this option.\n"
39      "\n"
40      " -8ulaw       = Output in 8-bit mu-law encoding.\n"
41      "                + Provides a little better quality than -8PCM,\n"
42      "                  but still has audible quantization noise hiss.\n"
43      "                +  https://en.wikipedia.org/wiki/M-law_algorithm\n"
44      "\n"
45      " -tper X      = X seconds of sound per time point in 'tsfile'.\n"
46      " -TR X          Allowed range for 'X' is 0.01 to 1.0 (inclusive).\n"
47      " -dt X          [default time step is 0.2 s]\n"
48      "                You can use '-tper', '-dt', or '-TR', as you like.\n"
49      "\n"
50      " ===== how the sound timeseries is produced from the data timeseries =====\n"
51      "\n"
52      " -FM          = Output sound is frequency modulated between 110 and 1760 Hz\n"
53      "                from min to max in the input 1D file.\n"
54      "                + Usually 'sounds terrible'.\n"
55      "                + The only reason this is here is that it was the first method\n"
56      "                  I implemented, and I kept it for the sake of nostalgia.\n"
57      "\n"
58      " -notes       = Output sound is a sequence of notes, low to high pitch\n"
59      "                based on min to max in the input 1D file.\n"
60      "              +++ This is the default method of operation.\n"
61      "                + A pentatonic scale is used, which usually 'sounds nice':\n"
62      "                  https://en.wikipedia.org/wiki/Pentatonic_scale\n"
63      "\n"
64      " -notewave W  = Selects the shape of the notes used. 'W' is one of these:\n"
65      " -waveform W      sine     = pure sine wave (sounds simplistic)\n"
66 #if 0
67      "                  h2sine   = sine wave with some second harmonic\n"
68 #endif
69      "                  sqsine   = square root of sine wave (a little harsh and loud)\n"
70      "                  square   = square wave              (a lot harsh and loud)\n"
71      "                  triangle = triangle wave            [the default waveform]\n"
72 #if 0  /** hidden - doesn't do much **/
73      "\n"
74      " -noADSR      = turn off the note 'envelope' to make sound more continuous.\n"
75      "                + The envelope is used to ramp each note's sound up and\n"
76      "                  then back down over the '-tper' interval, making the\n"
77      "                  notes sound somewhat discrete.\n"
78      "                + ADSR stands for Attack, Decay, Sustain, Release, which\n"
79      "                  are the components of the envelope shape that modulates\n"
80      "                  a note's pure waveform.\n"
81      "                + At this time, you cannot set the ADSR parameters;\n"
82      "                  you can only turn the ADSR envelope off.\n"
83 #endif
84      "\n"
85      " -despike     = apply a simple despiking algorithm, to avoid the artifact\n"
86      "                of one very large or small value making all the other notes\n"
87      "                end up being the same.\n"
88      "\n"
89      " ===== Notes about notes =====\n"
90      "\n"
91      " ** At this time, the default production method is '-notes',      **\n"
92      " **               using the triangle waveform (I like this best). **\n"
93      "\n"
94      " ** With '-notes', up to 6 columns of the input file will be used **\n"
95      " ** to produce a polyphonic sound (in a single channel).          **\n"
96      " ** (Any columns past the 6th in the input 'tsfile' are ignored.) **\n"
97      "\n"
98      " ===== hear the sound right away! =====\n"
99      "\n"
100      " -play        = Plays the sound file after it is written.\n"
101      "                On this computer: %s %s\n"
102      "            ===>> Playing sound on a remote computer is\n"
103      "                  annoying, pointless, and likely to get you punched.\n"
104     , (pprog != NULL)
105         ? "uses program"
106         : "can't find any sound playing program"
107     ,
108       (pprog != NULL) ? pprog : " :("
109    ) ;
110    printf(
111      "\n"
112      "--------\n"
113      "EXAMPLES\n"
114      "--------\n"
115      "The first 2 examples are purely synthetic, using 'data' files created\n"
116      "on the command line. The third example uses a data file that was written\n"
117      "out of an AFNI graph viewer using the 'w' keystroke.\n"
118      "\n"
119      " 1dsound -prefix A1 '1D: 0 1 2 1 0 1 2 0 1 2'\n"
120      "\n"
121      " 1deval -num 100 -expr 'sin(x+0.01*x*x)' | 1dsound -tper 0.1 -prefix A2 1D:stdin\n"
122      "\n"
123      " 1dsound -prefix -tper 0.1 A3 028_044_003.1D\n"
124      "\n"
125      "-----\n"
126      "NOTES\n"
127      "-----\n"
128      "* File can be played with the 'sox' audio package command\n"
129      "    play A1.au gain -5\n"
130      "  + Here 'gain -5' turns the volume down :)\n"
131      "  + sox is not provided with AFNI :(\n"
132      "  + To see if sox is on your system, type the command 'which sox'\n"
133      "  + If you have sox, you can add 'reverb 99' at the end of the\n"
134      "    'play' command line, and have some extra fun.\n"
135      "  + Many other effects are available with sox 'play',\n"
136      "    and they can also be used to produce edited sound files:\n"
137      "    http://sox.sourceforge.net/sox.html#EFFECTS\n"
138      "  + You can convert the .au file produced from here to other\n"
139      "    formats using sox; for example:\n"
140      "      sox Bob.au Cox.au BobCox.aiff\n"
141      "    combines the 2 .au input files to a 2-channel (stereo)\n"
142      "    Apple .aiff output file. See this for more information:\n"
143      "    http://sox.sourceforge.net/soxformat.html\n"
144      "\n"
145      "* Creation of the file does not depend on sox, so if you have\n"
146      "  another way to play .au files, you can use that.\n"
147      "  * Mac OS X: Quicktime (GUI) or afplay (command line) programs.\n"
148      "              + sox can be installed by first installing 'brew'\n"
149      "                -- see https://brew.sh/ -- and then using command\n"
150      "                'brew install sox'.\n"
151      "  * Linux:    Getting sox is probably the simplest thing to do.\n"
152      "              + Or install the mplayer package (which also does videos).\n"
153      "              + Another possibility is the aplay program.\n"
154      "\n"
155      "* The audio output file is sampled at 16K bytes per second.\n"
156      "  For example, a 30 second file will be 960K bytes in size,\n"
157      "  at 16 bits per sample.\n"
158      "\n"
159      "* The auditory effect varies significantly with the '-tper'\n"
160      "  parameter X; '-tper 0.02' is very different than '-tper 0.4'.\n"
161      "\n"
162      "--- Quick hack for experimentation and fun - RWCox - Aug 2018 ---\n"
163      "\n"
164    ) ;
165 
166    return;
167 }
168 
169 /*---------------------------------------------------------------------------*/
170 /*---------------------------------------------------------------------------*/
171 
172 #define CODE_FM    1
173 #define CODE_NOTES 2
174 
175 #define ENCODE_8ULAW 1
176 #define ENCODE_8PCM  2
177 #define ENCODE_16PCM 3
178 
main(int argc,char * argv[])179 int main( int argc , char *argv[] )
180 {
181    int iarg ;
182    char *prefix = "sound.au" ;
183    char fname[1024] ;
184    MRI_IMAGE *inim , *phim ;
185    float *far ;
186    int encoding=ENCODE_16PCM ;
187    int do_play=0 ;
188    float tper=0.2f ; int nsper ;
189    int opcode = CODE_NOTES ;
190    int do_despike = 0 ;
191 
192    /*---------- find a sound playing program ----------*/
193 
194    pprog = get_sound_player() ;
195 
196    /*----- immediate help and quit? -----*/
197 
198    if( argc < 2 ){ usage_1dsound(1) ; exit(0) ; }
199 
200    /*---------- startup bureaucracy ----------*/
201 
202    mainENTRY("1dsound main"); machdep();
203    sound_set_note_ADSR(1) ;
204    sound_set_note_waveform(SOUND_WAVEFORM_TRIANGLE) ;
205 
206    /*------------ scan arguments that X11 didn't eat ------------*/
207 
208    iarg = 1 ;
209    while( iarg < argc && argv[iarg][0] == '-' ){
210 
211      /*-- help? --*/
212 
213      if( strcasecmp(argv[iarg],"-help") == 0 ||
214          strcasecmp(argv[iarg],"-h")    == 0   ){
215        usage_1dsound(strlen(argv[iarg])>3?2:1);
216        exit(0) ;
217      }
218 
219      /*-----*/
220 
221      if( strcasecmp(argv[iarg],"-prefix") == 0 ){
222        if( iarg >= argc-1 )
223          ERROR_exit("need arg after %s",argv[iarg]) ;
224        prefix = strdup(argv[++iarg]) ; iarg++ ; continue ;
225      }
226 
227      /*-----*/
228 
229      if( strcasecmp(argv[iarg],"-8PCM") == 0 ){
230        encoding = ENCODE_8PCM ; iarg++ ; continue ;
231      }
232 
233      if( strcasecmp(argv[iarg],"-16PCM") == 0 ){
234        encoding = ENCODE_16PCM ; iarg++ ; continue ;
235      }
236 
237      if( strcasecmp(argv[iarg],"-8ulaw") == 0 ){
238        encoding = ENCODE_8ULAW ; iarg++ ; continue ;
239      }
240 
241      /*-----*/
242 
243      if( strcasecmp(argv[iarg],"-play") == 0 ){
244        if( pprog == NULL ){
245          WARNING_message("No external program available for playing sound :(") ;
246        } else if( getenv("SSH_CLIENT") != NULL ){
247          WARNING_message("You are logged in remotely: -play is not allowed!") ;
248        } else {
249          do_play = 1 ;
250        }
251        iarg++ ; continue ;
252      }
253 
254      /*-----*/
255 
256      if( strcasecmp(argv[iarg],"-despike") == 0 ){
257        do_despike = 1 ; iarg++ ; continue ;
258      }
259 
260      /*-----*/
261 
262      if( strcasecmp(argv[iarg],"-FM") == 0 ){
263        opcode = CODE_FM ; iarg++ ; continue ;
264      }
265 
266      if( strcasecmp(argv[iarg],"-NOTES") == 0 ){
267        opcode = CODE_NOTES ; iarg++ ; continue ;
268      }
269 
270      if( strcasecmp(argv[iarg],"-noADSR") == 0 ||     /* hidden option */
271          strcasecmp(argv[iarg],"-noENV" ) == 0   ){
272        sound_set_note_ADSR(0) ; iarg++ ; continue ;
273      }
274 
275      if( strcasecmp (argv[iarg],"-notewave")   == 0 ||
276          strncasecmp(argv[iarg],"-waveform",5) == 0   ){
277        if( iarg >= argc-1 )
278          ERROR_exit("need arg after %s",argv[iarg]) ;
279 
280        iarg++ ;
281             if( strncasecmp(argv[iarg],"sine",3) == 0 )
282              sound_set_note_waveform(SOUND_WAVEFORM_SINE) ;
283        else if( strncasecmp(argv[iarg],"h2sine",3) == 0 )
284              sound_set_note_waveform(SOUND_WAVEFORM_H2SINE) ;
285        else if( strncasecmp(argv[iarg],"sqsine",3) == 0 )
286              sound_set_note_waveform(SOUND_WAVEFORM_SQSINE) ;
287        else if( strncasecmp(argv[iarg],"square",3) == 0 )
288              sound_set_note_waveform(SOUND_WAVEFORM_SQUARE) ;
289        else if( strncasecmp(argv[iarg],"boxcar",3) == 0 )
290              sound_set_note_waveform(SOUND_WAVEFORM_SQUARE) ;
291        else if( strncasecmp(argv[iarg],"triangle",3) == 0 )
292              sound_set_note_waveform(SOUND_WAVEFORM_TRIANGLE) ;
293        else
294              WARNING_message("unknown note waveform '%s'",argv[iarg]) ;
295 
296        iarg++ ; continue ;
297      }
298 
299      /*-----*/
300 
301      if( strcasecmp(argv[iarg],"-tper")   == 0 ||
302          strcasecmp(argv[iarg],"-dt")     == 0 ||
303          strcasecmp(argv[iarg],"-TR") == 0   ){
304        if( iarg >= argc-1 )
305          ERROR_exit("need arg after %s",argv[iarg]) ;
306        tper = (float)strtod(argv[++iarg],NULL) ;
307        if( tper < 0.01f || tper > 1.00f )
308          ERROR_exit("1dsound: %s %s is out of range 0.01..1.0",
309                     argv[iarg-1],argv[iarg]) ;
310        iarg++ ; continue ;
311      }
312 
313 
314      /*--- symplectically stoopid user ---*/
315 
316      ERROR_message("Unknown option: %s\n",argv[iarg]) ;
317      suggest_best_prog_option(argv[0], argv[iarg]);
318      exit(1);
319 
320    } /*--------- end of scan over command line args ----------*/
321 
322    if( argc < 2 ){ usage_1dsound(0); exit(0) ; }
323 
324    if( iarg >= argc )
325       ERROR_exit("No time series file on command line!\n") ;
326 
327    /*----- read input data file -----*/
328 
329    inim = mri_read_1D( argv[iarg] ) ;
330    if( inim == NULL )
331      ERROR_exit("Can't read input file '%s' iarg=%d\n",argv[iarg],iarg) ;
332 
333    if( do_despike ){
334      int nx = inim->nx, ny = inim->ny, jj , nspike=0 ;
335      float *iar = MRI_FLOAT_PTR(inim), *far ;
336      if( ny > 6 ) ny = 6 ;
337      for( jj=0 ; jj < ny ; jj++ ){
338        nspike += DES_despike25( nx , iar+jj*nx , NULL ) ;
339      }
340      INFO_message( "%d spike%s squashed from %d input column%s" ,
341                    nspike , (nspike!=1)?"s were":" was" ,
342                    ny     , (ny    !=1)?"s"     :"\n"    ) ;
343    }
344 
345    /*-- samples per time point --*/
346 
347    nsper = (int)rintf( SRATE * tper ) ;
348 
349    /*-- create float time series of sound (cs_playsound.c) --*/
350 
351    switch( opcode ){
352 
353      case CODE_FM:
354        phim = mri_sound_1D_to_FM( inim ,
355                                   0.0f , 0.0f , SRATE , nsper ) ;
356        if( phim == NULL )
357          ERROR_exit("mri_sound_1D_to_FM fails") ;
358      break ;
359 
360      default:
361      case CODE_NOTES:
362        phim = mri_sound_1D_to_notes( inim , SRATE , nsper , 6,0,0 ) ;
363        if( phim == NULL )
364          ERROR_exit("mri_sound_1D_to_notes fails") ;
365      break ;
366 
367    }
368 
369    /*-- create filename from prefix --*/
370 
371    if( STRING_HAS_SUFFIX(prefix,".au") ) strcpy(fname,prefix) ;
372    else                                  sprintf(fname,"%s.au",prefix) ;
373 
374    /*-- write .au file out (cs_playsound.c) --*/
375 
376    switch( encoding ){
377      default:
378      case ENCODE_8ULAW:
379        sound_write_au_ulaw( fname, phim->nx, MRI_FLOAT_PTR(phim), SRATE, 0.2f );
380      break ;
381 
382      case ENCODE_8PCM:
383        sound_write_au_8PCM( fname, phim->nx, MRI_FLOAT_PTR(phim), SRATE, 0.2f );
384      break ;
385 
386      case ENCODE_16PCM:
387        sound_write_au_16PCM( fname, phim->nx, MRI_FLOAT_PTR(phim), SRATE, 0.2f );
388      break ;
389    }
390 
391    INFO_message  ("output sound file %s = %s bytes",
392                    fname , commaized_integer_string(THD_filesize(fname)) ) ;
393    ININFO_message(" %.1f s of audio" , phim->nx/(float)SRATE ) ;
394 
395    mri_free(phim) ;
396 
397    /*----- play the sound as well? -----*/
398 
399    if( pprog != NULL && do_play ){
400      char cmd[2048] ;
401      sprintf(cmd,"tcsh -c \'%s %s  >& /dev/null \'&",pprog,fname) ;
402      ININFO_message(" running command %s",cmd) ;
403      ININFO_message(" to stop it early: killall %s",pprog_name) ;
404      system(cmd) ;
405    }
406 
407    exit(0) ;
408 }
409