Making Recorders

Written by CyanPhase (Edward Blake)

Making a recorder machine is generally simple, and is similar in format to Buzz Machines. Your best bet is just to use the source code for the Raw Recorder and fill the blanks as needed. A copy of the Raw Recorder is also included at the bottom of this article. We'll describe all of the functions that are part of the Recorder class.

The first part of the source code of the recorder is usually some declarations, such as the main class and some constants. The second class declaration is the class that you actually fill up with code. Here's a description of each function of the COOERecorder class.

COOERecorder class
Function Description
Init This function is called when the recorder is getting loaded. you can set variables and other stuff here.
IsMultiTrack This function is called during initialization so the host can know if the recorder is a multitrack recorder or not.
IsTagged This function is called during initialization so the host can know if the recorder can accept meta tag data of the audio.
IsStreamed This function is called during initialization so the host can know if the recorder sends out it's data as a stream (ie. the internet, a device, etc.), in most cases this is only a informational thing and doesn't affect how the recorder is used by the host.
IsRequiresWaveout This function is called during initialization so the host can know if the recorder requires the sound device for some purpose (ie. a recorder where the output is first sent out to get processed by a outside unit, and then re-input back into the computer to be recorded on the disk). At the moment this is not functional in Overloader, this function was added for future planning.
IsLossyCompression This function is called during initialization so the host can know if the recorder writes in a lossy compression format. In most cases this is only a informational thing and doesn't affect how the recorder is used by the host.
IsSampleRateChangeable This function is called during initialization so the host can know if the recorder allows for samplerate changes during a record. At the moment this is not functional in Overloader, this function was added for future planning.
SampleRateChanged This function is called when the host wants to change sample rates.
IsBitRateChangeable This function is called during initialization so the host can know if the recorder allows for bitrate changes during a record. At the moment this is not functional in Overloader, this function was added for future planning.
BitRateChanged This function is called when the host wants to change bitrates.
SupportsSampleRate This function is called when the host wants to know if the recorder allows for the specified samplerate.
SupportsBitRate This function is called when the host wants to know if the recorder allows for the specified bitrate.
SaveAs This function is called when the user\host wants to bring up the save as window to specify a filename.
ReadyToRec This function is called so the host can know if the recorder is ready to record.
TrackNames This function is called before recording to tell the recorder the list of track names.
SongTagData This function is called before recording to tell the recorder the meta tag data associated with the audio.
Start This function is called before recording to initialize recording, the filename and samplerate is given.
WorkOutput This function is called to provide the recorder audio data to write to disk\stream. This function is called numerous times a second so it should be highly optimized.
WorkOutputMulti This function is similar to the WorkOutput function except it is for one of the tracks (only applies for Multitrack recorders).
Finish This function is called when the host is done with the recorder.
ConfigDlg This function is called when the user\host wants to bring up the configuration\format options dialog.
DispatchCommand This function is called for various functions, it is extendable.
DispatchCommandEx This function is called for various functions, it is extendable. Currently the only command is "about", with the str_value being a pointer to the parent's HWND.
LoadSettings At the moment this is not functional in Overloader, this function was added for future planning.
SaveSettings At the moment this is not functional in Overloader, this function was added for future planning.
OutputFilename This function is called to get the filename of the file being recorded. The output is used informationally by the host.
OutputSize This function is called to get the current size of the file being recorded. The output is used informationally by the host.
ExtraInfo This function is called by the host to get information on the Recorder used.
RecordersWebSiteURL This function is called by the host to get the web site associated with the Recorder.
RecorderVersion This function is used to get the Recorder's API version.
GetRecorderExtensionsClass This function is used to retrieve other extension classes, similar in a way to how COM works. At the moment this is not functional in Overloader, this function was added for future planning.


Raw Recorder source code

#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <windows.h>
#include "resource.h"

double const PI = 3.14159265358979323846;

HINSTANCE dllInstance;

#define MAX_BUFFER_LENGTH        256            // in number of samples

class COOERecorder {
public:
    virtual bool __cdecl Init();

    virtual bool __cdecl IsMultiTrack();
    virtual bool __cdecl IsTagged();
    virtual bool __cdecl IsStreamed();
    virtual bool __cdecl IsRequiresWaveout();
    virtual bool __cdecl IsLossyCompression();

    virtual bool __cdecl IsSampleRateChangeable();
    virtual bool __cdecl SampleRateChanged(int samplerate);
    virtual bool __cdecl IsBitRateChangeable();
    virtual bool __cdecl BitRateChanged(int bitrate);

    virtual bool __cdecl SupportsSampleRate(int samplerate);
    virtual bool __cdecl SupportsBitRate(int bitrate);

    virtual bool __cdecl SaveAs(HWND parentwindow);
    virtual bool __cdecl ReadyToRec();

    virtual bool __cdecl TrackNames(int track_id, char *track_name);
    virtual bool __cdecl SongTagData(int tag_index, char *tagdata);

    virtual bool __cdecl Start(char * filename, int samplespersec);
    virtual bool __cdecl WorkOutput(float *psamples, int numsamples);
    virtual bool __cdecl WorkOutputMulti(int track_id, float *psamples, int numsamples);

    virtual bool __cdecl Finish();
    virtual void __cdecl ConfigDlg(HWND parentwindow);
    virtual void __cdecl DispatchCommand(int command_id, int param1, int param2, int param3, int param4);
    virtual void __cdecl DispatchCommandEx(char * str_command, char * str_value);

    virtual void __cdecl LoadSettings(char * settingsname, char *username, char *domain);
    virtual void __cdecl SaveSettings(char * settingsname, char *username, char *domain);

    virtual char * __cdecl OutputFilename();
    virtual char * __cdecl OutputSize();
    virtual char * __cdecl ExtraInfo(int extra_info_id);
    virtual char * __cdecl RecordersWebSiteURL();

    virtual int __cdecl RecorderVersion();

    virtual void GetRecorderExtensionsClass(int param, void **exmodule);
};

class rec : public COOERecorder {
public:
    virtual bool __cdecl Init();

    virtual bool __cdecl IsMultiTrack() { return false; };
    virtual bool __cdecl IsTagged() { return false; };
    virtual bool __cdecl IsStreamed() { return false; };
    virtual bool __cdecl IsRequiresWaveout() { return false; };
    virtual bool __cdecl IsLossyCompression() { return false; };

    virtual bool __cdecl IsSampleRateChangeable() { return false; };
    virtual bool __cdecl SampleRateChanged(int samplerate) { return false; };
    virtual bool __cdecl IsBitRateChangeable() { return false; };
    virtual bool __cdecl BitRateChanged(int bitrate) { return false; };

    virtual bool __cdecl SupportsSampleRate(int samplerate) { return true; };
    virtual bool __cdecl SupportsBitRate(int bitrate) { return true; };

    virtual bool __cdecl SaveAs(HWND parentwindow);
    virtual bool __cdecl ReadyToRec();

    virtual bool __cdecl TrackNames(int track_id, char *track_name) { return false; };
    virtual bool __cdecl SongTagData(int tag_index, char *tagdata) { return false; };

    virtual bool __cdecl Start(char * filename, int samplespersec);
    virtual bool __cdecl WorkOutput(float *psamples, int numsamples);
    virtual bool __cdecl WorkOutputMulti(int track_id, float *psamples, int numsamples) { return false; };

    virtual bool __cdecl Finish();
    virtual void __cdecl ConfigDlg(HWND parentwindow);
    virtual void __cdecl DispatchCommand(int command_id, int param1, int param2, int param3, int param4);
    virtual void __cdecl DispatchCommandEx(char * str_command, char * str_value);

    virtual void __cdecl LoadSettings(char * settingsname, char *username, char *domain) { };
    virtual void __cdecl SaveSettings(char * settingsname, char *username, char *domain) { };

    virtual char * __cdecl OutputFilename();
    virtual char * __cdecl OutputSize();
    virtual char * __cdecl ExtraInfo(int extra_info_id) { return "No Extra Info"; };
    virtual char * __cdecl RecordersWebSiteURL() { return "http://www.buzzscene.ca"; };

    virtual int __cdecl RecorderVersion() { return 100; };

    virtual void GetRecorderExtensionsClass(int param, void **exmodule) { };
public:
    char myfilename[255];
    FILE * myfilehandle;
    int writtensofar;
    int bitdepth;
};

bool rec::Init(){
    sprintf(myfilename, "");
    myfilehandle = NULL;
    writtensofar = 0;
    bitdepth = 0;
    return true;
}
bool rec::SaveAs(HWND parentwindow){
    OPENFILENAME ofl;
    char filename[255];
    int nSuccess;

    sprintf(filename,"untitled.raw");
    ofl.lStructSize = sizeof(ofl);
    ofl.hwndOwner = parentwindow;
    ofl.hInstance = dllInstance;
    ofl.lpstrFilter = "Raw 32-bit Float (*.raw)\0*.raw\0";
    ofl.lpstrCustomFilter = NULL;
    ofl.nMaxCustFilter = NULL;
    ofl.nFilterIndex = 1;
    ofl.lpstrFile = filename;
    ofl.nMaxFile = 255;
    ofl.lpstrFileTitle = NULL;
    ofl.nMaxFileTitle = NULL;
    ofl.lpstrInitialDir = NULL;
    ofl.lpstrTitle = "Save Song Output as";
    ofl.Flags = OFN_OVERWRITEPROMPT|OFN_PATHMUSTEXIST;
    ofl.nFileOffset = 0;
    ofl.nFileExtension = 0;
    ofl.lpstrDefExt = ".raw";
    ofl.lCustData = NULL;
    ofl.lpfnHook = NULL;
    ofl.lpTemplateName = NULL;
    nSuccess = GetSaveFileName(&ofl);
    if (nSuccess == 0) {
        sprintf(myfilename, "");
//        SetWindowText(GetDlgItem(hDlg, IDC_FILENAMEBTN), "Save As...");
    } else {
        sprintf(myfilename, ofl.lpstrFile);
//        SetWindowText(GetDlgItem(hDlg, IDC_FILENAMEBTN), ext_hdfilename);
    }
    return true;
}
char *rec::OutputFilename() {
    return myfilename;
}
bool rec::Start (char * filename, int samplespersec) {
    //MessageBox(NULL, "start", "blah", MB_OK);
    myfilehandle = fopen(filename, "wb");
    writtensofar = 0;
    return true;
}
bool rec::ReadyToRec() {
    if (strcmp(myfilename, "") == 0) {
        return false;
    } else {
        return true;
    }

}

bool rec::WorkOutput (float *psamples, int numsamples) {
    if (bitdepth == 1) {
        short psampsm[1024];
        for (int i = 0; i < (numsamples * 2); i++) {
            if (psamples[i] > 32767.0f) psamples[i] = 32767.0f;
            if (psamples[i] < -32766.0f) psamples[i] = -32766.0f;
            psampsm[i] = (short)psamples[i];
        }
        writtensofar += numsamples;
        fwrite(psampsm, sizeof(short), numsamples * 2,myfilehandle);
    } else {
        for (int i = 0; i < (numsamples * 2); i++) {
            psamples[i] *= 0.00003f;
        }
        writtensofar += numsamples;
        fwrite(psamples, sizeof(float), numsamples * 2,myfilehandle);
    }
    return true;
}

bool rec::Finish() {
    fflush(myfilehandle);
    fclose(myfilehandle);
    return true;
}

char * rec::OutputSize() {
    static char megs[32];
    if (bitdepth == 1) {
        sprintf(megs, "%.2fM", ((float)writtensofar * 4.0f / 1024.0f / 1024.0f));
    } else {
        sprintf(megs, "%.2fM", ((float)writtensofar * 2.0f * (float)sizeof(float) / 1024.0f / 1024.0f));
    }
    return megs;
}

rec *prec;

BOOL APIENTRY ConfigDialog (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch(uMsg) {
    case WM_INITDIALOG:
    {
        if (prec->bitdepth == 1) {
            CheckDlgButton(hDlg,IDC_RADIO16,1);
        } else {
            CheckDlgButton(hDlg,IDC_RADIO32,1);
        }
        return 1;
    }
    case WM_SHOWWINDOW:
    {
        return 1;
    }
    case WM_CLOSE:
    {
        EndDialog (hDlg, TRUE);
    }
    case WM_COMMAND:
        switch ( LOWORD (wParam))
        {
        case IDOK:

            if (IsDlgButtonChecked(hDlg, IDC_RADIO32) == 1) { prec->bitdepth = 0; }
            if (IsDlgButtonChecked(hDlg, IDC_RADIO16) == 1) { prec->bitdepth = 1; }

            EndDialog (hDlg, TRUE);
            break;
        case IDCANCEL:
            EndDialog (hDlg, TRUE);
            break;
        default:
            return 0;
        }
        break;
    }
    return 0;
}

void rec::ConfigDlg (HWND parentwindow) {
    prec = this;
    DialogBox(dllInstance, MAKEINTRESOURCE (IDD_CONFIG), parentwindow, (DLGPROC) &ConfigDialog);
}

void rec::DispatchCommand(int command_id, int param1, int param2, int param3, int param4) {

}

void rec::DispatchCommandEx(char * str_command, char * str_value) {
    if (strcmp(str_command, "about") == 0) {
        // str_value is actually a pointer
        // to the parent HWND in this case
        HWND parentwindow = *(HWND*)str_value;
        MessageBox(parentwindow, "Raw Recorder version 1.0", "About Raw Recorder", MB_OK | MB_ICONINFORMATION);
    }
}

extern "C" {
__declspec(dllexport) COOERecorder * __cdecl CreateRecorder() { return new rec; }
__declspec(dllexport) char * __cdecl RecInfo() { return "Raw Recorder by CyanPhase"; }
}

BOOL WINAPI DllMain ( HANDLE hModule, DWORD fwdreason, LPVOID lpReserved )
{
    switch (fwdreason) {
    case DLL_PROCESS_ATTACH: { dllInstance = (HINSTANCE) hModule; } break;
    case DLL_THREAD_ATTACH: break;
    case DLL_THREAD_DETACH: break;
    case DLL_PROCESS_DETACH: break;
    }
    return TRUE;
}