This article was contributed by Pierre Fournier .
Wave files (.WAV) are very easy to handle with Microsoft compilers. Unfortunately, I
had to work with Creative Labs’ .VOC audio files which are not directly supported.
Although some documentation say that you simply need to "throw the sound file to
the wave mapper which will take care of everything!", it was not that easy. When I
was doing so, I was hearing a constant ‘pop’ in the playback. To solve this problem, I had
to extract each data block from the file and get rid of the unwanted data.
VOC files work in blocks. Each of the nine different blocks has its own format. A basic
VOC file only uses blocks of type 1, 8 and 9. This is what I had to work with, and this is
what my code uses.
The code uses the waveOutxxx() functions. These functions work wonderfully, as long as
you provide the correct information to them. The class used to play the VOC file is
declared as follow:
class CVocPlayer { public: CVocPlayer(); virtual ~CVocPlayer(); void lay( const CString &rcFileName, CWnd *pCallbackWnd ); inline void Reset() const { waveOutReset( hWaveOut ); } void Clear(); protected: char *pData; bool boPlaying; HWAVEOUT hWaveOut; WAVEHDR sWaveHdr; protected: void Decode( const CString &rcFileName, FILEINFO *psFileInfo ); };
Obviously, the function used to play the file is Play(). Notice that a pointer to a
window must be specified as a parameter. This window is the one that will be notified when
the audio file is done playing, and this step is required to cleanup by calling the
Clear() function. There are many ways to let us know when the file is done playing, and
this is the one I prefer. Changing the method shouldn’t be too hard.
The Decode() function uses a FILEINFO structure. This structure is used to gather
information about the audio file required by the device. The structure is defined as
follow:
typedef struct { UCHAR ucBitsPerSample; UCHAR ucChannels; USHORT usFileFormat; USHORT usTimeConstant; long lSamplesPerSeconds; long lTotalLength; } FILEINFO;
Now, the source code of the Play() function
void CVocPlayer::Play( const CString &rcFileName, CWnd *pCallbackWnd ) { Clear(); // Decode the file FILEINFO sFileInfo; Decode( rcFileName, &sFileInfo ); // Prepare a WAVEFORMATEX required for opening the device driver WAVEFORMATEX sWaveFormat; sWaveFormat.wFormatTag = WAVE_FORMAT_PCM; sWaveFormat.nChannels = sFileInfo.ucChannels; sWaveFormat.nSamplesPerSec = sFileInfo.lSamplesPerSeconds; sWaveFormat.nAvgBytesPerSec = sFileInfo.lSamplesPerSeconds; sWaveFormat.nBlockAlign = 1; sWaveFormat.wBitsPerSample = sFileInfo.ucBitsPerSample; sWaveFormat.cbSize = sizeof(WAVEFORMATEX); // Try to open the device driver MMRESULT Result = waveOutOpen( &hWaveOut, WAVE_MAPPER, &sWaveFormat, (ULONG)pCallbackWnd->m_hWnd, 0, CALLBACK_WINDOW ); if ( Result != MMSYSERR_NOERROR ) { hWaveOut = 0; return; } // Prepare the header sWaveHdr.lpData = pData; sWaveHdr.dwBufferLength = sFileInfo.lTotalLength; sWaveHdr.dwFlags = 0; sWaveHdr.dwLoops = 0; waveOutPrepareHeader( hWaveOut, &sWaveHdr, sizeof(sWaveHdr) ); // Play the file boPlaying = true; waveOutWrite( hWaveOut, &sWaveHdr, sizeof(sWaveHdr) ); }
After decoding the file and storing its information in the FILEINFO structure, we fill
the WAVEFORMATEX structure with the appropriate information and give it to the
waveOutOpen() function. If the device can handle the wanted format, we prepare the header
of the audio output and send our data to the waveOutWrite() function.
To decode the VOC file, we go through each of its blocks and store those we want in a
memory buffer (our pData member). At the same time, we fill the FILEINFO structure with
the information required by the WAVEFORMATEX structure.
void CVocPlayer::Decode( const CString &rcFileName, FILEINFO *psFileInfo ) { // Open the file and allocate the memory FILE *pFile = fopen( rcFileName, "rb" ); long lFileLength = _filelength( _fileno(pFile) ); pData = new char[ lFileLength ]; char *pDataPos = pData; // Place the file pointer at the beginning of the data fseek( pFile, 0x1A, SEEK_SET ); BYTE bType; signed long int lLen; do { // Read the block type fread( &bType, sizeof(bType), 1, pFile ); lLen = 0; switch( bType ) { case 1: { fread( &lLen, 3, 1, pFile ); lLen -= 2; // Remove Time Constant and File Format bytes fread( &psFileInfo->usTimeConstant, 1, 1, pFile ); fread( &psFileInfo->usFileFormat, 1, 1, pFile ); // For the moment, it's a plain 8-bit mono file psFileInfo->ucBitsPerSample = 8; psFileInfo->ucChannels = 1; psFileInfo->lSamplesPerSeconds = 1000000 / (256-(psFileInfo->usTimeConstant % 256)); // Store this sample in memory fread( pDataPos, lLen, 1, pFile ); pDataPos += lLen; break; } case 8: { fseek( pFile, 3, SEEK_CUR ); // Skip the length fread( &psFileInfo->usTimeConstant, 2, 1, pFile ); fread( &psFileInfo->usFileFormat, 1, 1, pFile ); fread( &psFileInfo->ucChannels, 1, 1, pFile ); // Block of type 8 is always followed by a block of type 1 fread( &bType, sizeof(bType), 1, pFile ); fread( &lLen, 3, 1, pFile ); lLen -= 2; // Remove Time Constant and File Format bytes fseek( pFile, 2, SEEK_CUR ); // Skip T.C. and F.F. psFileInfo->ucBitsPerSample = 8; psFileInfo->ucChannels++; psFileInfo->usTimeConstant >>= 8; psFileInfo->lSamplesPerSeconds = 1000000 / (256-(psFileInfo->usTimeConstant % 256)); // Store this sample in memory fread( pDataPos, lLen, 1, pFile ); pDataPos += lLen; break; } case 9: { fread( &lLen, 3, 1, pFile ); lLen -= 12; fread( &psFileInfo->lSamplesPerSeconds, 4, 1, pFile ); fread( &psFileInfo->ucBitsPerSample, 1, 1, pFile ); fread( &psFileInfo->ucChannels, 1, 1, pFile ); fread( &psFileInfo->usFileFormat, 2, 1, pFile ); // Store this sample in memory fread( pDataPos, lLen, 1, pFile ); pDataPos += lLen; break; } }; } while ( bType != 0 ); psFileInfo->lTotalLength = pDataPos-pData; fclose( pFile ); }
After the file is played, we need to clean our stuff. The Clear() function takes care
of that
void CVocPlayer::Clear() { if ( !boPlaying ) return; waveOutUnprepareHeader( hWaveOut, &sWaveHdr, sizeof(sWaveHdr) ); delete [] pData; pData = NULL; waveOutClose( hWaveOut ); boPlaying = false; }
…and that’s it! All you need to do to play the file is call the Play() function!