1 /*-------------- Telecommunications & Signal Processing Lab ---------------
2 
3 Routine:
4   AFILE *AFrdWVhead (FILE *fp)
5 
6 Purpose:
7   Get file format information from a WAVE file
8 
9 Description:
10   This routine reads the header for a WAVE file.  The header information
11   is used to set the file data format information in the audio file pointer
12   structure.
13 
14   WAVE file:
15    Offset Length Type    Contents
16       0     4    char   "RIFF" file identifier
17       4     4    int    Chunk length
18       8     4    char   "WAVE" file identifier
19     ...   ...    ...    ...
20       F     4    char   "fmt " chunk identifier
21      +4     4    int    Chunk length
22      +8     2    int      Audio data type
23     +10     2    int      Number of interleaved channels
24     +12     4    int      Sample rate
25     +16     4    int      Average bytes/sec
26     +20     2    int      Block align
27     +22     2    int      Data word length (bits)
28     +24     2    int      Extra data count (for non-PCM or extensible format)
29     +26     2    int        Valid bits per sample
30     +28     4    int        Channel mask
31     +32    16    int        Sub-format type
32     ...    ...   ...
33       C     4    char   "fact" chunk identifier (for non-PCM)
34      +4     4    int    Chunk length
35      +8     4    int      Number of samples (per channel)
36     ...    ...   ...    ...
37       D     4    char   "data" chunk identifier
38      +4     4    int    Chunk length
39      +8    ...   ...      Audio data
40 
41 Parameters:
42   <-  AFILE *AFrdWVhead
43       Audio file pointer for the audio file
44    -> FILE *fp
45       File pointer for the file
46 
47 Author / revision:
48   P. Kabal  Copyright (C) 2004
49   $Revision: 1.90 $  $Date: 2004/03/29 01:49:41 $
50 
51 -------------------------------------------------------------------------*/
52 
53 #include <assert.h>
54 #include <setjmp.h>
55 #include <string.h>
56 
57 #include <libtsp.h>
58 #include <libtsp/AFdataio.h>
59 #include <libtsp/AFheader.h>
60 #include <libtsp/AFmsg.h>
61 #define AF_SPKR_MASKS
62 #include <libtsp/AFpar.h>
63 #define WV_CHANNEL_MAP
64 #define WAVEFORMATEX_SUBTYPE
65 #include <libtsp/WVpar.h>
66 #include <libtsp/nucleus.h>
67 
68 #define ICEILV(n,m)	(((n) + ((m) - 1)) / (m))	/* int n,m >= 0 */
69 #define NELEM(array)	((int) ((sizeof array) / (sizeof array[0])))
70 #define RNDUPV(n,m)	((m) * ICEILV (n, m))		/* Round up */
71 
72 #define SAME_CSTR(str,ref) 	(memcmp (str, ref, sizeof (str)) == 0)
73 
74 #define ALIGN		2	/* Chunks padded out to a multiple of ALIGN */
75 
76 #define WV_FMT_MINSIZE	16
77 #define WV_LHMIN	(8 + 4 + 8 + WV_FMT_MINSIZE + 8)
78 
79 /* setjmp / longjmp environment */
80 extern jmp_buf AFR_JMPENV;
81 
82 AF_READ_DEFAULT(AFr_default);	/* Define the AF_read defaults */
83 
84 static int
85 AF_decFMT (const struct WV_CKfmt *CKfmt, struct AF_read *AFr);
86 static int
87 AF_rdDISP_text (FILE *fp, int Size, struct AF_infoX *InfoX);
88 static int
89 AF_rdFMT (FILE *fp, struct WV_CKfmt *CKfmt);
90 static int
91 AF_rdFACT (FILE *fp, struct WV_CKfact *CKfact);
92 static int
93 AF_rdLIST_INFO (FILE *fp, int Size, struct AF_infoX *InfoX);
94 static int
95 AF_rdRIFF_WAVE (FILE *fp, struct WV_CKRIFF *CKRIFF);
96 static void
97 AF_UnsFormat (int FormatTag);
98 static void
99 AF_decChannelConfig (uint4_t ChannelMask, unsigned char *SpkrConfig);
100 
101 
102 AFILE *
AFrdWVhead(FILE * fp)103 AFrdWVhead (FILE *fp)
104 
105 {
106   AFILE *AFp;
107   int AtData, FoundAFsp;
108   long int offs, LRIFF, Fact_Nsamp, Dstart, EoD;
109   char Info[AF_MAXINFO];
110   struct WV_CKRIFF CKRIFF;
111   struct WV_CKpreamb CkHead;
112   struct AF_read AFr;
113 
114 /* Set the long jump environment; on error return a NULL */
115   if (setjmp (AFR_JMPENV))
116     return NULL;	/* Return from a header read error */
117 
118 /* Defaults and inital values */
119   AFr = AFr_default;
120   AFr.InfoX.Info = Info;
121   AFr.InfoX.Nmax = AF_MAXINFO;
122 
123   Fact_Nsamp = AF_NSAMP_UNDEF;
124 
125 /* Check the file magic for a RIFF/WAVE file */
126   if (AF_rdRIFF_WAVE (fp, &CKRIFF))
127     return NULL;
128   offs = 12L;	/* Positioned after RIFF/WAVE preamble */
129   LRIFF = CKRIFF.ckSize + 8;
130 
131   Dstart = 0L;
132   EoD = 0L;
133   AtData = 0;
134   FoundAFsp = 0;
135   while (offs < LRIFF-8) {	/* Leave 8 bytes for the chunk preamble */
136 
137     /* Read the chunk preamble */
138     offs += RHEAD_S (fp, CkHead.ckID);
139 
140     /* fmt chunk */
141     if (SAME_CSTR (CkHead.ckID, "fmt ")) {
142       offs += AF_rdFMT (fp, &CKRIFF.CKfmt);
143       if (AF_decFMT (&CKRIFF.CKfmt, &AFr))
144 	return NULL;
145     }
146 
147     /* fact chunk */
148     else if (SAME_CSTR (CkHead.ckID, "fact")) {
149       offs += AF_rdFACT (fp, &CKRIFF.CKfact);
150       Fact_Nsamp = CKRIFF.CKfact.dwSampleLength;
151     }
152 
153     /* data chunk */
154     else if (SAME_CSTR (CkHead.ckID, "data")) {
155       offs += RHEAD_V (fp, CKRIFF.CKdata.ckSize, DS_EL);
156       AFr.NData.Ldata = CKRIFF.CKdata.ckSize;
157       Dstart = offs;
158       EoD = RNDUPV (Dstart + AFr.NData.Ldata, ALIGN);
159       if (EoD >= LRIFF || ! FLseekable (fp)) {
160 	AtData = 1;
161 	break;
162       }
163       else {
164 	AtData = 0;
165 	offs += RSKIP (fp, EoD - Dstart);
166       }
167     }
168 
169     /* Text chunks */
170     else if (SAME_CSTR (CkHead.ckID, "afsp")) {
171       offs += RHEAD_V (fp, CkHead.ckSize, DS_EL);
172       offs += AFrdTextAFsp (fp, (int) CkHead.ckSize, "AFsp: ", &AFr.InfoX,
173 			    ALIGN);
174       FoundAFsp = 1;
175     }
176     else if (SAME_CSTR (CkHead.ckID, "DISP") && ! FoundAFsp) {
177       offs += RHEAD_V (fp, CkHead.ckSize, DS_EL);
178       offs += AF_rdDISP_text (fp, (int) CkHead.ckSize, &AFr.InfoX);
179     }
180     else if (SAME_CSTR (CkHead.ckID, "LIST") && ! FoundAFsp) {
181       offs += RHEAD_V (fp, CkHead.ckSize, DS_EL);
182       offs += AF_rdLIST_INFO (fp, (int) CkHead.ckSize, &AFr.InfoX);
183     }
184     /* Miscellaneous chunks */
185     else {
186       offs += RHEAD_V (fp, CkHead.ckSize, DS_EL);
187       offs += RSKIP (fp, RNDUPV (CkHead.ckSize, ALIGN));
188     }
189   }
190   /* Error Checks */
191   /* Check that we found a fmt and a data chunk */
192   if (AFr.DFormat.Format == FD_UNDEF || AFr.NData.Ldata == AF_LDATA_UNDEF) {
193     UTwarn ("AFrdWVhead - %s", AFM_WV_BadHead);
194     return NULL;
195   }
196   if ((! AtData && offs != LRIFF) || (AtData && EoD != LRIFF))
197     UTwarn ("AFrdWVhead - %s", AFM_WV_BadSize);
198 
199   /* Use the number of samples from the fact chunk only for non-PCM
200      or WAVE-EX files.  Some PCM files use the fact chunk, but it does
201      NOT specify the number of samples */
202   if (CKRIFF.CKfmt.wFormatTag != WAVE_FORMAT_PCM
203       && Fact_Nsamp != AF_NSAMP_UNDEF)
204     AFr.NData.Nsamp = AFr.NData.Nchan * Fact_Nsamp;
205 
206   /* Position at the start of data */
207   if (! AtData) {
208     if (AFseek (fp, Dstart, NULL))
209       return NULL;
210   }
211 
212 /* Set the parameters for file access */
213   AFp = AFsetRead (fp, FT_WAVE, &AFr, AF_FIX_NSAMP_HIGH + AF_FIX_LDATA_HIGH);
214 
215   return AFp;
216 }
217 
218 /* Check the RIFF/WAVE file preamble */
219 
220 
221 static int
AF_rdRIFF_WAVE(FILE * fp,struct WV_CKRIFF * CKRIFF)222 AF_rdRIFF_WAVE (FILE *fp, struct WV_CKRIFF *CKRIFF)
223 
224 {
225   long int Lfile, LRIFF;
226 
227   RHEAD_S (fp, CKRIFF->ckID);
228   if (! SAME_CSTR (CKRIFF->ckID, "RIFF")) {
229     UTwarn ("AFrdWVhead - %s", AFM_WV_BadId);
230     return 1;
231   }
232 
233   RHEAD_V (fp, CKRIFF->ckSize, DS_EL);
234   LRIFF = CKRIFF->ckSize + 8;
235   if (LRIFF < WV_LHMIN) {
236     UTwarn ("AFrdWVhead - %s", AFM_WV_BadRIFF);
237     return 1;
238   }
239 
240   if (FLseekable (fp)) {
241     Lfile = FLfileSize (fp);
242     if (LRIFF > Lfile) {
243       CKRIFF->ckSize = Lfile - 8;
244       UTwarn ("AFrdWVhead - %s", AFM_WV_FixRIFF);
245     }
246   }
247 
248   RHEAD_S (fp, CKRIFF->WAVEID);
249   if (! SAME_CSTR (CKRIFF->WAVEID, "WAVE")) {
250     UTwarn ("AFrdWVhead - %s", AFM_WV_BadId);
251     return 1;
252   }
253 
254   return 0;
255 }
256 
257 /* Read the fmt chunk, starting at ckSize */
258 
259 
260 static int
AF_rdFMT(FILE * fp,struct WV_CKfmt * CKfmt)261 AF_rdFMT (FILE *fp, struct WV_CKfmt *CKfmt)
262 
263 {
264   int offs, NB;
265 
266   offs = RHEAD_V (fp, CKfmt->ckSize, DS_EL);
267   if (CKfmt->ckSize < WV_FMT_MINSIZE) {
268     UTwarn ("AFrdWVhead - %s", AFM_WV_BadfmtSize);
269     longjmp (AFR_JMPENV, 1);
270   }
271 
272   offs += RHEAD_V (fp, CKfmt->wFormatTag, DS_EL);
273   offs += RHEAD_V (fp, CKfmt->nChannels, DS_EL);
274   offs += RHEAD_V (fp, CKfmt->nSamplesPerSec, DS_EL);
275   offs += RHEAD_V (fp, CKfmt->nAvgBytesPerSec, DS_EL);
276   offs += RHEAD_V (fp, CKfmt->nBlockAlign, DS_EL);
277   offs += RHEAD_V (fp, CKfmt->wBitsPerSample, DS_EL);
278 
279   NB = ((int) CKfmt->ckSize + 4) - offs;
280   if (NB >= 24) {
281     offs += RHEAD_V (fp, CKfmt->cbSize, DS_EL);
282     if (CKfmt->cbSize >= 22) {
283       offs += RHEAD_V (fp, CKfmt->wValidBitsPerSample, DS_EL);
284       offs += RHEAD_V (fp, CKfmt->dwChannelMask, DS_EL);
285       offs += RHEAD_V (fp, CKfmt->SubFormat.wFormatTag, DS_EL);
286       offs += RHEAD_S (fp, CKfmt->SubFormat.guidx);
287     }
288     else
289       CKfmt->cbSize = 0;
290   }
291 
292   /* Skip over any extra data at the end of the fmt chunk */
293   offs += RSKIP (fp, RNDUPV (CKfmt->ckSize + 4, ALIGN) - offs);
294 
295   return offs;
296 }
297 
298 /* Decode the data format */
299 
300 
301 static int
AF_decFMT(const struct WV_CKfmt * CKfmt,struct AF_read * AFr)302 AF_decFMT (const struct WV_CKfmt *CKfmt, struct AF_read *AFr)
303 
304 {
305   uint2_t FormatTag;
306   int NBytesS;
307 
308 /*
309   Ordinary WAVE (not EXTENSIBLE format) files
310   - The data format is over-specified
311     - The number of bits per sample is wBitsPerSample.
312     - The number of bytes per sample is NByteS = nBlockAlign/nChannels.
313   - A reasonable approach is to get the container size from the block size
314     and the data size from wBitsPerSample.
315   - The CoolEdit float format specifies float data (flagged as 24 bits per
316     sample, in a 4-byte container)
317   - In calculating the container size, it is expected that nBlockAlign be a
318     multiple of the number of channels.  Note, however, that some compressed
319     schemes set nBlockAlign to 1.
320   WAVE EXTENSIBLE files
321   - wBitsPerSample now explicitly is the container size in bits and hence
322     should agree with 8 * NByteS, and nBlockAlign must be a multiple of
323     nChannels.
324 */
325   if (CKfmt->nChannels == 0)
326     NBytesS = 0;
327   else
328     NBytesS = (int) (CKfmt->nBlockAlign / CKfmt->nChannels);
329 
330   if (CKfmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
331     if (CKfmt->cbSize < 22 ||
332 	! IS_VALID_WAVEFORMATEX_GUID (CKfmt->SubFormat)) {
333       UTwarn ("AFrdWVhead - %s", AFM_WV_BadfmtEx);
334       return 1;
335     }
336     /* Extensible file: container size must match wBitsPerSample */
337     AFr->DFormat.NbS = 8 * NBytesS;
338     if (CKfmt->wBitsPerSample != 8 * NBytesS) {
339       UTwarn ("AFrdWVhead - %s: %d <-> %d", AFM_WV_BadSS,
340 	      (int) CKfmt->wBitsPerSample, 8 * NBytesS);
341     }
342     AFr->DFormat.NbS = (int) CKfmt->wValidBitsPerSample;
343     FormatTag = CKfmt->SubFormat.wFormatTag;
344     AF_decChannelConfig (CKfmt->dwChannelMask, AFr->NData.SpkrConfig);
345   }
346   else {
347     AFr->DFormat.NbS = (int) CKfmt->wBitsPerSample;
348     FormatTag = CKfmt->wFormatTag;
349   }
350 
351   switch (FormatTag) {
352   case WAVE_FORMAT_PCM:
353 
354     if (NBytesS == FDL_INT16)
355       AFr->DFormat.Format = FD_INT16;
356     /* Special case: IEEE float from CoolEdit (samples are flagged as 24-bit
357        integer, but are 32-bit IEEE float) */
358     else if (CKfmt->wFormatTag == WAVE_FORMAT_PCM &&
359 	     AFr->DFormat.NbS == 24 && NBytesS == 4) {
360       AFr->DFormat.Format = FD_FLOAT32;
361       AFr->DFormat.NbS = 8 * FDL_FLOAT32;
362       AFr->DFormat.ScaleF = AF_SF_INT24;	/* Same scaling as INT24 */
363       if (! UTcheckIEEE ())
364 	UTwarn ("AFrdAIhead - %s", AFM_NoIEEE);
365     }
366     else if (NBytesS == FDL_INT24)
367       AFr->DFormat.Format = FD_INT24;
368     else if (NBytesS == FDL_INT32)
369       AFr->DFormat.Format = FD_INT32;
370     else if (NBytesS == FDL_UINT8)
371       AFr->DFormat.Format = FD_UINT8;
372     else {
373       UTwarn ("AFrdWVhead - %s: \"%d\"", AFM_WV_UnsDSize, 8 * NBytesS);
374       return 1;
375     }
376     break;
377 
378   case WAVE_FORMAT_MULAW:
379     if (NBytesS != 1) {
380       UTwarn ("AFrdWVhead - %s: \"%d\"", AFM_WV_BadMulaw, 8 * NBytesS);
381       return 1;
382     }
383     if (AFr->DFormat.NbS != 8) {
384       UTwarn ("AFrdWVhead - %s: \"%d\"", AFM_WV_BadMulaw, AFr->DFormat.NbS);
385       AFr->DFormat.NbS = 8;
386     }
387     AFr->DFormat.Format = FD_MULAW8;
388     break;
389 
390   case WAVE_FORMAT_ALAW:
391     if (NBytesS != 1) {
392       UTwarn ("AFrdWVhead - %s: \"%d\"", AFM_WV_BadAlaw, 8 * NBytesS);
393       return 1;
394     }
395     if (AFr->DFormat.NbS != 8) {
396       UTwarn ("AFrdWVhead - %s: \"%d\"", AFM_WV_BadAlaw, AFr->DFormat.NbS);
397       AFr->DFormat.NbS = 8;
398     }
399     AFr->DFormat.Format = FD_ALAW8;
400     break;
401 
402   case WAVE_FORMAT_IEEE_FLOAT:
403     if (NBytesS == FDL_FLOAT32)
404       AFr->DFormat.Format = FD_FLOAT32;
405     else if (NBytesS == FDL_FLOAT64)
406       AFr->DFormat.Format = FD_FLOAT64;
407     else
408       UTwarn ("AFrdWVhead - %s: \"%d\"", AFM_WV_BadFloat, 8 * NBytesS);
409     if (! UTcheckIEEE ())
410       UTwarn ("AFrdWVhead - %s", AFM_NoIEEE);
411     break;
412 
413   /* Unsupported data formats */
414   default:
415     AF_UnsFormat ((int) CKfmt->wFormatTag);
416     return 1;
417   }
418 
419   AFr->NData.Nchan = (long int) CKfmt->nChannels;
420 
421   /* Error checks */
422   if (AFr->NData.Nchan <= 0) {
423     UTwarn ("AFrdWVhead - %s : %d", AFM_WV_BadNChan, CKfmt->nChannels);
424     return 1;
425   }
426   if ((CKfmt->nBlockAlign % CKfmt->nChannels) != 0) {
427     UTwarn ("AFrdWVhead - %s: %d", AFM_WV_BadBlock, CKfmt->nBlockAlign);
428     return 1;
429   }
430   if (AFr->DFormat.NbS > 8 * NBytesS) {
431     UTwarn ("AFrdWVhead - %s: %d", AFM_WV_BadNbS, AFr->DFormat.NbS);
432     AFr->DFormat.NbS = 8 * NBytesS;
433   }
434 
435   /* Consistency check */
436   if (CKfmt->nAvgBytesPerSec != CKfmt->nChannels * CKfmt->nSamplesPerSec *
437       NBytesS)
438     UTwarn ("AFrdWVhead - %s", AFM_WV_BadBytesSec);
439 
440   AFr->DFormat.Swapb = DS_EL;
441   AFr->Sfreq = (double) CKfmt->nSamplesPerSec;
442 
443   return 0;
444 }
445 
446 /* Read the fact chunk */
447 
448 static int
AF_rdFACT(FILE * fp,struct WV_CKfact * CKfact)449 AF_rdFACT (FILE *fp, struct WV_CKfact *CKfact)
450 
451 {
452   int offs;
453 
454   offs = RHEAD_V (fp, CKfact->ckSize, DS_EL);
455   if (CKfact->ckSize < 4) {
456     UTwarn ("AFrdWVhead - %s", AFM_WV_BadFACT);
457     longjmp (AFR_JMPENV, 1);
458   }
459   offs += RHEAD_V (fp, CKfact->dwSampleLength, DS_EL);
460   offs += RSKIP (fp, RNDUPV (CKfact->ckSize + 4, ALIGN) - offs);
461 
462   return offs;
463 }
464 
465 /* Unsupported data format */
466 
467 static void
AF_UnsFormat(int FormatTag)468 AF_UnsFormat (int FormatTag)
469 
470 {
471   switch (FormatTag) {
472   case WAVE_FORMAT_ADPCM:
473     UTwarn ("AFrdWVhead - %s: \"%s\"", AFM_WV_UnsData, "MS ADPCM");
474     break;
475   case WAVE_FORMAT_MPEGLAYER3:
476     UTwarn ("AFrdWVhead - %s: \"%s\"", AFM_WV_UnsData, "MPEG-1 Layer 3");
477     break;
478   case WAVE_FORMAT_MSG723:
479     UTwarn ("AFrdWVhead - %s: \"%s\"", AFM_WV_UnsData, "G.723.1 coding");
480     break;
481   default:
482     UTwarn ("AFrdWVhead - %s: \"%d\"", AFM_WV_UnsData, FormatTag);
483     break;
484   }
485 }
486 
487 /* Read a DISP chunk */
488 
489 
490 static int
AF_rdDISP_text(FILE * fp,int Size,struct AF_infoX * InfoX)491 AF_rdDISP_text (FILE *fp, int Size, struct AF_infoX *InfoX)
492 
493 {
494   int offs;
495   uint4_t DISP_ID;
496 
497   offs = RHEAD_V (fp, DISP_ID, DS_EL);
498   if (DISP_ID == CF_TEXT)
499     offs += AFrdTextAFsp (fp, Size - offs, "display_name: ", InfoX, ALIGN);
500   else
501     offs += RSKIP (fp, RNDUPV (Size, ALIGN) - offs);
502 
503   return offs;
504 }
505 
506 /* Read the LIST-INFO records from the header */
507 
508 
509 struct WV_LI{
510   char ckid[5];
511   char *key;
512 };
513 static const struct WV_LI IID[] = {
514   {"IARL", "archival_location: "},
515   {"IART", "artist: "},
516   {"ICMS", "commissioned: "},
517   {"ICMT", "comments: "},
518   {"ICOP", "copyright: "},
519   {"ICRD", "creation_date: "},
520   {"IENG", "engineer: "},
521   {"IGNR", "genre: "},
522   {"IKEY", "keywords: "},
523   {"IMED", "medium: "},
524   {"INAM", "name: "},
525   {"IPRD", "product: "},
526   {"ISBJ", "subject: "},
527   {"ISFT", "software: "},
528   {"ISRC", "source: "},
529   {"ISRF", "source_form: "},
530   {"ITCH", "technician: "}
531 };
532 
533 #define NIID	NELEM (IID)
534 
535 
536 static int
AF_rdLIST_INFO(FILE * fp,int Size,struct AF_infoX * InfoX)537 AF_rdLIST_INFO (FILE *fp, int Size, struct AF_infoX *InfoX)
538 
539 {
540   int i, offs;
541   char ID[4], key[7];
542   struct WV_CKpreamb CkHead;
543 
544   offs = RHEAD_S (fp, ID);
545   if (SAME_CSTR (ID, "INFO")) {
546 
547     while (offs < Size) {
548       offs += RHEAD_S (fp, CkHead.ckID);
549       offs += RHEAD_V (fp, CkHead.ckSize, DS_EL);
550 
551       /* Look for standard INFO ID values */
552       for (i = 0; i < (int) NIID; ++i) {
553 	if (SAME_CSTR (CkHead.ckID, IID[i].ckid)) {
554 	  offs += AFrdTextAFsp (fp, (int) CkHead.ckSize, IID[i].key,
555 				InfoX, ALIGN);
556 	  break;
557 	}
558       }
559 
560       /* No match, use the INFO ID field as the information record keyword */
561       if (i == NIID) {
562 	strncpy (key, CkHead.ckID, 4);
563 	strcpy (&key[4], ": ");
564 	offs += AFrdTextAFsp (fp, (int) CkHead.ckSize, key, InfoX, ALIGN);
565       }
566     }
567   }
568   offs += RSKIP (fp, RNDUPV (Size, ALIGN) - offs);
569 
570   return offs;
571 }
572 
573 /* Decode channel/speaker information */
574 
575 
576 static void
AF_decChannelConfig(uint4_t ChannelMask,unsigned char * SpkrConfig)577 AF_decChannelConfig (uint4_t ChannelMask, unsigned char *SpkrConfig)
578 
579 {
580   int i, k;
581 
582   assert (AF_N_SPKR_NAMES - 1 == WV_N_CHANNEL_MAP);
583 
584   k = 0;
585   if (ChannelMask & WV_SPEAKER_ALL)
586     ;
587   else if (ChannelMask & ~WV_SPEAKER_KNOWN)
588     UTwarn ("AFrdWVhead - %s", AFM_WV_UnkChannel);
589   else if (ChannelMask != 0) {
590     for (i = 0, k = 0; i < WV_N_CHANNEL_MAP; ++i) {
591       if (ChannelMask & WV_ChannelMap[i]) {
592 	SpkrConfig[k] = i+1;
593 	++k;
594       }
595     }
596   }
597 
598   SpkrConfig[k] = '\0';
599   return;
600 }
601