Real-Time Sound Processing
by Randall Cook


Listing One
// WindowsRecorder.h -- A class for recording continuously on Windows.
// by Randall Cook. Copyright (C) 1998 Randall Cook. All Rights Reserved.

#ifndef WINDOWSRECORDER_H
#define WINDOWSRECORDER_H

#include <mmsystem.h>
#include "InputBuffer.h"

// interface for sample processing class
class SampleProcessor {
    public:
    virtual void ProcessSamples(unsigned char* buffer, long length) = 0;
};
// the sound recording class
class WindowsRecorder {
    friend void CALLBACK MyInterruptRoutine(HWAVEIN hwi, UINT uMsg, 
                DWORD dwInstance, DWORD dwParam1, DWORD dwParam2);
    private:
    HWND hostWindow;            // the window that gets the recycle messages
    HWAVEIN device;             // the wave input device
    bool recording;             // a flag indicating recording status
    bool useInterruptRoutine;   // a flag indicating recording mode
    InputBuffer** bufferList;   // an array of InputBuffer*
    int bufferCount;            // number of buffers in bufferList
    UINT recycleBufferMessage;  // the recycle buffer message
    SampleProcessor* processor; // an object that can process samples
    
    bool ErrorSuggestsBusyDevice(MMRESULT err);
    MMRESULT TryToOpenWaveInDevice(int devIndex, WAVEFORMATEX& wf,
                    DWORD callbackObject, DWORD callbackObjectType);
    void PrepareBuffers();
    void UnprepareBuffers();
    void ProcessBufferData(InputBuffer* ib);
    void RecycleBuffer(InputBuffer* ib);
    
    protected:
    void SetRecordingParameters(WAVEFORMATEX& wf);
    
    public:x
    WindowsRecorder(int bufCount,int bufSize,SampleProcessor* sp,HWND host);
    ~WindowsRecorder();
    MMRESULT Start(bool interrupt);
    void Stop();
    bool IsRecycleBufferMessage(UINT message);
    LRESULT ProcessRecycleBufferMessage(LPARAM lParam);
    bool IsRecording() { return recording; }
};
#endif

Listing Two 
// WindowsRecorder.cpp -- A class for recording continuously on Windows.
// by Randall Cook. Copyright (C) 1998 Randall Cook. All Rights Reserved.

#include "WindowsRecorder.h"

void CALLBACK MyInterruptRoutine(HWAVEIN hwi, UINT uMsg, 
    DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
// Process the samples, but don't recycle the buffer.
{
    if (uMsg == WIM_DATA) {
        WindowsRecorder* winRec = (WindowsRecorder*)dwInstance;
        // Extract the InputBuffer pointer from the WAVEHDR structure.
        LPWAVEHDR hdr = (LPWAVEHDR)dwParam1;
        InputBuffer* ib = (InputBuffer*)hdr->dwUser;
        if (ib)
            winRec->ProcessBufferData(ib);
        // Post a recycle buffer message to the main window. Put the WAVEHDR 
        // pointer in lParam so the message can be handled like MM_WIM_DATA.
        PostMessage(winRec->hostWindow, winRec->recycleBufferMessage, 
                                                              0, dwParam1);
    }
}
WindowsRecorder::WindowsRecorder(int bufCount, int bufSize,
                                       SampleProcessor* sp, HWND host)
{
    hostWindow = host;
    device = 0;
    useInterruptRoutine = false;
    processor = sp;
    recording = false;
    
    bufferList = new InputBuffer*[bufCount];
    bufferCount = bufCount;
    
    for (int i = 0; i < bufferCount; i++)
        bufferList[i] = new InputBuffer(bufSize);
        
    recycleBufferMessage = RegisterWindowMessage("WindowsRecorderRecycle");
}
WindowsRecorder::~WindowsRecorder()
{
    if (IsRecording())
        Stop();
    for (int i = 0; i < bufferCount; i++)
        delete bufferList[i];
    delete[] bufferList;
}
void WindowsRecorder::SetRecordingParameters(WAVEFORMATEX& wf)
{
    // In production code, use waveInGetDevCaps to query the device to ensure 
    // it can do what we want it to. Here, only set the sample rate, sample 
    // size, and number of channels to 44100 Hz, 16 bit, mono.
    
    wf.wFormatTag = WAVE_FORMAT_PCM;    // Standard sample format.
    wf.nSamplesPerSec = 44100L;         // 44100 Hz.
    wf.wBitsPerSample = 16;             // 16 bits.
    wf.nChannels = 1;                   // 1 channel (mono).
    wf.nBlockAlign = 2;                 // 2 bytes per sample.
    wf.nAvgBytesPerSec = wf.nSamplesPerSec
                         * wf.nBlockAlign
                         * wf.nChannels;
    wf.cbSize = 0;
}
bool WindowsRecorder::ErrorSuggestsBusyDevice(MMRESULT err)
// these errors (3, 4, 8, and 32) often come when the device is busy
{
    return err == MMSYSERR_NOTENABLED || err == MMSYSERR_ALLOCATED ||
           err == MMSYSERR_NOTSUPPORTED || err == WAVERR_BADFORMAT;
}
MMRESULT WindowsRecorder::TryToOpenWaveInDevice(int devIndex, 
            WAVEFORMATEX wf, DWORD callbackObject, DWORD callbackObjectType)
// Prepare to try to open the device several times if necessary. 
// Sets device, and returns the error code from the open call.
{
    MMRESULT err = 0;
    // Prepare to try to open device several times if necessary. If it works
    // the first time, we'll break out. We try for 3.5 seconds since that is 
    // a reasonable amount of time to try to open device. 0.25 seconds is a 
    // reasonable time to wait between tries, since user will have to wait at 
    // most this amount of time for things to start. The 14 below is 3.5/0.25.
    for (int count = 0; count < 14; count++) {
        err = waveInOpen(&device, devIndex, &wf, callbackObject, 
                                       (DWORD)this, callbackObjectType);
        if (ErrorSuggestsBusyDevice(err))
            Sleep(250);
        else
            break;
    }
    return err;
}
MMRESULT WindowsRecorder::Start(bool interrupt)
{
    MMRESULT err;
    // Describe the desired recording format.
    WAVEFORMATEX wf;
    SetRecordingParameters(wf);
    // Open the default device.
    useInterruptRoutine = interrupt;    // Remember which mode we are using.
    if (useInterruptRoutine) {
        err = TryToOpenWaveInDevice(WAVE_MAPPER, wf,
              (DWORD)MyInterruptRoutine, CALLBACK_FUNCTION);
    } else {
        err = TryToOpenWaveInDevice(WAVE_MAPPER, wf,
              (DWORD)hostWindow, CALLBACK_WINDOW);
    }
    if (err == 0) {
        PrepareBuffers();               // Prepare supply of input buffers.
        err = waveInStart(device);      // Actually begin recording.
        if (err == 0) {                 // No errors: we are recording.
            recording = true;
        } else {                        // Clean up on errors.
            UnprepareBuffers();
            waveInClose(device);
            device = 0;
        }
    }
    return err;
}
void WindowsRecorder::Stop()
{
    // Since the goal of this function is to stop recording and
    // close the device, it is reasonable to ignore errors.
    if (IsRecording()) {
        waveInReset(device);
        waveInStop(device);
        UnprepareBuffers();
        waveInClose(device);
        device = 0;
        recording = false;
    }
}
void WindowsRecorder::PrepareBuffers()
{
    for (int i = 0; i < bufferCount; i++)
        bufferList[i]->Prepare(device);
}
void WindowsRecorder::UnprepareBuffers()
{
    for (int i = 0; i < bufferCount; i++)
        bufferList[i]->Unprepare();
}
void WindowsRecorder::ProcessBufferData(InputBuffer* ib)
{
    if (ib->GetDataLen() && processor) {
        // Process the samples here. There are ib->GetDataLen() bytes
        // of data at ib->GetData().
        processor->ProcessSamples(ib->GetData(), ib->GetDataLen());
    }
}
void WindowsRecorder::RecycleBuffer(InputBuffer* ib)
{
    // Only recycle buffers that have been filled and are not being 
    // flushed out.
    if (ib->ContainsValidData() && device != 0) {
        // If using a window as the notification object, you must process the
        // samples now. If using a function as notification object, samples 
        // have already been processed in interrupt routine and don't need to 
        // be processed again here.
        if (!useInterruptRoutine)
            ProcessBufferData(ib);
        // Ihis does the actual recycling.
        ib->Prepare(device);
    }
}
bool WindowsRecorder::IsRecycleBufferMessage(UINT message)
{
    return message == MM_WIM_DATA || message == recycleBufferMessage;
}
LRESULT WindowsRecorder::ProcessRecycleBufferMessage(LPARAM lParam)
{
    // Extract the InputBuffer pointer from the WAVEHDR structure.
    LPWAVEHDR hdr = (LPWAVEHDR)lParam;
    InputBuffer* ib = (InputBuffer*)hdr->dwUser;
    if (ib)
        RecycleBuffer(ib);
    return 0;
}
/*
// modify your main window procedure thus:
WindowsRecorder* gRecorder;     // This doesn't have to be a global...
LRESULT CALLBACK MainWindowProc(HWND w, UINT message, WPARAM wParam,
                                                             LPARAM lParam)
{
    // ...
    if (gRecorder->IsRecycleBufferMessage(message)) {
        return gRecorder->ProcessRecycleBufferMessage(lParam);
    }
    // ...
}
*/


5


