1
/* $Header: /cvsroot/qgo/qgo/src/wavfile.c,v 1.8 2005/09/08 18:46:31 yfh2 Exp $
2
* Copyright: wavfile.c (c) Erik de Castro Lopo erikd@zip.com.au
4
* wavfile.c - Functions for reading and writing MS-Windoze .WAV files.
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2, or (at your option)
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* This code was originally written to manipulate Windoze .WAV files
17
* under i386 Linux. Please send any bug reports or requests for
18
* enhancements to : erikd@zip.com.au
21
* Revision 1.4 2004/05/26 17:44:05 yfh2
22
* 0.2.1 b1 preparing BSD compatibiility
24
* Revision 1.3 2003/04/14 07:41:49 frosla
25
* 0.0.16b8: skipped some widget; cosmetics
27
* Revision 1.1.1.1 1999/11/21 19:50:56 wwg
28
* Import wavplay-1.3 into CVS
30
* Revision 1.2 1997/04/17 00:59:55 wwg
31
* Fixed so that a fmt chunk that is larger than expected
32
* is accepted, but if smaller - _then_ treat it as an
35
* Revision 1.1 1997/04/14 00:14:38 wwg
39
* 16.02.97 - Erik de Castro Lopo
40
* Ported from MS-Windows to Linux for Warren W. Gay's
46
static const char rcsid[] = "@(#)wavfile.c $Revision: 1.8 $";
59
#include <sys/types.h>
61
#include <sys/ioctl.h>
63
#include <linux/soundcard.h>
69
#define SwapLE16(x) ((((u_int16_t)x)<<8)|(((u_int16_t)x)>>8))
70
#define SwapLE32(x) ((((u_int32_t)x)<<24)|((((u_int32_t)x)<<8)&0x00FF0000) \
71
|((((u_int32_t)x)>>8)&0x0000FF00)|(((u_int32_t)x)>>24))
73
#define SwapLE16(x) (x)
74
#define SwapLE32(x) (x)
80
#define BUFFERSIZE 1024
81
#define PCM_WAVE_FORMAT 1
86
#define RECPLAY_UPDATES_PER_SEC 3
91
u_int16_t wFormatTag ;
93
u_int32_t dwSamplesPerSec ;
94
u_int32_t dwAvgBytesPerSec ;
95
u_int16_t wBlockAlign ;
96
u_int16_t wBitsPerSample ;
105
u_int16_t wFormatTag ;
106
u_int16_t nChannels ;
107
u_int32_t nSamplesPerSec ;
108
u_int32_t nAvgBytesPerSec ;
109
u_int16_t nBlockAlign ;
110
u_int16_t wBitsPerSample ;
112
u_int32_t nDataBytes ;
115
/*=================================================================================================*/
117
char* findchunk (char* s1, char* s2, size_t n) ;
119
/*=================================================================================================*/
122
static WAVE_HEADER waveheader =
123
{ { 'R', 'I', 'F', 'F' },
125
{ 'W', 'A', 'V', 'E' },
126
{ 'f', 'm', 't', ' ' },
128
PCM_WAVE_FORMAT, /* wFormatTag*/
134
{ 'd', 'a', 't', 'a' },
138
static ErrFunc v_erf; /* wwg: Error reporting function */
140
static void _v_erf(const char *, va_list); /* This module's error reporting function */
141
static char emsg[2048];
146
* Error reporting function for this source module:
149
err(const char *format,...) {
151
fprintf(stdout, "error : %s \n",format);
152
/*/if ( v_erf == NULL )*/
153
/*/ return; /* Only report error if we have function */
155
_v_erf(format,ap); /* Use caller's supplied function */
161
_v_erf(const char *format,va_list ap) {
162
vsprintf(emsg,format,ap); /* Capture message into emsg[] */
167
int WaveReadHeader (int wavefile, int* channels, u_long* samplerate, int* samplebits, u_long* samples, u_long* datastart,ErrFunc erf)
168
{ static WAVEFORMAT waveformat ;
169
static char buffer [ BUFFERSIZE ] ; /* Function is not reentrant.*/
173
v_erf = erf; /* wwg: Set error reporting function */
175
if (lseek (wavefile, 0L, SEEK_SET)) {
176
err("%s",sys_errlist[errno]); /* wwg: Report error */
180
read (wavefile, buffer, BUFFERSIZE) ;
182
if (findchunk (buffer, "RIFF", BUFFERSIZE) != buffer) {
183
err("Bad format: Cannot find RIFF file marker"); /* wwg: Report error */
187
if (! findchunk (buffer, "WAVE", BUFFERSIZE)) {
188
err("Bad format: Cannot find WAVE file marker"); /* wwg: report error */
192
ptr = findchunk (buffer, "fmt ", BUFFERSIZE) ;
195
err("Bad format: Cannot find 'fmt' file marker"); /* wwg: report error */
196
return WR_BADFORMAT ;
199
ptr += 4 ; /* Move past "fmt ".*/
200
memcpy (&waveformat, ptr, sizeof (WAVEFORMAT)) ;
201
waveformat.dwSize = SwapLE32(waveformat.dwSize);
202
waveformat.wFormatTag = SwapLE16(waveformat.wFormatTag) ;
203
waveformat.wChannels = SwapLE16(waveformat.wChannels) ;
204
waveformat.dwSamplesPerSec = SwapLE32(waveformat.dwSamplesPerSec) ;
205
waveformat.dwAvgBytesPerSec = SwapLE32(waveformat.dwAvgBytesPerSec) ;
206
waveformat.wBlockAlign = SwapLE16(waveformat.wBlockAlign) ;
207
waveformat.wBitsPerSample = SwapLE16(waveformat.wBitsPerSample) ;
209
if (waveformat.dwSize < (sizeof (WAVEFORMAT) - sizeof (u_long))) {
210
err("Bad format: Bad fmt size"); /* wwg: report error */
211
return WR_BADFORMATSIZE ;
214
if (waveformat.wFormatTag != PCM_WAVE_FORMAT) {
215
err("Only supports PCM wave format"); /* wwg: report error */
216
return WR_NOTPCMFORMAT ;
219
ptr = findchunk (buffer, "data", BUFFERSIZE) ;
222
err("Bad format: unable to find 'data' file marker"); /* wwg: report error */
223
return WR_NODATACHUNK ;
226
ptr += 4 ; /* Move past "data".*/
227
memcpy (&databytes, ptr, sizeof (u_long)) ;
229
/* Everything is now cool, so fill in output data.*/
231
*channels = waveformat.wChannels ;
232
*samplerate = waveformat.dwSamplesPerSec ;
233
*samplebits = waveformat.wBitsPerSample ;
234
*samples = databytes / waveformat.wBlockAlign ;
236
*datastart = ((u_long) (ptr + 4)) - ((u_long) (&(buffer[0]))) ;
238
if (waveformat.dwSamplesPerSec != waveformat.dwAvgBytesPerSec / waveformat.wBlockAlign) {
239
err("Bad file format"); /* wwg: report error */
240
return WR_BADFORMATDATA ;
243
if (waveformat.dwSamplesPerSec != waveformat.dwAvgBytesPerSec / waveformat.wChannels / ((waveformat.wBitsPerSample == 16) ? 2 : 1)) {
244
err("Bad file format"); /* wwg: report error */
245
return WR_BADFORMATDATA ;
249
} ; /* WaveReadHeader*/
251
/*===========================================================================================*/
254
char* WaveFileError (int errno)
256
{ case WW_BADOUTPUTFILE : return "Bad output file.\n" ;
257
case WW_BADWRITEHEADER : return "Not able to write WAV header.\n" ;
259
case WR_BADALLOC : return "Not able to allocate memory.\n" ;
260
case WR_BADSEEK : return "fseek failed.\n" ;
261
case WR_BADRIFF : return "Not able to find 'RIFF' file marker.\n" ;
262
case WR_BADWAVE : return "Not able to find 'WAVE' file marker.\n" ;
263
case WR_BADFORMAT : return "Not able to find 'fmt ' file marker.\n" ;
264
case WR_BADFORMATSIZE : return "Format size incorrect.\n" ;
265
case WR_NOTPCMFORMAT : return "Not PCM format WAV file.\n" ;
266
case WR_NODATACHUNK : return "Not able to find 'data' file marker.\n" ;
267
case WR_BADFORMATDATA : return "Format data questionable.\n" ;
268
default : return "No error\n" ;
271
} ; /* WaveFileError*/
273
/*===========================================================================================*/
275
char* findchunk (char* pstart, char* fourcc, size_t n)
281
while (pstart < pend)
282
{ if (*pstart == *fourcc) /* found match for first char*/
284
for (k = 1 ; fourcc [k] != 0 ; k++)
285
test = (test ? ( pstart [k] == fourcc [k] ) : FALSE) ;
290
} ; /* while lpstart*/
295
/* $Source: /cvsroot/qgo/qgo/src/wavfile.c,v $ */
297
* Internal routine to allocate WAVFILE structure:
300
wavfile_alloc(const char *Pathname) {
301
WAVFILE *wfile = (WAVFILE *) malloc(sizeof (WAVFILE));
303
if ( wfile == NULL ) {
304
err("%s: Allocating WAVFILE structure",sys_errlist[ENOMEM]);
308
memset(wfile,0,sizeof *wfile);
310
if ( (wfile->Pathname = strdup(Pathname)) == NULL ) {
312
err("%s: Allocating storage for WAVFILE.Pathname",sys_errlist[ENOMEM]);
316
wfile->fd = -1; /* Initialize fd as not open */
317
wfile->wavinfo.Channels = Mono;
318
wfile->wavinfo.DataBits = 8;
324
* Internal routine to release WAVFILE structure:
325
* No errors reported.
328
wavfile_free(WAVFILE *wfile) {
329
if ( wfile->Pathname != NULL )
330
free(wfile->Pathname);
335
* Open a WAV file for reading: returns (WAVFILE *)
337
* The opened file is positioned at the first byte of WAV file data, or
338
* NULL is returned if the open is unsuccessful.
341
WavOpenForRead(const char *Pathname,ErrFunc erf) {
342
WAVFILE *wfile = wavfile_alloc(Pathname);
343
int e; /* Saved errno value */
344
UInt32 offset; /* File offset */
345
Byte ubuf[4]; /* 4 byte buffer */
346
UInt32 dbytes; /* Data byte count */
347
/* wavfile.c values : */
348
int channels; /* Channels recorded in this wav file */
349
u_long samplerate; /* Sampling rate */
350
int sample_bits; /* data bit size (8/12/16) */
351
u_long samples; /* The number of samples in this file */
352
u_long datastart; /* The offset to the wav data */
354
v_erf = erf; /* Set error reporting function */
357
return NULL; /* Insufficient memory (class B msg) */
360
* Open the file for reading:
362
if ( (wfile->fd = open(wfile->Pathname,O_RDONLY)) < 0 ) {
363
err("%s:\nOpening WAV file %s",
369
if ( lseek(wfile->fd,0L,SEEK_SET) != 0L ) {
370
err("%s:\nRewinding WAV file %s",
373
goto errxit; /* Wav file must be seekable device */
376
if ( (e = WaveReadHeader(wfile->fd,&channels,&samplerate,&sample_bits,&samples,&datastart,_v_erf)) != 0 ) {
377
err("%s:\nReading WAV header from %s",
384
* Copy WAV data over to WAVFILE struct:
387
wfile->wavinfo.Channels = Stereo;
388
else wfile->wavinfo.Channels = Mono;
390
wfile->wavinfo.SamplingRate = (UInt32) samplerate;
391
wfile->wavinfo.Samples = (UInt32) samples;
392
wfile->wavinfo.DataBits = (UInt16) sample_bits;
393
wfile->wavinfo.DataStart = (UInt32) datastart;
394
wfile->num_samples = wfile->wavinfo.Samples;
395
wfile->rw = 'R'; /* Read mode */
397
offset = wfile->wavinfo.DataStart - 4;
400
* Seek to byte count and read dbytes:
402
if ( lseek(wfile->fd,offset,SEEK_SET) != offset ) {
403
err("%s:\nSeeking to WAV data in %s",sys_errlist[errno],wfile->Pathname);
404
goto errxit; /* Seek failure */
407
if ( read(wfile->fd,ubuf,4) != 4 ) {
408
err("%s:\nReading dbytes from %s",sys_errlist[errno],wfile->Pathname);
413
* Put little endian value into 32 bit value:
416
dbytes = (dbytes << 8) | ubuf[2];
417
dbytes = (dbytes << 8) | ubuf[1];
418
dbytes = (dbytes << 8) | ubuf[0];
420
wfile->wavinfo.DataBytes = dbytes;
425
return wfile; /* Return open descriptor */
428
* Return error after failed open:
430
errxit: e = errno; /* Save errno */
431
free(wfile->Pathname); /* Dispose of copied pathname */
432
free(wfile); /* Dispose of WAVFILE struct */
433
errno = e; /* Restore error number */
434
return NULL; /* Return error indication */
438
* Apply command line option overrides to the interpretation of the input
443
WavReadOverrides(WAVFILE *wfile,WavPlayOpts *wavopts) {
447
* Override sampling rate: -s sampling_rate
449
if ( wavopts->SamplingRate.optChar != 0 ) {
450
wfile->wavinfo.SamplingRate = wavopts->SamplingRate.optValue;
451
wfile->wavinfo.bOvrSampling = 1;
455
* Override mono/stereo mode: -S / -M
457
if ( wavopts->Channels.optChar != 0 ) {
458
wfile->wavinfo.Channels = wavopts->Channels.optValue;
459
wfile->wavinfo.bOvrMode = 1;
463
* Override the sample size in bits: -b bits
465
if ( wavopts->DataBits.optChar != 0 ) {
466
wfile->wavinfo.DataBits = wavopts->DataBits.optValue;
467
wfile->wavinfo.bOvrBits = 1;
471
* Set the first sample:
473
wfile->StartSample = 0;
474
num_samples = wfile->wavinfo.Samples = wfile->num_samples;
475
if ( wavopts->StartSample != 0 ) {
476
wfile->StartSample = wavopts->StartSample;
477
wfile->wavinfo.Samples -= wfile->StartSample;
481
* Override # of samples if -t seconds option given:
483
if ( wavopts->Seconds != 0 ) {
484
wfile->wavinfo.Samples = wavopts->Seconds * wfile->wavinfo.SamplingRate;
485
if (wfile->StartSample+wfile->wavinfo.Samples > num_samples)
486
wfile->wavinfo.Samples = num_samples-1;
494
WavClose(WAVFILE *wfile,ErrFunc erf) {
495
int e = 0; /* Returned error code */
496
int channels; /* Channels recorded in this wav file */
497
u_long samplerate; /* Sampling rate */
498
int sample_bits; /* data bit size (8/12/16) */
499
u_long samples; /* The number of samples in this file */
500
u_long datastart; /* The offset to the wav data */
501
long fpos; /* File position in bytes */
503
v_erf = erf; /* Set error reporting function */
505
if ( wfile == NULL ) {
506
err("%s: WAVFILE pointer is NULL!",sys_errlist[EINVAL]);
512
* If the wav file was open for write, update the actual number
513
* of samples written to the file:
515
if ( wfile->rw == 'W' ) {
516
fpos = lseek(wfile->fd,0L,SEEK_CUR); /* Get out file position */
517
if ( (e = WaveReadHeader(wfile->fd,&channels,&samplerate,&sample_bits,&samples,&datastart,_v_erf)) != 0 )
518
err("%s:\nReading WAV header from %s",emsg,wfile->Pathname);
519
else if ( lseek(wfile->fd,(long)(datastart-4),SEEK_SET) != (long)(datastart-4) )
520
err("%s:\nSeeking in WAV header file %s",sys_errlist[errno],wfile->Pathname);
521
else if ( write(wfile->fd,&wfile->wavinfo.Samples,sizeof wfile->wavinfo.Samples) != sizeof wfile->wavinfo.Samples )
522
err("%s:\nWriting in WAV header file %s",sys_errlist[errno],wfile->Pathname);
525
* 'data' chunk was updated OK: Now we have to update the RIFF block
526
* count. Someday very soon, a real RIFF module is going to replace
529
if ( ftruncate(wfile->fd,(size_t)fpos) )
530
err("%s:\nTruncating file %s to correct size",
533
else if ( lseek(wfile->fd,4L,SEEK_SET) < 0L )
534
err("%s:\nSeek 4 for RIFF block update of %s",
538
fpos -= 8; /* Byte count for RIFF block */
539
if ( write(wfile->fd,&fpos,sizeof fpos) != sizeof fpos )
540
err("%s:\nUpdate of RIFF block count in %s failed",
547
if ( close(wfile->fd) < 0 ) {
548
err("%s:\nClosing WAV file",sys_errlist[errno]);
549
e = errno; /* Save errno value to return */
552
wavfile_free(wfile); /* Release WAVFILE structure */
554
if ( (errno = e) != 0 )
555
return -1; /* Failed exit */
556
return 0; /* Successful exit */
562
* Open /dev/dsp for reading or writing:
565
OpenDSP(WAVFILE *wfile,int omode,ErrFunc erf) {
566
int e; /* Saved errno value */
567
int t; /* Work int */
568
unsigned long ul; /* Work unsigned long */
569
DSPFILE *dfile = (DSPFILE *) malloc(sizeof (DSPFILE));
571
v_erf = erf; /* Set error reporting function */
573
if ( dfile == NULL ) {
574
err("%s: Opening DSP device",sys_errlist[errno=ENOMEM]);
578
memset(dfile,0,sizeof *dfile);
579
dfile->dspbuf = NULL;
582
* Open the device driver:
584
if ( (dfile->fd = open(AUDIODEV,omode,0)) < 0 ) {
585
err("%s:\nOpening audio device %s",
592
* Determine the audio device's block size. Should be done after
593
* setting sampling rate etc.
595
if ( ioctl(dfile->fd,SNDCTL_DSP_GETBLKSIZE,&dfile->dspblksiz) < 0 ) {
596
err("%s: Optaining DSP's block size",sys_errlist[errno]);
601
* Check the range on the buffer sizes:
603
/* Minimum was 4096 but es1370 returns 1024 for 44.1kHz, 16 bit */
604
/* and 64 for 8130Hz, 8 bit */
605
if ( dfile->dspblksiz < 32 || dfile->dspblksiz > 65536 ) {
606
err("%s: Audio block size (%d bytes)",
607
sys_errlist[errno=EINVAL],
608
(int)dfile->dspblksiz);
613
* Allocate a buffer to do the I/O through:
615
if ( (dfile->dspbuf = (char *) malloc(dfile->dspblksiz)) == NULL ) {
616
err("%s: For DSP I/O buffer",sys_errlist[errno]);
621
* Set the data bit size:
623
t = wfile->wavinfo.DataBits;
624
if ( ioctl(dfile->fd,SNDCTL_DSP_SAMPLESIZE,&t) < 0 ) {
625
err("%s: Setting DSP to %u bits",sys_errlist[errno],(unsigned)t);
630
* Set the mode to be Stereo or Mono:
632
t = wfile->wavinfo.Channels == Stereo ? 1 : 0;
633
if ( ioctl(dfile->fd,SNDCTL_DSP_STEREO,&t) < 0 ) {
634
err("%s: Unable to set DSP to %s mode",
641
* Set the sampling rate:
643
ul = wfile->wavinfo.SamplingRate;
644
if ( ioctl(dfile->fd,SNDCTL_DSP_SPEED,&ul) < 0 ) {
645
err("Unable to set audio sampling rate",sys_errlist[errno]);
650
* Return successfully opened device:
652
return dfile; /* Return file descriptor */
655
* Failed to open/initialize properly:
657
errxit: e = errno; /* Save the errno value */
659
fprintf(stdout, "error %s : \n",sys_errlist[errno]);
661
if ( dfile->fd >= 0 )
662
close(dfile->fd); /* Close device */
663
if ( dfile->dspbuf != NULL )
666
errno = e; /* Restore error code */
667
return NULL; /* Return error indication */
671
* Close the DSP device:
674
CloseDSP(DSPFILE *dfile,ErrFunc erf) {
677
v_erf = erf; /* Set error reporting function */
679
if ( dfile == NULL ) {
680
err("%s: DSPFILE is not open",sys_errlist[errno=EINVAL]);
685
if ( dfile->dspbuf != NULL )
690
err("%s: Closing DSP fd %d",sys_errlist[errno],fd);
698
* Play DSP from WAV file:
701
PlayDSP(DSPFILE *dfile,WAVFILE *wfile,DSPPROC work_proc,ErrFunc erf) {
702
UInt32 byte_count = (UInt32) wfile->wavinfo.Samples;
706
int total_bytes, update_bytes;
709
v_erf = erf; /* Set error reporting function */
712
* Check that the WAVFILE is open for reading:
714
if ( wfile->rw != 'R' ) {
715
err("%s: WAVFILE must be open for reading",sys_errlist[errno=EINVAL]);
720
* First determine how many bytes are required for each channel's sample:
722
switch ( wfile->wavinfo.DataBits ) {
730
err("%s: Cannot process %u bit samples",
731
sys_errlist[errno=EINVAL],
732
(unsigned)wfile->wavinfo.DataBits);
737
* Allow for Mono/Stereo difference:
739
if ( wfile->wavinfo.Channels == Stereo )
740
byte_count *= 2; /* Twice as many bytes for stereo */
741
else if ( wfile->wavinfo.Channels != Mono ) {
742
err("%s: DSPFILE control block is corrupted (chan_mode)",
743
sys_errlist[errno=EINVAL]);
747
byte_modulo = byte_count; /* This many bytes per sample */
748
byte_count = wfile->wavinfo.Samples * byte_modulo; /* Total bytes to process */
749
total_bytes = byte_count;
751
/* Number of bytes to write between client updates. Must be */
752
/* a multiple of dspblksiz. */
753
update_bytes = ((wfile->wavinfo.SamplingRate*byte_modulo) / (RECPLAY_UPDATES_PER_SEC*dfile->dspblksiz)) * dfile->dspblksiz;
755
if ( ioctl(dfile->fd,SNDCTL_DSP_SYNC,0) != 0 )
756
err("%s: ioctl(%d,SNDCTL_DSP_SYNC,0)",sys_errlist[errno]);
758
/* Seek to requested start sample */
759
lseek(wfile->fd,wfile->StartSample*byte_modulo,SEEK_CUR);
761
for ( ; byte_count > 0 && wfile->wavinfo.DataBytes > 0; byte_count -= (UInt32) n ) {
763
bytes = (int) ( byte_count > dfile->dspblksiz ? dfile->dspblksiz : byte_count );
765
if ( bytes > wfile->wavinfo.DataBytes ) /* Size bigger than data chunk? */
766
bytes = wfile->wavinfo.DataBytes; /* Data chunk only has this much left */
768
if ( (n = read(wfile->fd,dfile->dspbuf,bytes)) != bytes ) {
770
err("Unexpected EOF reading samples from WAV file",sys_errlist[errno=EIO]);
771
else err("Reading samples from WAV file",sys_errlist[errno]);
775
if ((clntIPC >= 0) && !((total_bytes-byte_count) % update_bytes)) {
776
msg.msg_type = ToClnt_PlayState;
777
msg.bytes = sizeof(msg.u.toclnt_playstate);
778
msg.u.toclnt_playstate.SamplesLeft = byte_count / byte_modulo;
779
msg.u.toclnt_playstate.CurrentSample =
780
wfile->num_samples - msg.u.toclnt_playstate.SamplesLeft;
781
MsgToClient(clntIPC,&msg,0);
782
} /* Tell client playback status */
784
if ( write(dfile->fd,dfile->dspbuf,n) != n ) {
785
err("Writing samples to audio device",sys_errlist[errno]);
789
wfile->wavinfo.DataBytes -= (UInt32) bytes; /* We have fewer bytes left to read */
792
* The work procedure function is called when operating
793
* in server mode to check for more server messages:
795
if ( work_proc != NULL && work_proc(dfile) ) /* Did work_proc() return TRUE? */
796
break; /* Yes, quit playing */
799
#if 0 /* I think this is doing a destructive flush: disabled */
800
if ( ioctl(dfile->fd,SNDCTL_DSP_SYNC,0) != 0 )
801
err("%s: ioctl(%d,SNDCTL_DSP_SYNC,0)",sys_errlist[errno]);
803
/* Update client time display at end of sucessful play
805
msg.msg_type = ToClnt_PlayState;
806
msg.bytes = sizeof(msg.u.toclnt_playstate);
807
msg.u.toclnt_playstate.SamplesLeft = byte_count / byte_modulo;
808
msg.u.toclnt_playstate.CurrentSample =
809
wfile->num_samples - msg.u.toclnt_playstate.SamplesLeft;
810
MsgToClient(clntIPC,&msg,0);
811
} /* Tell client playback status */
812
return 0; /* All samples played successfully */
814
errxit: return -1; /* Indicate error return */