1 /*
2 * audsav_dlg.c
3 *
4 * Code for the audio save control dialog
5 *
6 * (C) 1997 Randall Hopper
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are
10 * met: 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer. 2.
12 * Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
20 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 *
28 */
29
30 /* ******************** Include Files ************** */
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <fcntl.h>
35 #include <string.h>
36 #include <errno.h>
37 #include <signal.h>
38 #include <unistd.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include "voxware.h"
42 #include <X11/Intrinsic.h>
43 #include <X11/StringDefs.h>
44 #include <X11/Shell.h>
45 #include <X11/Xaw/AsciiText.h>
46 #include <X11/Xaw/Box.h>
47 #include <X11/Xaw/Command.h>
48 #include <X11/Xaw/Label.h>
49 #include <X11/Xaw/AsciiText.h>
50 #include <X11/Xaw/Form.h>
51 #include <X11/Xaw/MenuButton.h>
52 #include <X11/Xaw/SimpleMenu.h>
53 #include <X11/Xaw/SmeBSB.h>
54 #include <X11/Xaw/Toggle.h>
55 #include "tvdefines.h"
56 #include "glob.h"
57 #include "actions.h"
58 #include "xutil.h"
59 #include "tvutil.h"
60 #include "audsav_dlg.h"
61 #include "app_rsrc.h"
62
63 /* ******************** Local defines ************** */
64
65 #define RDWR_TIME 0.5 /* Seconds */
66 #define MAX_BYTES_PER_SEC 44100*2*2
67 #define MAX_RDWR_BUF_SIZE (((long)(MAX_BYTES_PER_SEC * RDWR_TIME))+3)/4*4
68
69 #define REC_CMD_MPEG2_RSRC "Fxtv.recordCmd.mpeg2"
70 #define REC_CMD_MPEG3_RSRC "Fxtv.recordCmd.mpeg3"
71
72 #define DO_IOCTL_SERR(str,arg) fprintf(stderr, "ioctl(%s, %ld) failed: %s\n",\
73 str, (long)arg, strerror(errno) )
74
75 typedef struct {
76 TV_SOUND_FMT fmt_out;
77 char **cmd; /* Cmd running and its args */
78 char fname_in [ MAXPATHLEN ], /* Input filename */
79 fname_out[ MAXPATHLEN ]; /* Output filename */
80 char *tmpstr;
81 Widget dialog_shell;
82 TV_BOOL mute_on;
83 } TV_AUDIO_CMD_STATE;
84
85 /* ******************** Private variables ************** */
86
87 static TV_AUDIO_FILE_FMT Sel_ffmt;
88 static TV_AUDIO_SAMPLE_FMT Sel_sfmt;
89 static TV_BOOL Sel_stereo;
90 static TV_UINT32 Sel_rate;
91
92 static Widget Dialog_wgt = NULL,
93 Text_wgt = NULL,
94 Ffmt_menu_btn = NULL,
95 Sfmt_menu_btn = NULL,
96 Chan_menu_btn = NULL,
97 Rate_menu_btn = NULL,
98 Record_btn = NULL,
99 Stop_btn = NULL,
100 Playback_btn = NULL,
101 Dismiss_btn = NULL;
102 static Boolean Recording = False,
103 Playing = False;
104
105 static TV_FFMT_ITEM_DEF Ffmt_item_def[] = {
106 { TV_AUDIO_FILE_FMT_RAW , "raw" , NULL },
107 { TV_AUDIO_FILE_FMT_SUNAU , "au" , "-t raw -U -b -c 1 -r 8012" },
108 { TV_AUDIO_FILE_FMT_WAV , "wav" , "-t wav" },
109 { TV_AUDIO_FILE_FMT_VOC , "voc" , "-t voc" },
110 { TV_AUDIO_FILE_FMT_AIFF , "aiff" , "-t aiff" },
111 { TV_AUDIO_FILE_FMT_MPEG2 , "mp2" , "-t aiff" }, /* precond */
112 { TV_AUDIO_FILE_FMT_MPEG3 , "mp3" , "-t aiff" }, /* precond */
113 };
114
115 static TV_SFMT_ITEM_DEF Sfmt_item_def[] = {
116 { TV_AUDIO_SAMPLE_FMT_MULAW_U8 , "mulawU8" , "-t raw -U -b" },
117 { TV_AUDIO_SAMPLE_FMT_LIN_S8 , "linS8" , "-t raw -s -b" },
118 { TV_AUDIO_SAMPLE_FMT_LIN_U8 , "linU8" , "-t raw -u -b" },
119 { TV_AUDIO_SAMPLE_FMT_LIN_S16_LE, "linS16LE" , "-t raw -s -w" },
120 { TV_AUDIO_SAMPLE_FMT_LIN_U16_LE, "linU16LE" , "-t raw -u -w" },
121 { TV_AUDIO_SAMPLE_FMT_LIN_S16_BE, "linS16BE" , "-t raw -s -w -x" },
122 { TV_AUDIO_SAMPLE_FMT_LIN_U16_BE, "linU16BE" , "-t raw -u -w -x" }
123 };
124
125 static TV_CHAN_ITEM_DEF Chan_item_def[] = {
126 { FALSE , "mono" },
127 { TRUE , "stereo" }
128 };
129
130 static TV_RATE_ITEM_DEF Rate_item_def[] = {
131 { 8012 , "r8012" },
132 { 11025 , "r11025" },
133 { 22050 , "r22050" },
134 { 44100 , "r44100" },
135 };
136
137 /* ******************** Forward declarations ************** */
138 /* ******************** Function Definitions ************** */
139
140 /* SetMenuSelection: Set the active selection of the option menus */
SetMenuSelection(Widget menu_btn,TV_UINT32 choice)141 static void SetMenuSelection( Widget menu_btn, TV_UINT32 choice )
142 {
143 TV_INT32 i;
144 String label;
145
146 if ( menu_btn == Ffmt_menu_btn ) {
147 for ( i = 0; i < XtNumber( Ffmt_item_def ); i++ )
148 if ( Ffmt_item_def[i].fmt == choice ) {
149 XtVaGetValues( Ffmt_item_def[i].wgt, XtNlabel, &label,
150 NULL );
151 XtVaSetValues( menu_btn, XtNlabel, label,
152 NULL );
153 break;
154 }
155 if ( i >= XtNumber( Ffmt_item_def ) ) {
156 fprintf( stderr,
157 "TVAUDSAVDIALOGSetSel: Unsupported filefmt %ld\n",
158 choice );
159 exit(1);
160 }
161 Sel_ffmt = choice;
162 }
163 else if ( menu_btn == Sfmt_menu_btn ) {
164 for ( i = 0; i < XtNumber( Sfmt_item_def ); i++ )
165 if ( Sfmt_item_def[i].fmt == choice ) {
166 XtVaGetValues( Sfmt_item_def[i].wgt, XtNlabel, &label,
167 NULL );
168 XtVaSetValues( menu_btn, XtNlabel, label,
169 NULL );
170 break;
171 }
172 if ( i >= XtNumber( Sfmt_item_def ) ) {
173 fprintf( stderr,
174 "TVAUDSAVDIALOGSetSel: Unsupported sampfmt %ld\n",
175 choice );
176 exit(1);
177 }
178 Sel_sfmt = choice;
179 }
180 else if ( menu_btn == Chan_menu_btn ) {
181 for ( i = 0; i < XtNumber( Chan_item_def ); i++ )
182 if ( Chan_item_def[i].stereo == choice ) {
183 XtVaGetValues( Chan_item_def[i].wgt, XtNlabel, &label,
184 NULL );
185 XtVaSetValues( menu_btn, XtNlabel, label,
186 NULL );
187 break;
188 }
189 if ( i >= XtNumber( Chan_item_def ) ) {
190 fprintf( stderr, "TVAUDSAVDIALOGSetSel: Unsupported #chan %ld\n",
191 choice );
192 exit(1);
193 }
194 Sel_stereo = choice;
195 }
196 else if ( menu_btn == Rate_menu_btn ) {
197 for ( i = 0; i < XtNumber( Rate_item_def ); i++ )
198 if ( Rate_item_def[i].rate == choice ) {
199 XtVaGetValues( Rate_item_def[i].wgt, XtNlabel, &label,
200 NULL );
201 XtVaSetValues( menu_btn, XtNlabel, label,
202 NULL );
203 break;
204 }
205 if ( i >= XtNumber( Rate_item_def ) ) {
206 fprintf( stderr, "TVAUDSAVDIALOGSetSel: Unsupported rate %ld\n",
207 choice );
208 exit(1);
209 }
210 Sel_rate = choice;
211 }
212 else {
213 fprintf( stderr, "TVAUDSAV:SetMenuSelection: Bad menu_btn\n" );
214 exit(1);
215 }
216 }
217
218 /* UpdateButtons - Enable/disable btns based on state */
UpdateButtons()219 static void UpdateButtons()
220 {
221 TV_BOOL rec, stop, play, dismiss;
222
223 if ( Recording || Playing )
224 stop = True , rec = play = dismiss = False;
225 else
226 stop = False, rec = play = dismiss = True;
227
228 XtSetSensitive( Record_btn , rec );
229 XtSetSensitive( Stop_btn , stop );
230 XtSetSensitive( Playback_btn, dismiss );
231 XtSetSensitive( Dismiss_btn , dismiss );
232 }
233
234
235 /* PrepareForAudio - Take dialog values, save them in globals, open */
236 /* the audio device and setup its play/record parameters. */
PrepareForAudio(TV_BOOL recording,int buf_subdiv,int * dsp_fd,TV_INT32 * bps)237 static TV_BOOL PrepareForAudio( TV_BOOL recording, int buf_subdiv,
238 int *dsp_fd, TV_INT32 *bps )
239 {
240 TV_DISK *d = &G_glob.disk;
241 TV_BOOL error = False;
242 String filename;
243 TV_SOUND snd;
244 char *err_msg;
245
246 *dsp_fd = -1;
247
248 /* ...Filename base */
249 XtVaGetValues( Text_wgt, XtNstring, &filename,
250 NULL );
251 if ( filename == NULL )
252 filename = "";
253 if ( strlen( filename ) == 0 ) {
254 XUTILDialogPause( TVTOPLEVEL, "Error", "No filename specified.",
255 TV_DIALOG_TYPE_OK );
256 error = True;
257 goto RETURN;
258 }
259
260 /* Save off filename */
261 d->fn_audio_base[0] = '\0';
262 strncat( d->fn_audio_base, filename, sizeof( d->fn_audio_base ) - 1 );
263
264 /* Gather audio capture params */
265 snd.sample_fmt = Sel_sfmt,
266 snd.stereo = Sel_stereo,
267 snd.sample_rate = Sel_rate,
268 snd.buf = NULL,
269 snd.bytes = 0;
270
271 /* FIXME: If AU file fmt selected, validate capture fmt */
272
273 /* Open & configure DSP device */
274 if ( !TVAUDIOOpenDsp( &snd, recording, dsp_fd, &err_msg ) ) {
275 XUTILDialogPause( TVTOPLEVEL, "Error", err_msg, TV_DIALOG_TYPE_OK );
276 *dsp_fd = -1;
277 error = True;
278 goto RETURN;
279 }
280
281 *bps = TVAUDIOGetSampleFmtBps( snd.sample_fmt );
282
283 /* Ok, these record values are good, so save 'em off for the next time */
284 d->audio.sample_fmt = Sel_sfmt;
285 d->audio.stereo = Sel_stereo;
286 d->audio.sample_rate = Sel_rate;
287
288 RETURN:
289 /* Done recording, close up shop */
290 if ( error && ( *dsp_fd >= 0 )) {
291 close( *dsp_fd );
292 *dsp_fd = -1;
293 }
294 return !error;
295 }
296
297 /* DoCmdFailDialog - Display a dialog citing the command that failed */
298 /* and its exit status; wait on user to dismiss. */
DoCmdFailDialog(char * cmd[],int status)299 static void DoCmdFailDialog(
300 char *cmd[],
301 int status )
302 {
303 char msg[ 2*MAXPATHLEN + 160 ];
304 TV_INT32 i;
305
306 sprintf( msg, "Audio conversion failed.\nCMD = " );
307 for ( i = 0; cmd[i] != NULL; i++ )
308 sprintf( msg+strlen(msg), "%s ", cmd[i] );
309 sprintf( msg+strlen(msg), "\nSTATUS = 0x%.4x", status );
310 XUTILDialogPause( TVTOPLEVEL, "Error", msg, TV_DIALOG_TYPE_OK );
311 }
312
313
314 /* RecordCmdCancelTestCB */
315 /* - Used by RecordCmd invocations of XUTILRunCmdAllowCancel to */
316 /* identify when the the user aborted the operation. */
RecordCmdCancelTestCB(void * cb_data)317 static TV_BOOL RecordCmdCancelTestCB( void *cb_data )
318 {
319 TV_AUDIO_CMD_STATE *state = (TV_AUDIO_CMD_STATE *) cb_data;
320
321 return !XtIsRealized( state->dialog_shell );
322 }
323
324
325 /* RecordCmdDoneCB */
326 /* - Called when audio conversion cmd completes or is aborted */
RecordCmdDoneCB(TV_BOOL aborted,int status,void * cb_data)327 static void RecordCmdDoneCB( TV_BOOL aborted, int status, void *cb_data )
328 {
329 TV_AUDIO_CMD_STATE *state = (TV_AUDIO_CMD_STATE *) cb_data;
330
331 /* At this stage, always pull down the "wait" dialog and destroy it */
332 if ( !aborted || ( status != 0 ) )
333 XtPopdown( state->dialog_shell );
334 XtDestroyWidget( state->dialog_shell );
335
336 /* The TVAUDIOCNVT code has already told the user if a conversion */
337 /* command failed, so no need to do anything else about that here. */
338 /* If the command failed, tell the user about it */
339
340 /* Do post-cmd cleanup */
341 unlink( state->fname_in );
342 if ( aborted )
343 unlink( state->fname_out );
344
345 free( state );
346 return;
347 }
348
349
350 /* RecordCmdCB - Check record preconditions and start recording */
RecordCmdCB(Widget w,XtPointer cl,XtPointer cb)351 static void RecordCmdCB( Widget w, XtPointer cl, XtPointer cb )
352 {
353 TV_DISK *d = &G_glob.disk;
354 String filename;
355 int out_fd = -1,
356 dsp_fd = -1;
357 TV_BOOL exists;
358 struct stat sb;
359 TV_INT32 bps;
360 ssize_t read_bytes;
361 char msg[MAXPATHLEN+160],
362 recname[ MAXPATHLEN ];
363 TV_BOOL mute_on,
364 conv_reqd;
365 TV_AUDIO_CMD_STATE *state = NULL;
366
367 if ( Dialog_wgt == NULL )
368 return;
369
370 /* If already recording, ignore user */
371 if ( Recording || Playing ) {
372 XBell( TVDISPLAY, 100 );
373 return;
374 }
375
376 /* Save off original mute state */
377 TVAUDIOGetMuteState( &mute_on );
378
379 /* Grab values off dialog */
380 if ( !PrepareForAudio( TRUE, 1, &dsp_fd, &bps ) )
381 goto RETURN;
382
383 filename = d->fn_audio_base;
384
385 TVAUDIOSelectLineForRecord();
386
387 /* See if the output file exists; if so, we need to verify user wants */
388 /* to overwrite. */
389 exists = TRUE;
390 if ( stat( filename, &sb ) < 0 ) {
391 if ( errno != ENOENT ) {
392 fprintf( stderr, "Whoah! stat() failed on '%s'.\n", filename );
393 XBell( TVDISPLAY, 100 );
394 goto RETURN;
395 }
396 exists = FALSE;
397 }
398
399 if ( exists ) {
400 sprintf( msg, "This file exists (%s).\nOverwrite?", filename );
401 if ( XUTILDialogPause( TVTOPLEVEL, "Confirm", msg,
402 TV_DIALOG_TYPE_YES_NO ) != TV_DIALOG_YES )
403 goto RETURN;
404 unlink( filename );
405 }
406
407 /* Will conversion be required after we're done recording? */
408 conv_reqd = ( Sel_ffmt != TV_AUDIO_FILE_FMT_RAW ) &&
409 !( Sel_ffmt == TV_AUDIO_FILE_FMT_SUNAU &&
410 Sel_sfmt == TV_AUDIO_SAMPLE_FMT_MULAW_U8 &&
411 Sel_stereo == False &&
412 Sel_rate == 8012 );
413
414 /* Determine file to record to initially (tmpfile if conv req'd) */
415 recname[0] = '\0';
416 strncat( recname, filename, sizeof( recname )-1 );
417 if ( conv_reqd )
418 strcat( recname, ".fxtvTMP" );
419
420 /* Now open the desired output sound file. */
421 if ( (out_fd = open( recname, O_CREAT|O_TRUNC|O_WRONLY, 0666 )) < 0 ) {
422 XUTILDialogPause( TVTOPLEVEL, "Error", "Couldn't open output file\n",
423 TV_DIALOG_TYPE_OK );
424 goto RETURN;
425 }
426
427 Recording = True;
428
429 /* Make sure mute is off, & disable all but stop btn */
430 TVAUDIOSetMuteState( False );
431 UpdateButtons();
432
433 /* Flush X events (update GUI buttons, etc.) */
434 XSync( TVDISPLAY, False );
435
436 /* All right sports fans, we're finally ready to record */
437 read_bytes = RDWR_TIME * (Sel_rate * bps * (Sel_stereo ? 2 : 1));
438 read_bytes = (read_bytes+3)/4*4;
439
440 assert( read_bytes <= MAX_RDWR_BUF_SIZE );
441
442 while ( Recording ) {
443 XEvent ev;
444 char buf[ MAX_RDWR_BUF_SIZE ];
445 ssize_t len;
446
447 if ( (len = read( dsp_fd, buf, read_bytes )) < 0 ) {
448 sprintf( msg, "read() failed on audio device: %s\n",
449 strerror(errno) );
450 XUTILDialogPause( TVTOPLEVEL, "Error", msg, TV_DIALOG_TYPE_OK );
451 Recording = False;
452 break;
453 }
454 if ( write( out_fd, buf, len ) < 0 ) {
455 sprintf( msg, "write() failed on output file: %s\n",
456 strerror(errno) );
457 XUTILDialogPause( TVTOPLEVEL, "Error", msg, TV_DIALOG_TYPE_OK );
458 Recording = False;
459 break;
460 }
461
462 /* FIXME: XtAppNextEvent will block if just a timer event is */
463 /* outstanding -- is there a better way to do this? */
464 while ( XtAppPending( TVAPPCTX ) & ~XtIMTimer ) {
465 XtAppNextEvent( TVAPPCTX, &ev );
466 XtDispatchEvent( &ev );
467 }
468 }
469
470 close( dsp_fd ); dsp_fd = -1;
471 close( out_fd ); out_fd = -1;
472
473 /* If the selected format requires conversion, do it */
474 if ( conv_reqd ) {
475 TV_SOUND_FMT fmt_in,
476 fmt_out;
477 Widget dialog_shell;
478
479 /* Alloc state structure for use during command execution */
480 if ( (state = malloc( sizeof( *state ) )) == NULL )
481 TVUTILOutOfMemory();
482 memset( state, '\0', sizeof( *state ) );
483
484 dialog_shell = XUTILDialogBuild( TVTOPLEVEL, "Please Wait",
485 "Format conversion in progress...",
486 TV_DIALOG_TYPE_CANCEL );
487 XUTILXtPopup( dialog_shell, XtGrabNone, TVTOPLEVEL );
488
489 /* Fill in from- and to- sound formats */
490 fmt_in.file_fmt = TV_AUDIO_FILE_FMT_RAW;
491 fmt_in.samp_fmt = Sel_sfmt,
492 fmt_in.stereo = Sel_stereo,
493 fmt_in.samp_rate = Sel_rate;
494
495 memcpy( &fmt_out, &fmt_in, sizeof( fmt_out ) );
496 fmt_out.file_fmt = Sel_ffmt;
497
498 /* Execute conversion cmd & wait on it to finish or user to cancel. */
499 /* FIXME: Also displaying output of child as it runs might */
500 /* be useful. */
501 memcpy( &state->fmt_out, &fmt_out, sizeof( state->fmt_out ) );
502 strcpy( state->fname_in , recname );
503 strcpy( state->fname_out, filename );
504 state->dialog_shell = dialog_shell;
505
506 /* Kick off the conversion in the background */
507 TVAUDIOCNVTConvertFormat( -1, recname , &fmt_in,
508 -1, filename, &fmt_out,
509 RecordCmdCancelTestCB, state,
510 RecordCmdDoneCB , state );
511 state = NULL;
512 }
513
514 RETURN:
515 /* Done recording, close up shop */
516 if ( dsp_fd >= 0 )
517 close( dsp_fd );
518 if ( out_fd >= 0 )
519 close( out_fd );
520 if ( state != NULL )
521 free( state );
522
523 TVAUDIOSetMuteState( mute_on );
524
525 /* FIXME: Check code - make sure no problem (other than CPU) starting */
526 /* another record/play while conversion for previous still going on. */
527 UpdateButtons();
528 }
529
530
531 /* RecordCmdCB - Stop recording, if we're recording now */
StopCmdCB(Widget w,XtPointer cl,XtPointer cb)532 static void StopCmdCB( Widget w, XtPointer cl, XtPointer cb )
533 {
534 if ( !Recording && !Playing ) {
535 XBell( TVDISPLAY, 100 );
536 return;
537 }
538
539 Recording = Playing = False;
540 UpdateButtons();
541 }
542
543
544 /* DismissCmdCB - Dismiss the dialog, if we're not recording */
DismissCmdCB(Widget w,XtPointer cl,XtPointer cb)545 static void DismissCmdCB( Widget w, XtPointer cl, XtPointer cb )
546 {
547 if ( Recording || Playing ) {
548 XBell( TVDISPLAY, 100 );
549 return;
550 }
551
552 XtPopdown( Dialog_wgt );
553 }
554
555
556 /* PlaybackCmdCancelTestCB */
557 /* - Used by PlaybackCmd invocations of XUTILRunCmdAllowCancel to */
558 /* identify when the the user aborted the operation. */
PlaybackCmdCancelTestCB(void * cb_data)559 static TV_BOOL PlaybackCmdCancelTestCB( void *cb_data )
560 {
561
562 return !Playing;
563 }
564
565
566 /* PlaybackCmdDoneCB */
567 /* - Called when playback cmd completes or is aborted */
PlaybackCmdDoneCB(TV_BOOL aborted,int status,void * cb_data)568 static void PlaybackCmdDoneCB( TV_BOOL aborted, int status, void *cb_data )
569 {
570 TV_AUDIO_CMD_STATE *state = (TV_AUDIO_CMD_STATE *) cb_data;
571
572 Playing = False;
573 UpdateButtons();
574
575 /* If the command failed, tell the user about it */
576 if ( !aborted && ( status != 0 ) )
577 DoCmdFailDialog( state->cmd, status );
578
579 TVAUDIOSetMuteState( state->mute_on );
580
581 /* Do post-cmd cleanup */
582 free( state->cmd );
583 free( state->tmpstr );
584
585 free( state );
586 return;
587 }
588
589
590 /* PlaybackCmdCB - Playback the selected audio file */
PlaybackCmdCB(Widget w,XtPointer cl,XtPointer cb)591 static void PlaybackCmdCB( Widget w, XtPointer cl, XtPointer cb )
592 {
593 TV_DISK *d = &G_glob.disk;
594 String filename;
595 int dsp_fd = -1,
596 in_fd = -1;
597 #ifdef OLDSTUFF
598 ssize_t read_bytes;
599 char msg[160];
600 #endif
601 TV_INT32 bps;
602 TV_BOOL mute_on,
603 conv_reqd;
604 TV_INT32 i,j;
605 TV_BOOL cmd_running = FALSE;
606 TVUTIL_PIPE_END end[3] = {{ -1, FALSE }, { -1, FALSE }, { -1 } };
607 TV_AUDIO_CMD_STATE *state = NULL;
608 char shell_cmd[ 2*MAXPATHLEN+80 ];
609
610 if ( Dialog_wgt == NULL )
611 return;
612
613 /* If already recording, ignore user */
614 if ( Recording || Playing ) {
615 XBell( TVDISPLAY, 100 );
616 return;
617 }
618
619 /* Save off original mute state */
620 TVAUDIOGetMuteState( &mute_on );
621
622 /* Grab values off dialog */
623 if ( !PrepareForAudio( FALSE, 1, &dsp_fd, &bps ) )
624 goto RETURN;
625
626 filename = d->fn_audio_base;
627
628 /* Now open the desired input sound file. */
629 if ( (in_fd = open( filename, O_RDONLY, 0 )) < 0 ) {
630 XUTILDialogPause( TVTOPLEVEL, "Error", "Couldn't open input file\n",
631 TV_DIALOG_TYPE_OK );
632 goto RETURN;
633 }
634 end[0].fd = in_fd;
635 end[1].fd = dsp_fd;
636
637 Playing = True;
638
639 /* Make sure mute is on, & disable all but stop btn */
640 TVAUDIOSetMuteState( True );
641 UpdateButtons();
642
643 /* Flush X events (update GUI buttons, etc.) */
644 XSync( TVDISPLAY, False );
645
646 /* Will conversion be required to play this file? */
647 conv_reqd = ( Sel_ffmt != TV_AUDIO_FILE_FMT_RAW ) &&
648 !( Sel_ffmt == TV_AUDIO_FILE_FMT_SUNAU &&
649 Sel_sfmt == TV_AUDIO_SAMPLE_FMT_MULAW_U8 &&
650 Sel_stereo == False &&
651 Sel_rate == 8012 );
652
653 /* Alloc state structure for use during command execution */
654 if ( (state = malloc( sizeof( *state ) )) == NULL )
655 TVUTILOutOfMemory();
656 memset( state, '\0', sizeof( *state ) );
657
658 /* Construct conversion command. */
659 /* No conversion req'd -- use "cat" */
660 /* MPEG-2 or -3 -- use user command */
661 /* All else -- use "sox" */
662 if ( !conv_reqd )
663 strcpy( shell_cmd, "cat" );
664 else if (( Sel_ffmt == TV_AUDIO_FILE_FMT_MPEG2 ) ||
665 ( Sel_ffmt == TV_AUDIO_FILE_FMT_MPEG3 )) {
666 char *play_cmd;
667
668 /* Grab the right player cmd */
669 if ( Sel_ffmt == TV_AUDIO_FILE_FMT_MPEG2 )
670 play_cmd = App_res.play_cmd_mpeg2;
671 else
672 play_cmd = App_res.play_cmd_mpeg3;
673
674 if ( strspn( play_cmd, " " ) == strlen( play_cmd ) ) {
675 XUTILDialogPause( TVTOPLEVEL, "Error", "No MPEG conv cmd.",
676 TV_DIALOG_TYPE_OK );
677 free(state);
678 Playing = False;
679 goto RETURN;
680 }
681
682 /* Build Cmd: "<play cmd> -" */
683 sprintf( shell_cmd, "%s -", play_cmd );
684
685 /* Let MPEG players open /dev/dsp themselves */
686 close( dsp_fd );
687 dsp_fd = -1;
688 end[1].fd = -1;
689 }
690 else {
691
692 /* FIXME: Make Sel_* pointers to records */
693 for ( i = 0; i < XtNumber( Sfmt_item_def ); i++ )
694 if ( Sfmt_item_def[i].fmt == Sel_sfmt )
695 break;
696 for ( j = 0; j < XtNumber( Ffmt_item_def ); j++ )
697 if ( Ffmt_item_def[j].fmt == Sel_ffmt )
698 break;
699
700 /* Build Cmd: "sox %s - %s -c %d -r %d -" */
701 sprintf( shell_cmd, "sox %s - %s -c %d -r %ld -",
702 Ffmt_item_def[j].sox_opt, Sfmt_item_def[i].sox_opt,
703 Sel_stereo ? 2 : 1, Sel_rate );
704 }
705 TVUTILCmdStrToArgList( shell_cmd, &state->cmd, &state->tmpstr );
706
707
708 /* We now use "cat" as a subprocess when no conv is req'd (see above) */
709 #ifdef OLDSTUFF
710 if ( !conv_reqd ) {
711 /* FIXME: We probably wanna fork a child to handle this rather */
712 /* than try to handle X events AND blocking reads/writes. */
713 /* This is more of a problem for low-bitrate audio where the */
714 /* buffers fill and we just block for a while. */
715 read_bytes = RDWR_TIME * (Sel_rate * bps * (Sel_stereo ? 2 : 1));
716 read_bytes = (read_bytes+3)/4*4;
717
718 assert( read_bytes <= MAX_RDWR_BUF_SIZE );
719
720 while ( Playing ) {
721 XEvent ev;
722 char buf[ MAX_RDWR_BUF_SIZE ];
723 ssize_t len;
724
725 if ( (len = read( in_fd, buf, read_bytes )) < 0 ) {
726 sprintf( msg, "read() failed on input file: %s\n",
727 strerror(errno) );
728 XUTILDialogPause( TVTOPLEVEL, "Error", msg,
729 TV_DIALOG_TYPE_OK);
730 Playing = False;
731 break;
732 }
733 if ( len == 0 ) {
734 Playing = False;
735 break;
736 }
737 if ( write( dsp_fd, buf, len ) < 0 ) {
738 sprintf( msg, "write() failed on audio device: %s\n",
739 strerror(errno) );
740 XUTILDialogPause( TVTOPLEVEL, "Error", msg,
741 TV_DIALOG_TYPE_OK);
742 Playing = False;
743 break;
744 }
745
746 while ( XtAppPending( TVAPPCTX ) & ~XtIMTimer ) {
747 XtAppNextEvent( TVAPPCTX, &ev );
748 XtDispatchEvent( &ev );
749 }
750 }
751 }
752 else { /* conv_reqd */
753 #endif
754
755
756 /* All right sports fans, we're finally ready to play. */
757 /* Execute conversion cmd & wait on it to finish or user to cancel. */
758 /* FIXME: Also displaying output of child as it runs might */
759 /* be useful. */
760 state->fmt_out.file_fmt = Sel_ffmt,
761 state->fmt_out.samp_fmt = Sel_sfmt,
762 state->fmt_out.stereo = Sel_stereo,
763 state->fmt_out.samp_rate = Sel_rate,
764 state->mute_on = mute_on;
765
766 XUTILRunCmdAllowCancel( TVAPPCTX, state->cmd, end,
767 PlaybackCmdCancelTestCB, state,
768 PlaybackCmdDoneCB , state );
769 state = NULL;
770 cmd_running = TRUE;
771
772 RETURN:
773 /* Done playing, close up shop */
774 if ( dsp_fd >= 0 )
775 close( dsp_fd );
776 if ( in_fd >= 0 )
777 close( in_fd );
778 if ( state != NULL )
779 free( state );
780
781 if ( !cmd_running ) {
782 TVAUDIOSetMuteState( mute_on );
783 UpdateButtons();
784 }
785 }
786
787
788 static void TextValUpdate( Widget text_wgt, char *str )
789 {
790 XawTextBlock tblk;
791 char *old_str;
792 int old_len;
793
794 assert( text_wgt != NULL );
795
796 memset( &tblk, '\0', sizeof( tblk ) );
797 tblk.firstPos = 0;
798 tblk.length = strlen( str );
799 tblk.ptr = str;
800 tblk.format = XawFmt8Bit;
801
802 XtVaGetValues( text_wgt, XtNstring, &old_str,
803 NULL );
804 old_len = (old_str == NULL) ? 0 : strlen( old_str );
805 XawTextReplace( text_wgt, 0, old_len, &tblk );
806 }
807
808 /* FFmtMenuItemCB - Update menu button text with selected choice */
809 static void FFmtMenuItemCB( Widget w, XtPointer cl, XtPointer cb )
810 {
811 Widget menu_btn = Ffmt_menu_btn;
812 TV_FFMT_ITEM_DEF *def = (TV_FFMT_ITEM_DEF *) cl;
813
814 SetMenuSelection( menu_btn, def->fmt );
815
816 /* If Sun AU, default to 8-bit MULAW, Mono, & 8012 s/s */
817 if ( def->fmt == TV_AUDIO_FILE_FMT_SUNAU ) {
818 SetMenuSelection( Sfmt_menu_btn, TV_AUDIO_SAMPLE_FMT_MULAW_U8 );
819 SetMenuSelection( Chan_menu_btn, FALSE );
820 SetMenuSelection( Rate_menu_btn, 8012 );
821 }
822
823 /* If any of the rest, go for the max */
824 else {
825 SetMenuSelection( Sfmt_menu_btn, TV_AUDIO_SAMPLE_FMT_LIN_S16_LE );
826 SetMenuSelection( Chan_menu_btn, TRUE );
827 SetMenuSelection( Rate_menu_btn, 44100 );
828 }
829 }
830
831
832 /* SFmtMenuItemCB - Update menu button text with selected choice */
833 static void SFmtMenuItemCB( Widget w, XtPointer cl, XtPointer cb )
834 {
835 Widget menu_btn = Sfmt_menu_btn;
836 TV_SFMT_ITEM_DEF *def = (TV_SFMT_ITEM_DEF *) cl;
837
838 SetMenuSelection( menu_btn, def->fmt );
839
840 /* If 8-bit ULAW, default to Mono & 8012 s/s */
841 if ( def->fmt == TV_AUDIO_SAMPLE_FMT_MULAW_U8 ) {
842 SetMenuSelection( Chan_menu_btn, FALSE );
843 SetMenuSelection( Rate_menu_btn, 8012 );
844 }
845 }
846
847
848 /* ChanMenuItemCB - Update menu button text with selected choice */
849 static void ChanMenuItemCB( Widget w, XtPointer cl, XtPointer cb )
850 {
851 Widget menu_btn = Chan_menu_btn;
852 TV_CHAN_ITEM_DEF *def = (TV_CHAN_ITEM_DEF *) cl;
853
854 SetMenuSelection( menu_btn, def->stereo );
855 }
856
857
858 /* RateMenuItemCB - Update menu button text with selected choice */
859 static void RateMenuItemCB( Widget w, XtPointer cl, XtPointer cb )
860 {
861 Widget menu_btn = Rate_menu_btn;
862 TV_RATE_ITEM_DEF *def = (TV_RATE_ITEM_DEF *) cl;
863
864 SetMenuSelection( menu_btn, def->rate );
865 }
866
867
868 static void TVAudSavDialogBuild( Widget *dialog_wgt )
869 {
870 Position x, y;
871 Dimension width;
872 Widget w, box, gbox, cbox, fbox, lbox, form, menu_shell;
873 TV_INT32 i;
874 XtTranslations transl;
875
876 /* Create the dialog widgets */
877 *dialog_wgt = XtVaCreatePopupShell( "audioSaveDialog",
878 transientShellWidgetClass, TVTOPLEVEL,
879 NULL );
880
881 box = XtVaCreateManagedWidget( "mainBox", boxWidgetClass, *dialog_wgt,
882 XtNorientation, XtorientVertical,
883 NULL );
884
885 gbox = XtVaCreateManagedWidget( "groupBox", boxWidgetClass, box,
886 XtNorientation, XtorientVertical,
887 NULL );
888
889 fbox = XtVaCreateManagedWidget( "fileBox", boxWidgetClass, gbox,
890 XtNorientation, XtorientVertical,
891 NULL );
892
893 cbox = XtVaCreateManagedWidget( "fileBaseBox", boxWidgetClass, fbox,
894 XtNorientation, XtorientHorizontal,
895 NULL );
896
897 w = XtVaCreateManagedWidget( "fileBaseLabel", labelWidgetClass, cbox,
898 XtNjustify, XtJustifyRight,
899 NULL );
900
901 Text_wgt = XtVaCreateManagedWidget( "fileBaseText", asciiTextWidgetClass,
902 cbox,
903 XtNtype , XawAsciiString,
904 XtNuseStringInPlace, False,
905 XtNscrollHorizontal, XawtextScrollNever,
906 XtNscrollVertical , XawtextScrollNever,
907 XtNdisplayCaret , False,
908 XtNeditType , XawtextEdit,
909 XtNresize , XawtextResizeNever,
910 NULL );
911
912 /* Text widget translation overrides */
913 transl = XtParseTranslationTable( G_transl_ovr_ascii_text );
914 XtOverrideTranslations( Text_wgt, transl );
915 transl = XtParseTranslationTable( G_transl_ovr_ascii_text_1line );
916 XtOverrideTranslations( Text_wgt, transl );
917
918 cbox = XtVaCreateManagedWidget( "fileFmtBox", boxWidgetClass, fbox,
919 XtNorientation, XtorientHorizontal,
920 NULL );
921
922 w = XtVaCreateManagedWidget( "fileFmtLabel", labelWidgetClass, cbox,
923 XtNjustify, XtJustifyRight,
924 NULL );
925
926
927 /* File Format Choice Box */
928 Ffmt_menu_btn = XtVaCreateManagedWidget( "fileFmtMenu",
929 menuButtonWidgetClass, cbox,
930 XtNresize, XawtextResizeNever,
931 NULL );
932
933 menu_shell = XtVaCreatePopupShell( "menu",
934 simpleMenuWidgetClass, Ffmt_menu_btn,
935 NULL );
936
937 for ( i = 0; i < XtNumber( Ffmt_item_def ); i++ ) {
938 Widget item;
939
940 item = XtVaCreateManagedWidget( Ffmt_item_def[i].file_ext,
941 smeBSBObjectClass,
942 menu_shell,
943 NULL );
944 Ffmt_item_def[i].wgt = item;
945
946 XtAddCallback( item, XtNcallback, FFmtMenuItemCB, &Ffmt_item_def[i] );
947 }
948
949
950
951 fbox = XtVaCreateManagedWidget( "captureBox", boxWidgetClass, gbox,
952 XtNorientation, XtorientHorizontal,
953 NULL );
954
955 lbox = XtVaCreateManagedWidget( "sampFmtLabelBox", boxWidgetClass, fbox,
956 XtNorientation, XtorientVertical,
957 NULL );
958
959 w = XtVaCreateManagedWidget( "sampFmtLabel", labelWidgetClass, lbox,
960 XtNresize, False,
961 XtNencoding, XawTextEncoding8bit,
962 NULL );
963
964 cbox = XtVaCreateManagedWidget( "sampFmtWgtBox", boxWidgetClass, fbox,
965 XtNorientation, XtorientVertical,
966 NULL );
967
968 /* Sample Format Choice Box */
969 Sfmt_menu_btn = XtVaCreateManagedWidget( "sampFmtMenu",
970 menuButtonWidgetClass, cbox,
971 XtNresize, XawtextResizeNever,
972 NULL );
973
974 menu_shell = XtVaCreatePopupShell( "menu",
975 simpleMenuWidgetClass, Sfmt_menu_btn,
976 NULL );
977
978 for ( i = 0; i < XtNumber( Sfmt_item_def ); i++ ) {
979 Widget item;
980
981 item = XtVaCreateManagedWidget( Sfmt_item_def[i].wgt_name,
982 smeBSBObjectClass,
983 menu_shell,
984 NULL );
985 Sfmt_item_def[i].wgt = item;
986
987 XtAddCallback( item, XtNcallback, SFmtMenuItemCB, &Sfmt_item_def[i] );
988 }
989
990 /* # Channels Choice Box */
991 w = XtVaCreateManagedWidget( "chanMenuBox", boxWidgetClass, cbox,
992 XtNorientation, XtorientHorizontal,
993 NULL );
994
995 Chan_menu_btn = XtVaCreateManagedWidget( "chanMenu",
996 menuButtonWidgetClass, w,
997 XtNresize, XawtextResizeNever,
998 NULL );
999
1000 menu_shell = XtVaCreatePopupShell( "menu",
1001 simpleMenuWidgetClass, Chan_menu_btn,
1002 NULL );
1003
1004 for ( i = 0; i < XtNumber( Chan_item_def ); i++ ) {
1005 Widget item;
1006
1007 item = XtVaCreateManagedWidget( Chan_item_def[i].wgt_name,
1008 smeBSBObjectClass,
1009 menu_shell,
1010 NULL );
1011 Chan_item_def[i].wgt = item;
1012
1013 XtAddCallback( item, XtNcallback, ChanMenuItemCB, &Chan_item_def[i] );
1014 }
1015
1016 /* Sample Rate Choice Box */
1017 w = XtVaCreateManagedWidget( "rateMenuBox", boxWidgetClass, cbox,
1018 XtNorientation, XtorientHorizontal,
1019 NULL );
1020
1021 Rate_menu_btn = XtVaCreateManagedWidget( "rateMenu",
1022 menuButtonWidgetClass, w,
1023 XtNresize, XawtextResizeNever,
1024 NULL );
1025
1026 menu_shell = XtVaCreatePopupShell( "menu",
1027 simpleMenuWidgetClass, Rate_menu_btn,
1028 NULL );
1029
1030 for ( i = 0; i < XtNumber( Rate_item_def ); i++ ) {
1031 Widget item;
1032
1033 item = XtVaCreateManagedWidget( Rate_item_def[i].wgt_name,
1034 smeBSBObjectClass,
1035 menu_shell,
1036 NULL );
1037 Rate_item_def[i].wgt = item;
1038
1039 XtAddCallback( item, XtNcallback, RateMenuItemCB, &Rate_item_def[i] );
1040 }
1041
1042 /* Action button form */
1043 form = XtVaCreateManagedWidget( "actionForm", formWidgetClass, box,
1044 NULL );
1045
1046 w = XtVaCreateManagedWidget( "recordCmd", commandWidgetClass, form,
1047 NULL );
1048 XtAddCallback( w, XtNcallback, RecordCmdCB, NULL );
1049 Record_btn = w;
1050
1051 w = XtVaCreateManagedWidget( "stopCmd", commandWidgetClass, form,
1052 XtNfromHoriz, w,
1053 NULL );
1054 XtAddCallback( w, XtNcallback, StopCmdCB, NULL );
1055 Stop_btn = w;
1056
1057 w = XtVaCreateManagedWidget( "playbackCmd", commandWidgetClass, form,
1058 XtNfromHoriz, w,
1059 NULL );
1060 XtAddCallback( w, XtNcallback, PlaybackCmdCB, NULL );
1061 Playback_btn = w;
1062
1063 w = XtVaCreateManagedWidget( "dismissCmd", commandWidgetClass, form,
1064 XtNfromHoriz, w,
1065 NULL );
1066 XtAddCallback( w, XtNcallback, DismissCmdCB, NULL );
1067 Dismiss_btn = w;
1068
1069 /* By default, position dialog just to right of main TV window */
1070 XtVaGetValues( TVTOPLEVEL, XtNwidth, &width,
1071 NULL);
1072
1073 XtTranslateCoords( TVTOPLEVEL, width, 0, &x, &y );
1074
1075 XtVaSetValues( *dialog_wgt, XtNx, x,
1076 XtNy, y,
1077 NULL );
1078 }
1079
1080
1081 void TVAUDSAVDIALOGPopUp()
1082 {
1083 /* Do dialog */
1084 if ( Dialog_wgt == NULL )
1085 TVAudSavDialogBuild( &Dialog_wgt );
1086
1087 TVAUDSAVDIALOGResync();
1088
1089 XUTILXtPopup( Dialog_wgt, XtGrabNone, TVTOPLEVEL );
1090 }
1091
1092 void TVAUDSAVDIALOGResync()
1093 {
1094 TV_DISK *d = &G_glob.disk;
1095
1096 /* FIXME: Also install EnterNotify handler for this dialog to */
1097 /* resync values on entry of it's shell. */
1098
1099 if ( Dialog_wgt == NULL )
1100 return;
1101
1102 /* Set text field to current base filename */
1103 TextValUpdate( Text_wgt, d->fn_audio_base );
1104
1105 /* Set selections based on active format */
1106 SetMenuSelection( Ffmt_menu_btn, d->audio.file_fmt );
1107 SetMenuSelection( Sfmt_menu_btn, d->audio.sample_fmt );
1108 SetMenuSelection( Chan_menu_btn, d->audio.stereo );
1109 SetMenuSelection( Rate_menu_btn, d->audio.sample_rate );
1110
1111 /* Set button sensitivites */
1112 UpdateButtons();
1113 }
1114
1115