Search code examples
winapiaudiowave

Playing an arbitrary sound on Windows?


How do I, say, play a sound with a given amplitude and a given frequency makeup (say, consisting of frequencies 2 kHz and 3 kHz) natively on Windows (32-bit and 64-bit, up to Windows 7)?

(By natively I mean without using an external library.)

I believe this needs the waveOutWrite method but I have no idea how it works.


Solution

  • I got something working...

    #define _USE_MATH_DEFINES 1
    #include <math.h>
    #include <stdio.h>
    #include <tchar.h>
    
    #include <windows.h>
    #include <mmreg.h>
    #include <complex>
    
    #pragma comment(lib, "Winmm.lib")
    
    MMRESULT play(float nSeconds,
        float signal(float timeInSeconds, unsigned short channel, void *context),
        void *context = NULL, unsigned long samplesPerSecond = 48000)
    {
        UINT timePeriod = 1;
    
        MMRESULT mmresult = MMSYSERR_NOERROR;
        WAVEFORMATEX waveFormat = {0};
        waveFormat.cbSize = 0;
        waveFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
        waveFormat.nChannels = 2;
        waveFormat.nSamplesPerSec = samplesPerSecond;
        const size_t nBuffer =
            (size_t)(nSeconds * waveFormat.nChannels * waveFormat.nSamplesPerSec);
        float *buffer;
        waveFormat.wBitsPerSample = CHAR_BIT * sizeof(buffer[0]);
        waveFormat.nBlockAlign =
            waveFormat.nChannels * waveFormat.wBitsPerSample / CHAR_BIT;
        waveFormat.nAvgBytesPerSec =
            waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
    
        buffer = (float *)calloc(nBuffer, sizeof(*buffer));
        __try
        {
            for (size_t i = 0; i < nBuffer; i += waveFormat.nChannels)
                for (unsigned short j = 0; j < waveFormat.nChannels; j++)
                    buffer[i+j] = signal((i+j) * nSeconds / nBuffer, j, context);
            HWAVEOUT hWavOut = NULL;
            mmresult = waveOutOpen(&hWavOut, WAVE_MAPPER,
                &waveFormat, NULL, NULL, CALLBACK_NULL);
            if (mmresult == MMSYSERR_NOERROR)
            {
                __try
                {
                    timeBeginPeriod(timePeriod);
                    __try
                    {
                        WAVEHDR hdr = {0};
                        hdr.dwBufferLength =
                            (ULONG)(nBuffer * sizeof(buffer[0]));
                        hdr.lpData = (LPSTR)&buffer[0];
                        mmresult = waveOutPrepareHeader(hWavOut,
                            &hdr, sizeof(hdr));
                        if (mmresult == MMSYSERR_NOERROR)
                        {
                            __try
                            {
                                ULONG start = GetTickCount();
                                mmresult =
                                    waveOutWrite(hWavOut, &hdr, sizeof(hdr));
                                Sleep((ULONG)(1000 * nSeconds
                                    - (GetTickCount() - start)));
                            }
                            __finally
                            { waveOutUnprepareHeader(hWavOut, &hdr, sizeof(hdr)); }
                        }
                    }
                    __finally { timeEndPeriod(timePeriod); }
                }
                __finally { waveOutClose(hWavOut); }
            }
        }
        __finally { free(buffer); }
        return mmresult;
    }
    
    // Triangle wave generator
    float triangle(float timeInSeconds, unsigned short channel, void *context)
    {
        const float frequency = *(const float *)context;
        const float angle = (float)(frequency * 2 * M_PI * timeInSeconds);
        switch (channel)
        {
            case  0: return (float)asin(sin(angle + 0 * M_PI / 2));
            default: return (float)asin(sin(angle + 1 * M_PI / 2));
        }
    }
    
    // Pure tone generator
    float pure(float timeInSeconds, unsigned short channel, void *context)
    {
        const float frequency = *(const float *)context;
        const float angle = (float)(frequency * 2 * M_PI * timeInSeconds);
        switch (channel)
        {
            case  0: return (float)sin(angle + 0 * M_PI / 2);
            default: return (float)sin(angle + 1 * M_PI / 2);
        }
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        float frequency = 2 * 261.626F;
        play(1, pure, &frequency);
        return 0;
    }