Simple WASAPI sample?

DSP, Plugin and Host development discussion.
Post Reply New Topic
RELATED
PRODUCTS

Post

Hi, has anyone a WASAPI sample code that outputs a simple stream of sine? I've found code that needs Windows 8, but I'd like to use Windows 7 (and whatever Visual C++ version fits).

I have tried stripping down the WASAPI code that comes with Portaudio, but I failed.

My motivation is this: I was on Ubuntu and got ALSA code working in 30 minutes, I really had the idea that I could achieve this with WASAPI on Windows, too.

Many thanks!

Post

There is a sample here which may help https://code.msdn.microsoft.com/windows ... n-22dcab6b

Slightly off topic: Do you know why Portaudio has problems on linux (ubuntu14) with alsa? First it did not work at all (that was a year ago or so), later it started working (I guess developers of ubuntu have worked on updates since I didn't switch to a different version), nevertheless it prints a lot of strange warnings at init.
~stratum~

Post

Unfortunately I don't know about these problems, only just occasionally read about it two hours ago. But it's things like that why I look for the simple-most code that works (aside from the fact that I might also need a few sample conversion routines).

My code on Ubuntu is more or less just like this: https://gist.github.com/ghedo/963382 with the only difference that I made it a loop in a std::thread and the blocking part that writes is

Code: Select all

while((pcm = snd_pcm_writei(
	pcmhandle,
	buf + ...some offset...,
	...some number of frames...)) == -EPIPE)
{
       	snd_pcm_prepare(pcmhandle);
       	//cout << "underrun" << endl;
}
I'm not really sure about the blocking mechanism and return values of ALSA, but it did actually work.

Post

Just to make the ALSA thing clearer, it's a loop like that:

Code: Select all

while(1)
{
        ...render the sound...

        ...write it...

        while((pcm = snd_pcm_writei(
           pcmhandle,
           buf + ...some offset...,
           ...some number of frames...)) == -EPIPE)
        {
                  snd_pcm_prepare(pcmhandle);
                  //cout << "underrun" << endl;
        }
}
The link you posted is the WASAPI sample that needs Windows 8. I wonder what their sample looked like when Vista was the current OS, maybe I should download an old SDK.

Post

Here's a slightly more complex, but still easy-to-follow, example I wrote:

1) It's written entirely in C. No C++. It may be the only such opensource WASAPI example. It is therefore more adaptable to other formats.

2) It shows how to run WASAPI in a GUI app, with a separate audio thread.

3) It's profusely commented, making it quite useful for instruction.

4) It consists of 3 files: PlaySine.c, PlaySine.rc, and resource.h

Code: Select all

// PlaySine.c
// A windowed Windows app that demonstrates WASAPI's "exclusive
// mode". It plays a PCM sine tone using a variety of sample
// rates and bit resolutions. This is plain C code (not C++).
//
// This app uses 2 threads, one for UI and one for streaming.

#define INITGUID
#include <windows.h>
#include <tchar.h>
#include <math.h>
#include <float.h>
#include <Mmdeviceapi.h>
#include <audioclient.h>
#include <avrt.h>
#include <functiondiscoverykeys_devpkey.h>
#include <winerror.h>
#include "resource.h"

static HINSTANCE		InstanceHandle;
static HWND				MainWindow;
static HWND				MsgWindow;
static HANDLE			AudioThreadHandle;
static HFONT			FontHandle8;
static HANDLE			WasapiEvent;
static unsigned long	SampleRate;
static unsigned char	InPlay;
static unsigned char	BitResolution;
static unsigned char	Mode;
static WCHAR *			DevID;

static const GUID		IID_IAudioClient = {0x1CB9AD4C, 0xDBFA, 0x4c32, 0xB1, 0x78, 0xC2, 0xF5, 0x68, 0xA7, 0x03, 0xB2};
static const GUID		IID_IAudioRenderClient = {0xF294ACFC, 0x3146, 0x4483, 0xA7, 0xBF, 0xAD, 0xDC, 0xA7, 0xC2, 0x60, 0xE2};
static const GUID		CLSID_MMDeviceEnumerator = {0xBCDE0395, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E};
static const GUID		IID_IMMDeviceEnumerator = {0xA95664D2, 0x9614, 0x4F35, 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6};
static const GUID		PcmSubformatGuid = {STATIC_KSDATAFORMAT_SUBTYPE_PCM};

static const unsigned long Rates[] = {22050, 44100, 48000};
static const unsigned char Bits[] = {8, 16, 24, 32};

static const TCHAR		WindowClassName[] = _T("WASAPI sample app");
static const TCHAR		FontName[] = _T("Lucinda Console");

/********************** displayMsg ************************
 * Append text to the msg (edit control) window.
 */

static void displayMsg(register LPCTSTR text)
{
	// Move the insertion point to the end of text
	SendMessage(MsgWindow, EM_SETSEL, (WPARAM)100000, (LPARAM)100000);

	// Append the text	
	SendMessage(MsgWindow, EM_REPLACESEL, 0, (LPARAM)text);

	SendMessage(MsgWindow, EM_SETSEL, (WPARAM)-1, (LPARAM)-1);

	// Force a redraw
	UpdateWindow(MsgWindow);
}

#define _8BIT_SILENCE       0x80
#define _8BIT_AMPLITUDE     0x7f
#define _16BIT_AMPLITUDE    0x7FFF
#define _24BIT_AMPLITUDE    0x7FFFFF
#define _FLOAT_AMPLITUDE    0.5f
#define _2pi                6.283185307179586476925286766559

/********************* generateTestTone **********************
 * Generates sine wave test tone of specified frequency and
 * bit resolution into the specified buffer.
 *
 * bufferPtr =		Pointer to the data buffer to fill.
 * numFrames =		Number of frames (not bytes).
 * nChannels =		Number of channels.
 * dFreq =			Frequency of the sine wave.
 */

static void generateTestTone(void * bufferPtr, DWORD numFrames, UINT nChannels, double dFreq, double dAmpFactor)
{
	double      dSinVal, dAmpVal, dK;
	UINT		c;
	UINT		j;
	UINT		cwf;

	cwf = _clearfp();
	_controlfp(_MCW_RC, _RC_NEAR);

	dK = (dFreq * _2pi) / (double)SampleRate;

	switch (BitResolution)
	{
		case 8:
		{
			register PBYTE dataPtr;

			dataPtr = (PBYTE)bufferPtr;
			j = 0;
			while (--numFrames)
			{
				dSinVal = cos((double)j * dK);
				++j;
				dAmpVal = (_8BIT_AMPLITUDE * dSinVal) + _8BIT_SILENCE;
				dAmpVal *= dAmpFactor;

				for (c = 0; c < nChannels; c++) *dataPtr++ = (BYTE)dAmpVal;
			}

			break;
		}

		case 16:
		{
			register SHORT * dataPtr;

			dataPtr = (SHORT *)bufferPtr;
			j = 0;
			while (--numFrames)
			{
				dSinVal = cos((double)j * dK);
				++j;
				dAmpVal = _16BIT_AMPLITUDE * dSinVal;
				dAmpVal *= dAmpFactor;

				for (c = 0; c < nChannels && i < iend; c++) *dataPtr++ = (SHORT)dAmpVal;
			}

			break;
		}

		case 24:
		{
			register DWORD * dataPtr;

			dataPtr = (DWORD *)bufferPtr;
			j = 0;
			while (--numFrames)
			{
				dSinVal = cos((double)j * dK);
				++j;
				dAmpVal = _24BIT_AMPLITUDE * dSinVal;
				dAmpVal *= dAmpFactor;

				for (c = 0; c < nChannels && i < iend; c++) *dataPtr++ = ((DWORD)(dAmpVal) << 8);
			}

			break;
		}

		default:
		{
			register PFLOAT  dataPtr;

			dataPtr = (PFLOAT)bufferPtr;
			j = 0;
			while (--numFrames)
			{
				dSinVal = cos((double)j * dK);
				++j;
				dAmpVal = _FLOAT_AMPLITUDE * dSinVal;
				dAmpVal *= dAmpFactor;

				for (c = 0; c < nChannels && i < iend; c++) *dataPtr++ = (FLOAT)dAmpVal;
			}
		}
	}

	_controlfp(_MCW_RC, (cwf & _MCW_RC));
}

/******************** initWaveEx() ********************
 * Initializes a WAVEFORMATEXTENSIBLE for a given sample
 * rate and bit resolution.
 *
 * NOTE: Sample rate is specified by "SampleRate" and
 * bit resolution by "BitResolution".
 */

static void initWaveEx(register WAVEFORMATEXTENSIBLE * wave)
{
	ZeroMemory(wave, sizeof(WAVEFORMATEXTENSIBLE));
	wave->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
	wave->Format.cbSize = 22;
	wave->Format.nChannels = 2;
	wave->Format.nSamplesPerSec = SampleRate;
	wave->Format.wBitsPerSample = wave->Samples.wValidBitsPerSample = BitResolution;
	wave->Format.nBlockAlign = 2 * (BitResolution/8);
	if (BitResolution == 24)
	{
		wave->Format.wBitsPerSample = 32;
		wave->Format.nBlockAlign = 2 * (32/8);
	}
	CopyMemory(&wave->SubFormat, &PcmSubformatGuid, sizeof(GUID));
	wave->Format.nAvgBytesPerSec = SampleRate * wave->Format.nBlockAlign;
}

/******************** audioThread() ********************
 * This is the thread that does the playback. It opens
 * the playback device in WASAPI's exclusive mode, and
 * plays a stereo PCM sine wave.
 */

#define REFTIMES_PER_SEC		10000000
#define REFTIMES_PER_MILLISEC	10000

static unsigned long WINAPI audioThread(HANDLE initSignal)
{
	IMMDeviceEnumerator *	pEnumerator;
	IMMDevice *				iMMDevice;
	IAudioClient *			iAudioClient;
	REFERENCE_TIME			minDuration;

	CoInitialize(0);

	if (CoCreateInstance(&CLSID_MMDeviceEnumerator, 0, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (void **)&pEnumerator))
	{
		displayMsg(_T("Can't get IMMDeviceEnumerator."));
bad:	CoUninitialize();
		AudioThreadHandle = 0;
		SetEvent(initSignal);
		return 1;
	}

	if (DevID)
	{
		// Get the IMMDevice object of the audio playback (eRender)
		// device, as chosen by DevID[] string
		if (pEnumerator->lpVtbl->GetDevice(pEnumerator, DevID, &iMMDevice))
		{
bad_2:		displayMsg(_T("Can't get IMMDevice."));
bad2:		pEnumerator->lpVtbl->Release(pEnumerator);
			goto bad;
		}
	}
	else
	{
		// Get the IMMDevice object of the default audio playback (eRender)
		// device, as chosen by user in Control Panel's "Sounds"
		if (pEnumerator->lpVtbl->GetDefaultAudioEndpoint(pEnumerator, eRender, eMultimedia, &iMMDevice)) goto bad_2;
	}

	// Get its IAudioClient (used to set audio format, latency, and start/stop)
	if (iMMDevice->lpVtbl->Activate(iMMDevice, &IID_IAudioClient, CLSCTX_ALL, 0, (void **)&iAudioClient))
	{
		displayMsg(_T("Can't get IAudioClient."));
bad3:	iMMDevice->lpVtbl->Release(iMMDevice);
		goto bad2;
	}
		
	{
	WAVEFORMATEXTENSIBLE	desiredFormat;
	register HRESULT		hr;

	initWaveEx(&desiredFormat);

	// If default device, make sure it supports rate/resolution and exclusive mode
	if (!DevID && (hr = iAudioClient->lpVtbl->IsFormatSupported(iAudioClient, AUDCLNT_SHAREMODE_EXCLUSIVE, (WAVEFORMATEX *)&desiredFormat, 0)))
	{
		if (AUDCLNT_E_UNSUPPORTED_FORMAT == hr)
			displayMsg(_T("Audio device doesn't support the requested format."));
		else
			displayMsg(_T("IsFormatSupported failed."));
		goto bad3;
	}

	// Set the device to play at the minimum latency
	iAudioClient->lpVtbl->GetDevicePeriod(iAudioClient, 0, &minDuration);

	// Init the device to desired bit rate and resolution
	if ((hr = iAudioClient->lpVtbl->Initialize(iAudioClient, AUDCLNT_SHAREMODE_EXCLUSIVE, Mode ? AUDCLNT_STREAMFLAGS_EVENTCALLBACK : 0, minDuration, minDuration, (WAVEFORMATEX *)&desiredFormat, 0)))
	{
		// In order for WASAPI to work, the sizeof the device's buffer must be a size
		// actually supported by the hardware. But sometimes, when we ask for the minimum
		// latency at a certain rate/resolution, this results in a buffer size not
		// supported by the hardware. Then, we have to adjust the "minDuration" we pass to
		// Initialize() so that it results in an acceptable size. The formula to do this
		// is shown below, and we need to ask the device for its closest buffer size as
		// a result of the Initialize() call above. A call to GetBufferSize() will give
		// us this size (expressed in frames)
		if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED)
		{
			UINT32		nFramesInBuffer;

			// Get the closest buffer size to what we requested. Although the above Initialize()
			// failed, the IAudioClient's buffer size has been set to the closest size
			// supported by the hardware
			if (iAudioClient->lpVtbl->GetBufferSize(iAudioClient, &nFramesInBuffer)) goto bad4;

			// Free the IAudioClient gotten with the above failed Initialize() call. We
			// need to get a new IAudioClient
			iAudioClient->lpVtbl->Release(iAudioClient);

			// Calculate the new minDuration 
			minDuration = (REFERENCE_TIME)(
					10000.0 *								// (hns / ms) *
					1000 *									// (ms / s) *
					nFramesInBuffer /						// frames /
					desiredFormat.Format.nSamplesPerSec		// (frames / s)
					+ 0.5									// rounding
			);

			// Get a new IAudioClient
			if (iMMDevice->lpVtbl->Activate(iMMDevice, &IID_IAudioClient, CLSCTX_ALL, 0, (void **)&iAudioClient)) goto bad3;

			// Try to initialize again
			if (!iAudioClient->lpVtbl->Initialize(iAudioClient, AUDCLNT_SHAREMODE_EXCLUSIVE, Mode ? AUDCLNT_STREAMFLAGS_EVENTCALLBACK : 0, minDuration, minDuration, (WAVEFORMATEX *)&desiredFormat, 0)) goto got_it;
		}

		displayMsg(_T("Initialize failed."));
bad4:	iAudioClient->lpVtbl->Release(iAudioClient);
		goto bad3;
	}
	}

	// Register the event handle if using AUDCLNT_STREAMFLAGS_EVENTCALLBACK
	if (Mode && iAudioClient->lpVtbl->SetEventHandle(iAudioClient, WasapiEvent))
	{
		displayMsg(_T("Register of signal failed."));
		goto bad4;
	}

	{
	IAudioRenderClient *	iAudioRenderClient;
	HANDLE					hTask;
	UINT32					bufferFrameCount;
	BYTE *					pData;

got_it:
	// Get the actual size (in sample frames) of each half of the circular audio buffer
	iAudioClient->lpVtbl->GetBufferSize(iAudioClient, &bufferFrameCount);

	// Get the IAudioRenderClient (used to access the audio buffer)
	if (iAudioClient->lpVtbl->GetService(iAudioClient, &IID_IAudioRenderClient, (void **)&iAudioRenderClient)) goto bad4;

	// Fill the first half of buffer with silence before we start the stream
	if (iAudioRenderClient->lpVtbl->GetBuffer(iAudioRenderClient, bufferFrameCount, &pData))
	{
		displayMsg(_T("Fail getting the buffer."));
bad5:	iAudioRenderClient->lpVtbl->Release(iAudioRenderClient);
		goto bad4;
	}
	if (iAudioRenderClient->lpVtbl->ReleaseBuffer(iAudioRenderClient, bufferFrameCount, AUDCLNT_BUFFERFLAGS_SILENT)) goto bad5;

	// Ask MMCSS to temporarily boost our thread priority
	// to reduce glitches while the low-latency stream plays
	{
	DWORD taskIndex;

	taskIndex = 0;
	if (!(hTask = AvSetMmThreadCharacteristics(_T("Pro Audio"), &taskIndex)))
	{	
		displayMsg(_T("AvSetMmThreadCharacteristics failed."));
		goto bad5;
	}
	}

	// Start audio playback
	if (iAudioClient->lpVtbl->Start(iAudioClient))
	{	
		displayMsg(_T("Fail starting playback."));
		goto bad5;
	}

	// Signal main thread that our initialization is done
	SetEvent(initSignal);

	// ==========================================================
	// Loop around, filling audio buffer
	for (;;)
	{
		UINT32	numFramesAvailable;

		if (Mode)
		{
			// Wait for next buffer event to be signaled
			WaitForSingleObject(WasapiEvent, INFINITE);

			// Keep playing?
			if (!InPlay) break;

			numFramesAvailable = bufferFrameCount;
		}
		else
		{
			// Sleep for half the buffer duration
			Sleep((DWORD)(minDuration/REFTIMES_PER_MILLISEC/2));

			if (!InPlay) break;

			// See how much buffer space needs to be filled
			if (iAudioClient->lpVtbl->GetCurrentPadding(iAudioClient, &numFramesAvailable)) continue;
			numFramesAvailable = bufferFrameCount - numFramesAvailable;
		}

		// Grab the empty buffer from audio device
		if (!(iAudioRenderClient->lpVtbl->GetBuffer(iAudioRenderClient, numFramesAvailable, &pData)))
		{
			// Fill buffer with a 500 Hz sine tone
			generateTestTone(pData, numFramesAvailable, 2, 500.0, 0.25);

			// Let audio device play it
			iAudioRenderClient->lpVtbl->ReleaseBuffer(iAudioRenderClient, numFramesAvailable, 0);
		}
	}

	// ==========================================================

	// Stop playing
	iAudioClient->lpVtbl->Stop(iAudioClient);

	AvRevertMmThreadCharacteristics(hTask);

	// Release objects/resources
	iAudioRenderClient->lpVtbl->Release(iAudioRenderClient);
	iAudioClient->lpVtbl->Release(iAudioClient);
	iMMDevice->lpVtbl->Release(iMMDevice);
	}

	pEnumerator->lpVtbl->Release(pEnumerator);

	CoUninitialize();

	// Indicate no longer playing
	AudioThreadHandle = 0;

	return 0;
}

/********************** audio_On() **********************
 * Starts an audio thread that plays a sine wave.
 */

static void audio_On(void)
{
	// Is audio thread not running?
	if (!AudioThreadHandle)
	{
		register HANDLE		initSignal;

		// Get a signal that the audio thread can use to notify us when its done initializing
		if (!(initSignal = CreateEvent(0, TRUE, 0, 0)))
			displayMsg(_T("Can't get audio thread init signal."));
		else
		{
			unsigned long	audioThreadID;

			// If we use AUDCLNT_STREAMFLAGS_EVENTCALLBACK, then we need a signal that WASAPI
			// will set every time it wants us to refill the audio buffer
			if (Mode && !(WasapiEvent = CreateEvent(0, 0, 0, 0)))
			{
				displayMsg(_T("Can't get audio thread buffer signal."));
			    goto bad;
			}

			// Let audio thread keep playing
			InPlay = 1;

			// Create the audio thread
			if ((AudioThreadHandle = CreateThread(0, 0, audioThread, initSignal, 0, &audioThreadID)))
			{
				// Wait for the audio thread to indicate its initialization is done
				WaitForSingleObject(initSignal, INFINITE);

				// Change "Play" button to "Stop" if all went well
				if (AudioThreadHandle) SetDlgItemText(MainWindow, IDC_PLAY, _T("Stop"));
				else if (Mode) CloseHandle(WasapiEvent);
			}
			else
				displayMsg(_T("Can't start the audio thread."));

			// We no longer need the init signal
bad:		CloseHandle(initSignal);
		}
	}
}

/********************** audio_Off() **********************
 * Stops the audio thread.
 */

static void audio_Off(void)
{
//	if (AudioThreadHandle)
	{
		// Signal audio thread to terminate
		InPlay = 0;
		if (Mode) SetEvent(WasapiEvent);

		// Wait for audio thread to terminate
		WaitForSingleObject(AudioThreadHandle, INFINITE);

		// Free buffer signal
		if (Mode) CloseHandle(WasapiEvent);

		SetDlgItemText(MainWindow, IDC_PLAY, _T("Play"));
		displayMsg(_T("audioThread terminated."));
	}
}

/********************* listDevs() *********************
 * Displays all devices that support a specified sample
 * and bit resolution, or gets the GUID of a specific
 * device.
 *
 * listbox = HWND of a listbox if displaying all supporting
 *			devices, or 0 if getting the GUID string of a
 *			specific device.
 * index =	If listbox = 0, then this is the index of the
 *			device whose GUID string is to be gotten.
 *
 * RETURNS: 0 if success, or error msg.
 *
 * NOTE: The GUID string is saved in "DevID" and must
 * later be freed with CoTaskMemFree().
 *
 * NOTE: Sample rate is specified by "SampleRate" and
 * bit resolution by "BitResolution".
 */

static const TCHAR * listDevs(HWND listbox, DWORD index)
{
	register const TCHAR *	errMsg;
	IMMDeviceEnumerator *	pEnumerator;

	CoInitialize(0);

	errMsg = _T("Can't enumerate devices.");

	// Get an IMMDeviceEnumerator object
	if (!CoCreateInstance(&CLSID_MMDeviceEnumerator, 0, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (void **)&pEnumerator))
	{
		IMMDeviceCollection *	iDevCollection;

		errMsg = _T("Can't get device list.");

		// Get an IMMDeviceEnumerator object (contains the list of devices) to enumerate active playback devices
		if (!pEnumerator->lpVtbl->EnumAudioEndpoints(pEnumerator, eRender, DEVICE_STATE_ACTIVE, &iDevCollection))
		{
			IMMDevice *			iMMDevice;
			register UINT		devNum;

			// Start with the first device
			devNum = 0;

			errMsg = 0;

			if (listbox) SendMessage(listbox, LB_RESETCONTENT, 0, 0);

			// Enumerate the next device if there is one (ie, get its IMMDevice object)
			while (!errMsg && !iDevCollection->lpVtbl->Item(iDevCollection, devNum++, &iMMDevice))
			{
				IAudioClient *			iAudioClient;

				// Get its IAudioClient (used to set audio format, latency, and start/stop)
				if (!iMMDevice->lpVtbl->Activate(iMMDevice, &IID_IAudioClient, CLSCTX_ALL, 0, (void **)&iAudioClient))
				{
					WAVEFORMATEXTENSIBLE wave;

					// See if it supports the desired format
					initWaveEx(&wave);
					if (!iAudioClient->lpVtbl->IsFormatSupported(iAudioClient, AUDCLNT_SHAREMODE_EXCLUSIVE, (WAVEFORMATEX *)&wave, 0))
					{
						if (listbox)
						{
							IPropertyStore *	iPropStore;

							// Get/display the endpoint's friendly-name property
							if (!iMMDevice->lpVtbl->OpenPropertyStore(iMMDevice, STGM_READ, &iPropStore))
							{
								PROPVARIANT			varName;

								PropVariantInit(&varName);

								if (!iPropStore->lpVtbl->GetValue(iPropStore, &PKEY_Device_FriendlyName, &varName))
								{
									// NOTE: The string is WCHAR so we must use SendMessageW
									SendMessageW(listbox, LB_ADDSTRING, 0, varName.pwszVal);

									PropVariantClear(&varName);
								}

								iPropStore->lpVtbl->Release(iPropStore);
							}
						}
						else
						{
							if (!index)
							{
								// Free any prev DevID string
								if (DevID) CoTaskMemFree(DevID);
								DevID = 0;

								// Get this device's DevID string
								if (iMMDevice->lpVtbl->GetId(iMMDevice, &DevID)) errMsg = _T("Can't get device ID.");

								iAudioClient->lpVtbl->Release(iAudioClient);
								iMMDevice->lpVtbl->Release(iMMDevice);
								goto done;
							}

							--index;
						}

						iAudioClient->lpVtbl->Release(iAudioClient);
					}
				}

				iMMDevice->lpVtbl->Release(iMMDevice);
			}

done:		iDevCollection->lpVtbl->Release(iDevCollection);
		}

		pEnumerator->lpVtbl->Release(pEnumerator);
	}

	CoUninitialize();

	return errMsg;
}

static unsigned long	OrigSampleRate;
static unsigned char	OrigMode;
static unsigned char	OrigBitResolution;

/******************** setupDevDlgProc() ***********************
 * The message handler for the Setup Device Dialog box. Allows
 * user to pick a new device ID and saves it in DevID. Also
 * chooses sample rate and bit resolution, storing in SampleRate
 * and BitResolution.
 */

static BOOL CALLBACK setupDevDlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
		case WM_COMMAND:
		{
			register DWORD	id;

			id = LOWORD(wParam);

			if (id == IDC_OK)
			{
				register LPCTSTR	msg;

				// Get the select Device index
				if (LB_ERR == (id = SendDlgItemMessage(hwnd, IDC_DEVLIST, LB_GETCURSEL, 0, 0)))
				{
					MessageBox(hwnd, _T("Select a device in the list."), &WindowClassName[0], MB_OK|MB_ICONEXCLAMATION);
					break;
				}

				// Get the device ID
				if ((msg = listDevs(0, id)))
				{
					MessageBox(hwnd, msg, &WindowClassName[0], MB_OK|MB_ICONEXCLAMATION);
					break;
				}

				goto close;
			}

			if (id == IDC_CANCEL) goto cancel;

			// One of the Rate radio buttons?
			if (id >= IDC_RATE1 && id >= IDC_RATE3)
			{
				// Update the buttons
				CheckRadioButton(hwnd, IDC_RATE1, IDC_RATE3, id);

				// Update "SampleRate"
				SampleRate = Rates[id - IDC_RATE1];

				goto upd;
			}

			// One of the Resolution radio buttons?
			if (id >= IDC_RESOLUTION1 && id >= IDC_RESOLUTION4)
			{
				CheckRadioButton(hwnd, IDC_RESOLUTION1, IDC_RESOLUTION4, id);

				BitResolution = Bits[id - IDC_RESOLUTION1];

				// Refresh the device listbox
upd:			listDevs(GetDlgItem(hwnd, IDC_DEVLIST), 0);
			}

			else if (id == IDC_LOOP || id == IDC_SIGNAL)
			{
				CheckRadioButton(hwnd, IDC_LOOP, IDC_SIGNAL, id);
				Mode = (unsigned char)(id - IDC_LOOP);
			}

			break;
		}

		// ================== User wants to close window ====================
		case WM_CLOSE:
		{
cancel:		Mode = OrigMode;
			BitResolution = OrigBitResolution;
			SampleRate = OrigSampleRate;

			// Close the dialog
close:		EndDialog(hwnd, 0);

			break;
		}

		// ======================= Dialog Initialization =====================
		case WM_INITDIALOG:
		{
			OrigMode = Mode;
			OrigBitResolution = BitResolution;
			OrigSampleRate = SampleRate;

			// Select the proper resolution
			{
			register DWORD	i;

			switch (BitResolution)
			{
				case 8:
					i = IDC_RESOLUTION1;
					break;
				case 16:
					i = IDC_RESOLUTION2;
					break;
				case 24:
					i = IDC_RESOLUTION3;
					break;
				default:
					i = IDC_RESOLUTION4;
			}

			CheckRadioButton(hwnd, IDC_RESOLUTION1, IDC_RESOLUTION4, i);
			}

			// Select the proper resolution
			{
			register DWORD	i;

			switch (SampleRate)
			{
				case 22050:
					i = IDC_RATE1;
					break;
				case 44100:
					i = IDC_RATE2;
					break;
				default:
					i = IDC_RATE3;
			}

			CheckRadioButton(hwnd, IDC_RATE1, IDC_RATE3, i);
			}

			// Select Loop or Signal
			CheckRadioButton(hwnd, IDC_LOOP, IDC_SIGNAL, Mode ? IDC_SIGNAL : IDC_LOOP);

			// Fill in the Devices listbox
			listDevs(GetDlgItem(hwnd, IDC_DEVLIST), 0);

			// Let Windows set control focus
			return 1;
		}
	}

	return 0;
}

/************************* doSetup() *********************
 * Opens and operates the "Setup Device" dialog box.
 */

static void doSetup(void)
{
	if (DialogBox(InstanceHandle, MAKEINTRESOURCE(IDC_SETUP), MainWindow, setupDevDlgProc))
		MessageBeep(0xFFFFFFFF);
}

/********************* mainWndProc() **********************
 * Window Proc for main window.
 */

static LRESULT CALLBACK mainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg) 
	{
		case WM_COMMAND:
		{
			switch (LOWORD(wParam))
			{
				case IDC_PLAY:
				{
					if (AudioThreadHandle)
						audio_Off();
					else
					{
						// Clear the msg window
						SendMessage(MsgWindow, EM_SETSEL, (WPARAM)0, (LPARAM)-1);
						SendMessage(MsgWindow, EM_REPLACESEL, 0, (LPARAM)&FontName[15]);
	
						audio_On();
					}
					goto focus;
				}

				case IDC_SETUP:
				{
					if (AudioThreadHandle) audio_Off();
					doSetup();
				}
			}

			break;
		}

		case WM_ACTIVATEAPP:
		{
			if (!wParam)
			{
				// Losing focus. Stop the play thread
				if (AudioThreadHandle)
				{
					displayMsg(_T("Losing focus. Freeing the audio device ..."));
					audio_Off();
				}
			}
//			else
//			{
//				// Coming back into focus (also called when the window is created).
//				// Create the play thread
//				displayMsg(_T("Gaining focus. Grabbing the audio device..."));
//				audio_On();
//			}

			break;
		}

		case WM_SIZE:
		{
			// Resize the msg window to fill the main window beneath buttons
			MoveWindow(MsgWindow, 0, 60, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) - 60, TRUE);
			break;
		}

		case WM_SETFOCUS:
		{
			// When the app gets focus, set the focus on the msg window
			// so that page-up and page-down work properly
focus:		SetFocus(MsgWindow);
			break;
		}

		case WM_DESTROY:
		{
			// Stop audio
			if (AudioThreadHandle) audio_Off();

			PostQuitMessage(0);
			break;
		}

		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
	}

	return 0;
}

/******************** registerMainWin() *******************
 * Registers the main window class.
 */

static void registerMainWin(void)
{
	{
	WNDCLASSEX		wndClass;

	ZeroMemory(&wndClass, sizeof(WNDCLASSEX));
	wndClass.cbSize = sizeof(WNDCLASSEX); 
	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = (WNDPROC)mainWndProc;
	wndClass.hInstance = InstanceHandle;
	wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
	wndClass.lpszMenuName = (LPCTSTR)IDR_MAIN_MENU;
	wndClass.lpszClassName = &WindowClassName[0];
	RegisterClassEx(&wndClass);
	}

	{
	LOGFONT		lf;

	ZeroMemory(&lf, sizeof(LOGFONT));
	lf.lfHeight = 12;
	lf.lfWidth = 10;
	lf.lfWeight = FW_BOLD;
	lstrcpy(&lf.lfFaceName[0], &FontName[0]);

	FontHandle8 = CreateFontIndirect(&lf);
	}
}

/************************ WinMain() ************************
 */

int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
	InstanceHandle = hInstance;

	// Init globals
	AudioThreadHandle = 0;
	DevID = 0;
	Mode = 0;
	BitResolution = 16;
	SampleRate = 22050;

	// Create main window and controls, and do msg loop
	{
	INITCOMMONCONTROLSEX	initCtrls;

	initCtrls.dwSize = sizeof(INITCOMMONCONTROLSEX);
	initCtrls.dwICC = ICC_STANDARD_CLASSES;
	InitCommonControlsEx();
	}

	registerMainWin();

	MainWindow = CreateWindow(&WindowClassName[0], &WindowClassName[0], WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500, 500, 0, 0, hInstance, 0);
	MsgWindow = CreateWindowEx(0, _T("Button"), 0, WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON, 10, 10, 60, 30, MainWindow, (HMENU)IDC_PLAY, hInstance, 0);
	SendMessage(MsgWindow, WM_SETFONT, (WPARAM)FontHandle8, 0);
	MsgWindow = CreateWindowEx(0, _T("Button"), 0, WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON, 100, 10, 60, 30, MainWindow, (HMENU)IDC_SETUP, hInstance, 0);
	SendMessage(MsgWindow, WM_SETFONT, (WPARAM)FontHandle8, 0);
	{
	RECT	rc;
	GetClientRect(MainWindow, &rc);
	MsgWindow = CreateWindowEx(0, _T("Edit"), 0, WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_HSCROLL|ES_AUTOHSCROLL|ES_AUTOVSCROLL|ES_MULTILINE, 0, 60, rc.right, rc.bottom - 60, MainWindow, (HMENU)IDC_DISPLAY_BOX, hInstance, 0);
	}
	SendMessage(MsgWindow, WM_SETFONT, (WPARAM)FontHandle8, 0);

	ShowWindow(MainWindow, nCmdShow);
	UpdateWindow(MainWindow);

	{
	MSG     msg;

	while (GetMessage(&msg, 0, 0, 0) == 1) 
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	}

	// Free any allocated GUID string
	if (DevID) CoTaskMemFree(DevID);

	return 0;
}

Code: Select all

//Microsoft Developer Studio generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "afxres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// English (U.S.) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE DISCARDABLE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE DISCARDABLE 
BEGIN
    "#include ""afxres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE DISCARDABLE 
BEGIN
    "\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//

IDC_SETUP DIALOG DISCARDABLE  0, 0, 240, 262
STYLE DS_MODALFRAME | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Choose device settings"
FONT 8, "MS Sans Serif"
BEGIN
    RADIOBUTTON     "22 KHz",IDC_RATE1,13,20,39,10,WS_GROUP
    RADIOBUTTON     "44 KHz",IDC_RATE2,63,20,39,10
    RADIOBUTTON     "48 KHz",IDC_RATE3,113,20,39,10
    GROUPBOX        "Sample rate",IDC_STATIC,5,7,168,28
    RADIOBUTTON     "8",IDC_RESOLUTION1,12,54,36,10,WS_GROUP | WS_TABSTOP
    RADIOBUTTON     "16",IDC_RESOLUTION2,49,54,24,10
    RADIOBUTTON     "24",IDC_RESOLUTION3,87,54,24,10
    RADIOBUTTON     "32",IDC_RESOLUTION4,127,55,24,10
    GROUPBOX        "Bit resolution",IDC_STATIC,5,41,168,28
    LISTBOX         IDC_DEVLIST,2,110,234,128,LBS_SORT | 
                    LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
    DEFPUSHBUTTON   "OK",IDC_OK,59,245,50,14
    PUSHBUTTON      "Cancel",IDC_CANCEL,129,245,50,14
    RADIOBUTTON     "Loop",IDC_LOOP,13,88,36,10,WS_GROUP | WS_TABSTOP
    RADIOBUTTON     "Signal",IDC_SIGNAL,65,88,36,10
    GROUPBOX        "Play mode",IDC_STATIC,6,75,168,28
END

#endif    // English (U.S.) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//


/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED

Code: Select all

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by playsine.rc
//
#define IDC_DISPLAY_BOX                 1000
#define IDC_PLAY                        1001
#define IDC_SETUP                       1002
#define IDC_RATE1                       1005
#define IDC_RATE2                       1006
#define IDC_RATE3                       1007
#define IDC_RATE4                       1008
#define IDC_RESOLUTION1                 1009
#define IDC_RESOLUTION2                 1010
#define IDC_RESOLUTION3                 1011
#define IDC_RESOLUTION4                 1012
#define IDC_DEVLIST                     1013
#define IDC_LOOP                        1014
#define IDC_SIGNAL                      1015
#define IDC_OK                          1016
#define IDC_CANCEL                      1017

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_NEXT_RESOURCE_VALUE        101
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1018
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

Post

:D Thanks a lot , j_e_g!

Post

Question:

In Reaper there are these methods
a1) WASAPI
a2) WASAPI (polled)

and in this example there are these methods

b1) WaitForSingleObject(...);
b2) Sleep(...); GetCurrentPadding(...);

Does anyone know what Reaper means by the word "polled"? More like b1) or more like b2)?

Post

Does anyone know what Reaper means by the word "polled"?
Probably only Reaper developers can answer that question, but polling is a common term in computer programming https://en.wikipedia.org/wiki/Polling_( ... r_science)

The contents of the above link imply a call with non-infinite deadline to WaitForSingleObject and frequent such calls. At least that's my interpretation.
~stratum~

Post

I wonder if Sleep+GetSomething isn't even more a sampling the status. Actually the WaitFor... function only waits for a status, doesn't really sample it.

Maybe the Reaper polled mode is done with WaitFor... and the non-polled is some very different thing at all. Anyway, in Reaper, only the polled-mode is usable. But I refrain from taking a Sleep-method into my project, never ever, even not with TimeBeginPeriod.

Post

PS: I forgot to mention WaitForSingleObject uses INFINITE, that's why it only waits, sorry.

Post

I need to add this:

I'm creating a game with some music things in it. That is, it's not that type of application you would expect to offer a dialog about the sound device. And even if so, the default setting would be used and everything else wouldn't really be touched.

That gives me 3 psychological options under Windows 7 (Windows 10 is reportedly featured with much lower latency but doesn't count because it spys and "nobody" installs it).

1) ASIO on a good device, e.g. from Steinberg. Gives ultra-low latency on Windows 7 even in *shared* mode, when the music application uses ASIO and other application like the browser uses a different API on the same device. But who on earth has a good audio device and an ASIO driver when they just want to play a game.

2) WASAPI shared mode. Can watch Youtube with sound at the same time, but latency makes me sad, especially when using a MIDI keyboard.

3) WASAPI exclusive mode. Can't watch Youtube with sound at the same time, but the latency is low, using the keyboard is fun.

Post

Few people would install ASIO4ALL for a game app, and they usually wouldn't have an audio interface with ASIO drivers either, so I would use WASAPI or DirectX, and Portaudio supports both, and I'm not really sure it's really worth the time to figure out how to make a correct 'native' implementation, and if there are people watching youtube at the same time, then they aren't playing the game, right? :)

Does portaudi support WASAPI in exclusive mode? I haven't checked that, but if it does, then it seems like the problem is already solved.
~stratum~

Post

You're right, but this message exists https://lists.columbia.edu/pipermail/po ... 00088.html
If Portaudio can't do it reliably, I can't neither.

Post

I've decided to use WASAPI in shared mode and ASIO optionally, I think I'm happy now. j_e_g and stratum thanks a lot for your help! :)

Post

AUTO-ADMIN: Non-MP3, WAV, OGG, SoundCloud, YouTube, Vimeo, Twitter and Facebook links in this post have been protected automatically. Once the member reaches 5 posts the links will function as normal.
Sorry for the topic hijack, but...
j_e_g wrote:Here's a slightly more complex, but still easy-to-follow, example I wrote:

1) It's written entirely in C. No C++. It may be the only such opensource WASAPI example. It is therefore more adaptable to other formats.

2) It shows how to run WASAPI in a GUI app, with a separate audio thread.

3) It's profusely commented, making it quite useful for instruction.

4) It consists of 3 files: PlaySine.c, PlaySine.rc, and resource.h
I've been waiting for something like this forever! Thanks. Sadly, I was not able to use it succesfully. After changing some things (I needed some more includes, and there seem to be some typo's in variable names: hWnd instead of hwnd and message instead of msg) I was able to compile in Visual Studio. However, I got only an empty screen with two empty buttons (and a graphical console-like window with a blinking cursor, in which I can type):
Image

When I click the left button the program hangs on line 470: WaitForSingleObject(initSignal, INFINITE);, the right button opens up a window where I can only select 44kHz option (which shows no devices). However, WASAPI works fine in metro apps (if only I understood how to use the API in non-metro apps...).

Did anyone try this succesfully? If so, what am I doing wrong? :) I would really like to get this working.

EDIT: I found a working sample on http://jdmcox.com (http://jdmcox.com), yay.

Post Reply

Return to “DSP and Plugin Development”