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