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