Simple WASAPI sample?
-
- KVRer
- Topic Starter
- 10 posts since 27 Mar, 2016
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!
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!
-
- KVRAF
- 2256 posts since 29 May, 2012
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.
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~
-
- KVRer
- Topic Starter
- 10 posts since 27 Mar, 2016
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
I'm not really sure about the blocking mechanism and return values of ALSA, but it did actually work.
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;
}
-
- KVRer
- Topic Starter
- 10 posts since 27 Mar, 2016
Just to make the ALSA thing clearer, it's a loop like that:
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.
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;
}
}
-
- KVRist
- 86 posts since 4 Mar, 2010
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
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
-
- KVRer
- Topic Starter
- 10 posts since 27 Mar, 2016
Thanks a lot , j_e_g!
-
- KVRer
- Topic Starter
- 10 posts since 27 Mar, 2016
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)?
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)?
-
- KVRAF
- 2256 posts since 29 May, 2012
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)Does anyone know what Reaper means by the word "polled"?
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~
-
- KVRer
- Topic Starter
- 10 posts since 27 Mar, 2016
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.
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.
-
- KVRer
- Topic Starter
- 10 posts since 27 Mar, 2016
PS: I forgot to mention WaitForSingleObject uses INFINITE, that's why it only waits, sorry.
-
- KVRer
- Topic Starter
- 10 posts since 27 Mar, 2016
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.
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.
-
- KVRAF
- 2256 posts since 29 May, 2012
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.
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~
-
- KVRer
- Topic Starter
- 10 posts since 27 Mar, 2016
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.
If Portaudio can't do it reliably, I can't neither.
-
- KVRer
- Topic Starter
- 10 posts since 27 Mar, 2016
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!
-
- KVRer
- 1 posts since 10 Jun, 2016
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...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):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
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.