/* * This module provides sound access for QUISK using the Windows WASAPI library. * This software is Copyright (C) 2020 by James C. Ahlstrom, and is * licensed for use under the GNU General Public License (GPL). * See http://www.opensource.org. * Note that there is NO WARRANTY AT ALL. USE AT YOUR OWN RISK!! */ #include #ifdef QUISK_HAVE_WASAPI #define UNICODE #include #include #include #include "quisk.h" #include #include #define INITGUID #define CINTERFACE #define COBJMACROS #include #include #include #include #include #include #include #include #define EXIT_ON_ERROR(hres) \ if (FAILED(hres)) { goto Exit; } // REFERENCE_TIME time units per second and per millisecond #define REFTIMES_PER_SEC 10000000 #define REFTIMES_PER_MILLISEC 10000 #define REFTIMES_PER_MICROSEC 10 #define PKEY_DEVICE_FRIENDLYNAME (&PKEY_Device_FriendlyName) #define CLSID_MMDEVICEENUMERATOR (&CLSID_MMDeviceEnumerator) #define IID_IAUDIOCLIENT (&IID_IAudioClient) #define IID_IAUDIORENDERCLIENT (&IID_IAudioRenderClient) #define IID_IAUDIOCAPTURECLIENT (&IID_IAudioCaptureClient) #define IID_IMMDEVICEENUMERATOR (&IID_IMMDeviceEnumerator) const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003,0x0000,0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} } ; const GUID KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001,0x0000,0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} } ; static IMMDeviceEnumerator *pEnumerator = NULL; struct dev_data_t { IMMDevice *pDevice; IAudioClient *pAudioClient; IAudioCaptureClient *pCaptureClient; IAudioRenderClient *pRenderClient; UINT32 bufferSizeFrames; // size of the Wasapi buffer in frames AUDCLNT_SHAREMODE sharemode; HANDLE hEvent; int playbuf_underrun_reset; // playbuf is used for the play device threadproc int playbuf_underrun_msg; int playbuf_overflow_reset; int playbuf_nFrames; _Atomic int64_t playbuf_nRead; // Total number of frames read from buffer _Atomic int64_t playbuf_nWrite; // Total number of frames written to buffer unsigned char * playbuf_buf; // frames are in native format } ; static LPWSTR to_pwsz(const char *str) { // Convert UTF-8 string to Wide Character string. static wchar_t wchar_buffer[256]; if (MultiByteToWideChar(CP_UTF8, 0, str, -1, wchar_buffer, 256) == 0) return NULL; return wchar_buffer; } static void quisk_reset_audio_device(struct sound_dev * dev) { // Contributed by Ben Cahill, AC2YD, February 2021 struct dev_data_t * DD = dev->device_data; HRESULT hr; hr = IAudioClient_Stop(DD->pAudioClient); if (hr != S_OK) { QuiskPrintf("%s: IAudioClient_Stop() returned hr = 0x%lx!!\n", dev->stream_description, hr); } hr = IAudioClient_Reset(DD->pAudioClient); if (hr != S_OK) { QuiskPrintf("%s: IAudioClient_Reset() returned hr = 0x%lx\n!!", dev->stream_description, hr); } hr = IAudioClient_Start(DD->pAudioClient); if (hr != S_OK) { QuiskPrintf("%s: IAudioClient_Start() returned hr = 0x%lx!!", dev->stream_description, hr); } } static void close_device(struct sound_dev *, const char *); static void open_wasapi_playback(struct sound_dev *, int); static DWORD WINAPI playdevice_threadproc(LPVOID lpParameter) // Called by a special thread. { // Read samples from the playdevice threadproc buffer and write them to the sound card. struct sound_dev * dev = (struct sound_dev *)lpParameter; struct dev_data_t * DD = dev->device_data; DWORD retval; BYTE * pData; BYTE * ptSamples; int64_t nRead, nWrite; int i, two, ch_I, ch_Q, frames_in_buffer, bytes_per_sample, bytes_per_frame, index, frames_to_end; DWORD taskIndex = 0; HANDLE hTask; UINT32 write_frames; UINT32 NumPaddingFrames; if (CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) != S_OK) QuiskPrintf("%s: CoInitializeEx failed\n", dev->stream_description); hTask = AvSetMmThreadCharacteristics(TEXT("Pro Audio"), &taskIndex); if (hTask == NULL) QuiskPrintf("%s: Could not set thread to Pro Audio\n", dev->stream_description); if (FAILED(IAudioClient_GetService(DD->pAudioClient, IID_IAUDIORENDERCLIENT, (void**)&DD->pRenderClient))) { close_device(dev, "Could not create playback client"); return 1; } if (DD->sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) { // Load the buffer with silence before starting the stream. if (SUCCEEDED(IAudioRenderClient_GetBuffer(DD->pRenderClient, DD->bufferSizeFrames, &pData))) IAudioRenderClient_ReleaseBuffer(DD->pRenderClient, DD->bufferSizeFrames, AUDCLNT_BUFFERFLAGS_SILENT); } dev->handle = DD->pRenderClient; if (FAILED(IAudioClient_Start(DD->pAudioClient))) { close_device(dev, "Could not start"); return 1; } while (1) { // Wait for next buffer event to be signaled. retval = WaitForSingleObject(DD->hEvent, 1000); if (retval != WAIT_OBJECT_0) break; if (DD == NULL || dev->handle == NULL) break; if (DD->sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) { write_frames = DD->bufferSizeFrames; } else { if (IAudioClient_GetCurrentPadding(DD->pAudioClient, &NumPaddingFrames) != S_OK) { QuiskPrintf("%s: playdevice: GetCurrentPadding failed\n", dev->stream_description); dev->dev_error++; continue; } write_frames = DD->bufferSizeFrames - NumPaddingFrames; if (write_frames == 0) continue; } if (IAudioRenderClient_GetBuffer(DD->pRenderClient, write_frames, &pData) != S_OK) { QuiskPrintf("%s: playdevice: GetBuffer failed\n", dev->stream_description); dev->dev_error++; continue; } if (quisk_play_state < RECEIVE) { // Shutdown or Starting DD->playbuf_underrun_reset = 0; IAudioRenderClient_ReleaseBuffer(DD->pRenderClient, write_frames, AUDCLNT_BUFFERFLAGS_SILENT); continue; } nRead = atomic_load(&DD->playbuf_nRead); nWrite = atomic_load(&DD->playbuf_nWrite); frames_in_buffer = nWrite - nRead; if (DD->playbuf_underrun_reset) { if (frames_in_buffer >= DD->playbuf_nFrames / 2) { DD->playbuf_underrun_reset = 0; } else { IAudioRenderClient_ReleaseBuffer(DD->pRenderClient, write_frames, AUDCLNT_BUFFERFLAGS_SILENT); continue; } } else if (frames_in_buffer < write_frames) { DD->playbuf_underrun_reset = 1; DD->playbuf_underrun_msg = 1; dev->dev_underrun++; IAudioRenderClient_ReleaseBuffer(DD->pRenderClient, write_frames, AUDCLNT_BUFFERFLAGS_SILENT); continue; } two = dev->num_channels >= 2; ch_I = dev->channel_I; ch_Q = dev->channel_Q; bytes_per_sample = dev->sample_bytes; bytes_per_frame = bytes_per_sample * dev->num_channels; if ((rxMode == CWU || rxMode == CWL) && quiskSpotLevel < 0 && dev->dev_index == t_MicPlayback) { // SoftRock Tx CW from wasapi for (i = 0; i < write_frames; i++) { ptSamples = quisk_make_txIQ(dev, 0); memcpy(pData + ch_I * bytes_per_sample, ptSamples, bytes_per_sample); if (two) { ptSamples += bytes_per_sample; memcpy(pData + ch_Q * bytes_per_sample, ptSamples, bytes_per_sample); } pData += bytes_per_frame; } } else if (quisk_play_state > RECEIVE && quisk_active_sidetone == 1 && dev->dev_index == t_Playback) { // Sidetone from wasapi for (i = 0; i < write_frames; i++) { ptSamples = quisk_make_sidetone(dev, 0); memcpy(pData + ch_I * bytes_per_sample, ptSamples, bytes_per_sample); if (two) memcpy(pData + ch_Q * bytes_per_sample, ptSamples, bytes_per_sample); pData += bytes_per_frame; } } else { // copy sound samples from the buffer to the sound device. index = nRead % DD->playbuf_nFrames; frames_to_end = DD->playbuf_nFrames - index; ptSamples = DD->playbuf_buf + index * bytes_per_frame; if (write_frames <= frames_to_end) { memcpy(pData, ptSamples, write_frames * bytes_per_frame); } else { memcpy(pData, ptSamples, frames_to_end * bytes_per_frame); memcpy(pData + frames_to_end * bytes_per_frame, DD->playbuf_buf, (write_frames - frames_to_end) * bytes_per_frame); } } IAudioRenderClient_ReleaseBuffer(DD->pRenderClient, write_frames, 0); nRead = nRead + write_frames; // update number of frames read atomic_store(&DD->playbuf_nRead, nRead); } AvRevertMmThreadCharacteristics(hTask); if (quisk_sound_state.verbose_sound) QuiskPrintf("%s: Exit playback thread\n", dev->stream_description); CoUninitialize(); return 0; } void quisk_write_wasapi(struct sound_dev * dev, int nSamples, complex double * cSamples, double volume) { // Called from Quisk by the sound thread. Write samples to the playdevice threadproc buffer. // The ring buffer always stores samples as native frames. struct dev_data_t * DD = dev->device_data; int64_t nRead, nWrite; int i, frames_in_buffer, index, bytes_per_frame, num_channels, ch_I, ch_Q; int8_t * ptInt8; int16_t * ptInt16; int32_t * ptInt32; float * ptFloat; int32_t samp32; unsigned char * buffer_end; //QuiskPrintTime("write play buffer", 0); //QuiskMeasureRate("write_wasapi", nSamples, 1, 1); if (quisk_play_state < RECEIVE) return; if (DD == NULL || dev->handle == NULL) return; nRead = atomic_load(&DD->playbuf_nRead); nWrite = atomic_load(&DD->playbuf_nWrite); frames_in_buffer = nWrite - nRead; dev->cr_average_fill += (double)(frames_in_buffer + nSamples / 2) / DD->playbuf_nFrames; dev->cr_average_count++; dev->dev_latency = frames_in_buffer + nSamples; // frames in buffer available to play if (quisk_sound_state.verbose_sound) { if (DD->playbuf_underrun_msg) { DD->playbuf_underrun_msg = 0; QuiskPrintf("%s: playbuf underflow\n", dev->stream_description); } } if (DD->playbuf_overflow_reset) { if (frames_in_buffer <= DD->playbuf_nFrames / 2) { DD->playbuf_overflow_reset = 0; if (quisk_sound_state.verbose_sound) QuiskPrintf("%s: play_buffer overflow recovery frames_in_buffer %d, nSamples %d\n", dev->stream_description, frames_in_buffer, nSamples); } else { //QuiskPrintf("frames_in_buffer %d, nSamples %d\n", frames_in_buffer, nSamples); return; } } if (frames_in_buffer + nSamples >= DD->playbuf_nFrames) { dev->dev_error++; nSamples = DD->playbuf_nFrames * 9 / 10 - frames_in_buffer; // almost fill buffer if (nSamples < 0) { // buffer is nearly full DD->playbuf_overflow_reset = 1; quisk_reset_audio_device(dev); // Ben Cahill: Correct fault in the play callback thread if (quisk_sound_state.verbose_sound) QuiskPrintf("%s: play_buffer overflow and reset, frames_in_buffer %d, nSamples %d\n", dev->stream_description, frames_in_buffer, nSamples); } else if (quisk_sound_state.verbose_sound) { QuiskPrintf("%s: play_buffer overflow, frames_in_buffer %d, nSamples %d\n", dev->stream_description, frames_in_buffer, nSamples); } } if (nSamples <= 0) return; ch_I = dev->channel_I; ch_Q = dev->channel_Q; bytes_per_frame = dev->sample_bytes * dev->num_channels; num_channels = dev->num_channels; index = nWrite % DD->playbuf_nFrames; buffer_end = DD->playbuf_buf + DD->playbuf_nFrames * bytes_per_frame; switch (dev->sound_format) { case Int16: ptInt16 = (int16_t *)(DD->playbuf_buf + index * bytes_per_frame); for (i = 0; i < nSamples; i++) { // for each frame ptInt16[ch_I] = volume * creal(cSamples[i]) / 65536; ptInt16[ch_Q] = volume * cimag(cSamples[i]) / 65536; ptInt16 += num_channels; if ((unsigned char *)ptInt16 >= buffer_end) { ptInt16 = (int16_t *)DD->playbuf_buf; } } break; case Int24: // only works for little-endian ptInt8 = (int8_t *)(DD->playbuf_buf + index * bytes_per_frame); for (i = 0; i < nSamples; i++) { samp32 = volume * creal(cSamples[i]); memcpy(ptInt8 + ch_I * 3, (int8_t *)&samp32 + 1, 3); samp32 = volume * cimag(cSamples[i]); memcpy(ptInt8 + ch_Q * 3, (int8_t *)&samp32 + 1, 3); ptInt8 += bytes_per_frame; if ((unsigned char *)ptInt8 >= buffer_end) { ptInt8 = (int8_t *)DD->playbuf_buf; } } break; case Int32: ptInt32 = (int32_t *)(DD->playbuf_buf + index * bytes_per_frame); for (i = 0; i < nSamples; i++) { ptInt32[ch_I] = volume * creal(cSamples[i]); ptInt32[ch_Q] = volume * cimag(cSamples[i]); ptInt32 += num_channels; if ((unsigned char *)ptInt32 >= buffer_end) { ptInt32 = (int32_t *)DD->playbuf_buf; } } break; case Float32: ptFloat = (float *)(DD->playbuf_buf + index * bytes_per_frame); for (i = 0; i < nSamples; i++) { ptFloat[ch_I] = volume * creal(cSamples[i]) / CLIP32; ptFloat[ch_Q] = volume * cimag(cSamples[i]) / CLIP32; ptFloat += num_channels; if ((unsigned char *)ptFloat >= buffer_end) { ptFloat = (float *)DD->playbuf_buf; } } break; } nWrite = nWrite + nSamples; atomic_store(&DD->playbuf_nWrite, nWrite); } int quisk_read_wasapi(struct sound_dev * dev, complex double * cSamples) { // Called from Quisk by the sound thread. Read samples from the sound card capture buffer. // cSamples can be NULL to discard samples. struct dev_data_t * DD = dev->device_data; int i, bytes_per_frame, num_channels, ch_I, ch_Q; BYTE * pData; UINT32 numFramesAvailable; DWORD flags; double samp_r, samp_i; int8_t * ptInt8; int16_t * ptInt16; int32_t * ptInt32; float * ptFloat32; int32_t iReal, iImag; int nSamples; HRESULT hr; if (DD == NULL || dev->handle == NULL) return 0; nSamples = 0; ch_I = dev->channel_I; ch_Q = dev->channel_Q; bytes_per_frame = dev->sample_bytes * dev->num_channels; num_channels = dev->num_channels; while (1) { // Get the available data hr = IAudioCaptureClient_GetBuffer(DD->pCaptureClient, &pData, &numFramesAvailable, &flags, NULL, NULL); if (hr == AUDCLNT_S_BUFFER_EMPTY) { break; // always use non-blocking read } if (hr != S_OK) { // Error dev->dev_error++; QuiskPrintf("%s: Error processing buffer\n", dev->stream_description); break; } // There is data to read if (cSamples) { if (flags & AUDCLNT_BUFFERFLAGS_SILENT) { for (i = 0; i < numFramesAvailable; i++) cSamples[nSamples++] = 0; } else switch (dev->sound_format) { case Int16: ptInt16 = (int16_t *)pData; for (i = 0; i < numFramesAvailable; i++) { samp_r = ptInt16[ch_I]; samp_i = ptInt16[ch_Q]; cSamples[nSamples++] = (samp_r + I * samp_i) * CLIP16; ptInt16 += num_channels; } break; case Int24: // only works for little-endian ptInt8 = (int8_t *)pData; for (i = 0; i < numFramesAvailable; i++) { iReal = 0; memcpy((int8_t *)&iReal + 1, ptInt8 + ch_I * 3, 3); iImag = 0; memcpy((int8_t *)&iImag + 1, ptInt8 + ch_Q * 3, 3); cSamples[nSamples++] = (iReal + I * iImag); ptInt8 += bytes_per_frame; } break; case Int32: ptInt32 = (int32_t *)pData; for (i = 0; i < numFramesAvailable; i++) { samp_r = ptInt32[ch_I]; samp_i = ptInt32[ch_Q]; cSamples[nSamples++] = (samp_r + I * samp_i); ptInt32 += num_channels; } break; case Float32: ptFloat32 = (float *)pData; for (i = 0; i < numFramesAvailable; i++) { samp_r = ptFloat32[ch_I]; samp_i = ptFloat32[ch_Q]; cSamples[nSamples++] = (samp_r + I * samp_i) * CLIP32; ptFloat32 += num_channels; } break; } } if (FAILED(IAudioCaptureClient_ReleaseBuffer(DD->pCaptureClient, numFramesAvailable))) { dev->dev_error++; QuiskPrintf("%s: Failure to release buffer\n", dev->stream_description); break; } } dev->dev_latency = nSamples; return nSamples; } #if 0 static void MeasureTimerPrecision(void) { DWORD t1, t2; int state = 1; t1 = timeGetTime(); while (state) { switch (state) { case 1: t1 = timeGetTime(); state = 2; break; case 2: t2 = timeGetTime(); if (t1 != t2) { t1 = t2; state = 3; } break; case 3: t2 = timeGetTime(); if (t1 != t2) { QuiskPrintf("MeasureTimerPrecision %d millisec\n", t2 - t1); t1 = t2; state = 4; } break; case 4: t2 = timeGetTime(); if (t1 != t2) { QuiskPrintf("MeasureTimerPrecision %d millisec\n", t2 - t1); t1 = t2; state = 0; } break; } } } #endif void quisk_play_wasapi(struct sound_dev * dev, int nSamples, complex double * cSamples, double volume) { // Called from Quisk by the sound thread. Write samples to the sound card buffer. UINT32 numFramesPadding; BYTE *pData; int8_t * ptb; int16_t * pts; int32_t * ptl; float * ptf; int32_t samp32; struct dev_data_t * DD = dev->device_data; double buffer_fill; int n, frames, bytes_per_frame; if (DD == NULL || dev->handle == NULL) return; if (FAILED(IAudioClient_GetCurrentPadding(DD->pAudioClient, &numFramesPadding))) { QuiskPrintf("%s: quisk_play_wasapi failed to get padding\n", dev->stream_description); return; } buffer_fill = (double)(numFramesPadding + nSamples / 2) / DD->bufferSizeFrames; dev->cr_average_fill += buffer_fill; dev->cr_average_count++; dev->dev_latency = numFramesPadding + nSamples; // frames in buffer available to play switch(dev->started) { case 0: // Starting state; wait for samples to become regular if (quisk_play_state < RECEIVE) { frames = DD->bufferSizeFrames / 2 - (int)numFramesPadding; if (frames > 0 && SUCCEEDED(IAudioRenderClient_GetBuffer(DD->pRenderClient, frames, &pData))) { IAudioRenderClient_ReleaseBuffer(DD->pRenderClient, frames, AUDCLNT_BUFFERFLAGS_SILENT); } return; } dev->started = 1; if (quisk_sound_state.verbose_sound) QuiskPrintf("%s: Starting\n", dev->stream_description); // FALL THRU case 1: // Normal run state // Check for underrun if (numFramesPadding <= dev->sample_rate / 200) { // mimumum play time in buffer quisk_sound_state.underrun_error++; dev->dev_underrun++; if (quisk_sound_state.verbose_sound) QuiskPrintf ("%s: Underrun error\n", dev->stream_description); for (nSamples = 0; nSamples < DD->bufferSizeFrames / 2; nSamples++) cSamples[nSamples] = 0; // fill with silence buffer_fill = 0.5; } // Check if play buffer is too full. if (numFramesPadding + nSamples >= DD->bufferSizeFrames) { quisk_sound_state.write_error++; dev->dev_error++; if (quisk_sound_state.verbose_sound) QuiskPrintf ("%s: Buffer too full\n", dev->stream_description); nSamples = 0; dev->started = 2; } break; case 2: // Buffer is too full; wait for it to drain if (buffer_fill <= 0.5) { dev->started = 1; if (quisk_sound_state.verbose_sound) QuiskPrintf("%s: Resume adding samples\n", dev->stream_description); } else { nSamples = 0; } break; } if (nSamples <= 0) return; if (FAILED(IAudioRenderClient_GetBuffer(DD->pRenderClient, nSamples, &pData))) { QuiskPrintf("%s: quisk_play_wasapi failed get the buffer\n", dev->stream_description); return; } switch (dev->sound_format) { case Int16: pts = (int16_t *)pData; for (n = 0; n < nSamples; n++) { pts[dev->channel_I] = (int16_t)(volume * creal(cSamples[n]) / 65536); pts[dev->channel_Q] = (int16_t)(volume * cimag(cSamples[n]) / 65536); pts += dev->num_channels; } break; case Int32: ptl = (int32_t *)pData; for (n = 0; n < nSamples; n++) { ptl[dev->channel_I] = (int32_t)(volume * creal(cSamples[n])); ptl[dev->channel_Q] = (int32_t)(volume * cimag(cSamples[n])); ptl += dev->num_channels; } break; case Float32: ptf = (float *)pData; for (n = 0; n < nSamples; n++) { ptf[dev->channel_I] = (volume * creal(cSamples[n]) / CLIP32); ptf[dev->channel_Q] = (volume * cimag(cSamples[n]) / CLIP32); ptf += dev->num_channels; } break; case Int24: // only works for little-endian ptb = (int8_t *)pData; bytes_per_frame = dev->sample_bytes * dev->num_channels; for (n = 0; n < nSamples; n++) { samp32 = volume * creal(cSamples[n]); memcpy(ptb + dev->channel_I * 3, (int8_t *)&samp32 + 1, 3); samp32 = volume * cimag(cSamples[n]); memcpy(ptb + dev->channel_Q * 3, (int8_t *)&samp32 + 1, 3); ptb += bytes_per_frame; } break; } IAudioRenderClient_ReleaseBuffer(DD->pRenderClient, nSamples, 0); } static void MakeWFext(int extensible, sound_format_t sound_format, WORD nChannels, struct sound_dev * dev, WAVEFORMATEXTENSIBLE * pwfex) { // fill in a WAVEFORMATEXTENSIBLE structure dev->sound_format = sound_format; switch (sound_format) { case Int16: dev->use_float = 0; dev->sample_bytes = 2; break; case Int24: dev->use_float = 0; dev->sample_bytes = 3; break; case Int32: dev->use_float = 0; dev->sample_bytes = 4; break; case Float32: dev->use_float = 1; dev->sample_bytes = 4; break; } if (extensible) { pwfex->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; pwfex->Format.cbSize = 22; pwfex->Samples.wValidBitsPerSample = dev->sample_bytes * 8; switch (nChannels) { case 1: pwfex->dwChannelMask = 0x01; break; case 2: pwfex->dwChannelMask = 0x03; break; case 3: pwfex->dwChannelMask = 0x07; break; case 4: pwfex->dwChannelMask = 0x0F; break; case 5: pwfex->dwChannelMask = 0x1F; break; case 6: pwfex->dwChannelMask = 0x3F; break; case 7: default: pwfex->dwChannelMask = 0x7F; break; } if (sound_format == Float32) pwfex->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; else pwfex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; } else { pwfex->Format.cbSize = 0; if (sound_format == Float32) pwfex->Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; else pwfex->Format.wFormatTag = WAVE_FORMAT_PCM; } pwfex->Format.nChannels = nChannels; pwfex->Format.nSamplesPerSec = dev->sample_rate; pwfex->Format.nAvgBytesPerSec = nChannels * dev->sample_rate * dev->sample_bytes; pwfex->Format.nBlockAlign = nChannels * dev->sample_bytes; pwfex->Format.wBitsPerSample = dev->sample_bytes * 8; } static int make_format(struct sound_dev * dev, WAVEFORMATEXTENSIBLE * pWaveFormat, AUDCLNT_SHAREMODE sharemode) { struct dev_data_t * DD = dev->device_data; WAVEFORMATEX * pEx = (WAVEFORMATEX *)pWaveFormat; WAVEFORMATEX * pClosestMatch; WORD nChannels; sound_format_t sound_format; HRESULT result; nChannels = dev->num_channels; for (sound_format = 0; sound_format <= Int24; sound_format++) { MakeWFext(1, sound_format, nChannels, dev, pWaveFormat); if (sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) result = IAudioClient_IsFormatSupported(DD->pAudioClient, sharemode, pEx, NULL); else { pClosestMatch = NULL; result = IAudioClient_IsFormatSupported(DD->pAudioClient, sharemode, pEx, &pClosestMatch); CoTaskMemFree(pClosestMatch); } if (result == S_OK) return 1; } if (dev->dev_index == t_Playback || dev->dev_index == t_MicCapture) { // mono device is OK for radio sound or mic for (sound_format = 0; sound_format <= Int24; sound_format++) { MakeWFext(1, sound_format, 1, dev, pWaveFormat); if (sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) result = IAudioClient_IsFormatSupported(DD->pAudioClient, sharemode, pEx, NULL); else { pClosestMatch = NULL; result = IAudioClient_IsFormatSupported(DD->pAudioClient, sharemode, pEx, &pClosestMatch); CoTaskMemFree(pClosestMatch); } if (result == S_OK) { dev->num_channels = 1; dev->channel_I = 0; dev->channel_Q = 0; return 1; } } } // Try to open with more channels for (nChannels = dev->num_channels + 1; nChannels <= 7; nChannels++) { for (sound_format = 0; sound_format <= Int24; sound_format++) { MakeWFext(1, sound_format, nChannels, dev, pWaveFormat); if (sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) result = IAudioClient_IsFormatSupported(DD->pAudioClient, sharemode, pEx, NULL); else { pClosestMatch = NULL; result = IAudioClient_IsFormatSupported(DD->pAudioClient, sharemode, pEx, &pClosestMatch); CoTaskMemFree(pClosestMatch); } if (result == S_OK) { dev->num_channels = nChannels; return 1; } } } return 0; } static void close_device(struct sound_dev * dev, const char * msg) { struct dev_data_t * device_data = dev->device_data; if (msg) { snprintf(dev->dev_errmsg, QUISK_SC_SIZE, "%s: %.80s", msg, dev->name); if (quisk_sound_state.verbose_sound) QuiskPrintf("%s\n", dev->dev_errmsg); } if (device_data->pAudioClient) IAudioClient_Stop(device_data->pAudioClient); if (device_data->pCaptureClient) IAudioCaptureClient_Release(device_data->pCaptureClient); if (device_data->pRenderClient) IAudioRenderClient_Release(device_data->pRenderClient); if (device_data->pAudioClient) IAudioClient_Release(device_data->pAudioClient); if (device_data->pDevice) IMMDevice_Release(device_data->pDevice); if (device_data->playbuf_buf) free(device_data->playbuf_buf); if (device_data->hEvent) CloseHandle(device_data->hEvent); free(device_data); dev->device_data = NULL; } static void open_wasapi_capture(struct sound_dev * dev) { REFERENCE_TIME hnsRequestedDuration; REFERENCE_TIME def_period, min_period; WAVEFORMATEXTENSIBLE wave_format; LPWSTR pwszID; struct dev_data_t * DD = dev->device_data; HRESULT hr; DWORD err_code; dev->dev_errmsg[0] = 0; if (quisk_sound_state.verbose_sound) QuiskPrintf("Opening Wasapi capture device %s\n Name %s\n Device name %s\n", dev->stream_description, dev->name, dev->device_name); if (pEnumerator == NULL) return; pwszID = to_pwsz(dev->device_name); if (pwszID == NULL) { err_code = GetLastError(); snprintf(dev->dev_errmsg, QUISK_SC_SIZE, "Failure 0x%lX to convert device name: %.80s", err_code, dev->device_name); if (quisk_sound_state.verbose_sound) QuiskPrintf("%s\n", dev->dev_errmsg); close_device(dev, NULL); return; } if (FAILED(IMMDeviceEnumerator_GetDevice(pEnumerator, pwszID, &DD->pDevice))) { close_device(dev, "Sound device not found"); return; } if (FAILED(IMMDevice_Activate(DD->pDevice, IID_IAUDIOCLIENT, CLSCTX_ALL, NULL, (void**)&DD->pAudioClient))) { close_device(dev, "No audio client"); return; } if (SUCCEEDED(IAudioClient_GetDevicePeriod(DD->pAudioClient, &def_period, &min_period))) { if (quisk_sound_state.verbose_sound) QuiskPrintf(" Device default period %d, min period %d microsec\n", (int)def_period / REFTIMES_PER_MICROSEC, (int)min_period / REFTIMES_PER_MICROSEC); } if (make_format(dev, &wave_format, AUDCLNT_SHAREMODE_EXCLUSIVE) == 0) { // failure if (make_format(dev, &wave_format, AUDCLNT_SHAREMODE_SHARED) == 0) { // failure close_device(dev, "Device can not support the sample rate or number of channels."); return; } DD->sharemode = AUDCLNT_SHAREMODE_SHARED; if (quisk_sound_state.verbose_sound) QuiskPrintf(" Exclusive mode refused. Open device in Shared mode\n"); } else { DD->sharemode = AUDCLNT_SHAREMODE_EXCLUSIVE; if (quisk_sound_state.verbose_sound) QuiskPrintf(" Open device in Exclusive mode\n"); } if (quisk_sound_state.verbose_sound) { QuiskPrintf(" Sample rate %d\n Channel_I %d\n Channel_Q %d\n", dev->sample_rate, dev->channel_I, dev->channel_Q); QuiskPrintf(" Sound device format %s\n", sound_format_names[dev->sound_format]); QuiskPrintf(" Number of channels %d, bytes per sample %d\n", dev->num_channels, dev->sample_bytes); } hnsRequestedDuration = REFTIMES_PER_MILLISEC * quisk_sound_state.latency_millisecs; hr = IAudioClient_Initialize(DD->pAudioClient, DD->sharemode, 0, hnsRequestedDuration, 0, (WAVEFORMATEX *)&wave_format, NULL); if (FAILED(hr)) { for ( ;hnsRequestedDuration > REFTIMES_PER_MILLISEC * 20; hnsRequestedDuration -= (REFTIMES_PER_MILLISEC * 20)) { hr = IAudioClient_Initialize(DD->pAudioClient, DD->sharemode, 0, hnsRequestedDuration, 0, (WAVEFORMATEX *)&wave_format, NULL); if (hr == S_OK) break; } } if (FAILED(hr)) { close_device(dev, "Could not initialize"); return; } // Get the size of the allocated buffer. if (FAILED(IAudioClient_GetBufferSize(DD->pAudioClient, &DD->bufferSizeFrames))) { close_device(dev, "Could not get buffer size"); return; } dev->play_buf_size = DD->bufferSizeFrames * dev->sample_bytes * dev->num_channels; if (FAILED(IAudioClient_GetService(DD->pAudioClient, IID_IAUDIOCAPTURECLIENT, (void**)&DD->pCaptureClient))) { close_device(dev, "Could not create capture client"); return; } dev->handle = DD->pCaptureClient; if (FAILED(IAudioClient_Start(DD->pAudioClient))) { close_device(dev, "Could not start"); return; } if (quisk_sound_state.verbose_sound) { QuiskPrintf(" Capture Wasapi buffer size %d frames\n Started.\n", DD->bufferSizeFrames); } } static void open_wasapi_playback(struct sound_dev * dev, int use_callback) { REFERENCE_TIME hnsRequestedDuration; REFERENCE_TIME def_period, min_period; WAVEFORMATEXTENSIBLE wave_format; int i; BYTE *pData; LPWSTR pwszID; struct dev_data_t * DD = dev->device_data; HRESULT hr = S_OK; HANDLE hThread = NULL; DWORD ThreadID; DWORD err_code; dev->dev_errmsg[0] = 0; if (quisk_sound_state.verbose_sound) { QuiskPrintf("Opening Wasapi playback device %s\n Name %s\n Device name %s\n", dev->stream_description, dev->name, dev->device_name); } if (pEnumerator == NULL) return; pwszID = to_pwsz(dev->device_name); if (pwszID == NULL) { err_code = GetLastError(); snprintf(dev->dev_errmsg, QUISK_SC_SIZE, "Failure 0x%lX to convert device name: %.80s", err_code, dev->device_name); if (quisk_sound_state.verbose_sound) QuiskPrintf("%s\n", dev->dev_errmsg); close_device(dev, NULL); return; } if (FAILED(IMMDeviceEnumerator_GetDevice(pEnumerator, pwszID, &DD->pDevice))) { close_device(dev, "Sound device not found"); return; } if (FAILED(IMMDevice_Activate(DD->pDevice, IID_IAUDIOCLIENT, CLSCTX_ALL, NULL, (void**)&DD->pAudioClient))) { close_device(dev, "No audio client"); return; } if (SUCCEEDED(IAudioClient_GetDevicePeriod(DD->pAudioClient, &def_period, &min_period))) { if (quisk_sound_state.verbose_sound) QuiskPrintf(" Device default period %d, min period %d microsec\n", (int)def_period / REFTIMES_PER_MICROSEC, (int)min_period / REFTIMES_PER_MICROSEC); } if (make_format(dev, &wave_format, AUDCLNT_SHAREMODE_EXCLUSIVE) == 0) { // failure if (make_format(dev, &wave_format, AUDCLNT_SHAREMODE_SHARED) == 0) { // failure close_device(dev, "Device can not support the sample rate or number of channels."); return; } DD->sharemode = AUDCLNT_SHAREMODE_SHARED; if (quisk_sound_state.verbose_sound) QuiskPrintf(" Exclusive mode refused. Open device in Shared mode\n"); } else { DD->sharemode = AUDCLNT_SHAREMODE_EXCLUSIVE; if (quisk_sound_state.verbose_sound) QuiskPrintf(" Open device in Exclusive mode\n"); } if (quisk_sound_state.verbose_sound) { QuiskPrintf(" Sample rate %d\n Channel_I %d\n Channel_Q %d\n", dev->sample_rate, dev->channel_I, dev->channel_Q); QuiskPrintf(" Sound device format %s\n", sound_format_names[dev->sound_format]); QuiskPrintf(" Number of channels %d, bytes per sample %d\n", dev->num_channels, dev->sample_bytes); //MeasureTimerPrecision(); } if (use_callback) { if (DD->sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) { hnsRequestedDuration = REFTIMES_PER_MICROSEC * quisk_sound_state.data_poll_usec; if (hnsRequestedDuration < min_period) hnsRequestedDuration = min_period; } else { hnsRequestedDuration = 0; } hr = IAudioClient_Initialize(DD->pAudioClient, DD->sharemode, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, hnsRequestedDuration, hnsRequestedDuration, (WAVEFORMATEX *)&wave_format, NULL); if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { if (quisk_sound_state.verbose_sound) QuiskPrintf(" Fix for buffer size not aligned\n"); // Get the size of the aligned buffer. if (FAILED(IAudioClient_GetBufferSize(DD->pAudioClient, &DD->bufferSizeFrames))) { close_device(dev, "Could not get buffer size"); return; } hnsRequestedDuration = (REFERENCE_TIME)((10000.0 * 1000 / wave_format.Format.nSamplesPerSec * DD->bufferSizeFrames) + 0.5); IAudioClient_Release(DD->pAudioClient); if (FAILED(IMMDevice_Activate(DD->pDevice, IID_IAUDIOCLIENT, CLSCTX_ALL, NULL, (void**)&DD->pAudioClient))) { close_device(dev, "No audio client"); return; } hr = IAudioClient_Initialize(DD->pAudioClient, DD->sharemode, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, hnsRequestedDuration, hnsRequestedDuration, (WAVEFORMATEX *)&wave_format, NULL); } if (hr != S_OK) { close_device(dev, "Could not initialize"); return; } // Get the size of the allocated buffer. if (FAILED(IAudioClient_GetBufferSize(DD->pAudioClient, &DD->bufferSizeFrames))) { close_device(dev, "Could not get buffer size"); return; } // Create an event handle and register it for buffer-event notifications. DD->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (DD->hEvent == NULL) { close_device(dev, "CreateEvent failed"); return; } if (FAILED(IAudioClient_SetEventHandle(DD->pAudioClient, DD->hEvent))) { close_device(dev, "SetEventHandle failed"); return; } i = dev->latency_frames * 2 / DD->bufferSizeFrames; // playbuf_nFrames is a multiple of bufferSizeFrames i = ((i + 1) / 2) * 2; // multiple is an even number if ( i < 4) i = 4; DD->playbuf_nFrames = i * DD->bufferSizeFrames; DD->playbuf_buf = calloc(DD->playbuf_nFrames * dev->sample_bytes * dev->num_channels, 1); atomic_store(&DD->playbuf_nWrite, i / 2 * DD->bufferSizeFrames); // buffer starts half full atomic_store(&DD->playbuf_nRead, 0); DD->playbuf_underrun_reset = 0; DD->playbuf_underrun_msg = 0; DD->playbuf_overflow_reset = 0; hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) playdevice_threadproc, dev, CREATE_SUSPENDED, &ThreadID); if (hThread == NULL ) { close_device(dev, "Create thread failed"); return; } dev->play_buf_size = DD->playbuf_nFrames; if (quisk_sound_state.verbose_sound) { QuiskPrintf(" Wasapi buffer size %d frames\n", DD->bufferSizeFrames); QuiskPrintf(" Callback ring buffer size %d frames\n", DD->playbuf_nFrames); QuiskPrintf(" Starting the callback thread for %s\n", dev->stream_description); } ResumeThread(hThread); } else { hnsRequestedDuration = quisk_sound_state.latency_millisecs * REFTIMES_PER_MILLISEC * 2; // reduce this if too large for ( ;hnsRequestedDuration > REFTIMES_PER_MILLISEC * 20; hnsRequestedDuration -= (REFTIMES_PER_MILLISEC * 20)) { hr = IAudioClient_Initialize(DD->pAudioClient, DD->sharemode, 0, hnsRequestedDuration, 0, (WAVEFORMATEX *)&wave_format, NULL); if (hr == S_OK) break; } if (FAILED(hr)) { close_device(dev, "Could not initialize"); return; } // Get the size of the allocated buffer. if (FAILED(IAudioClient_GetBufferSize(DD->pAudioClient, &DD->bufferSizeFrames))) { close_device(dev, "Could not get buffer size"); return; } dev->play_buf_size = DD->bufferSizeFrames; if (FAILED(IAudioClient_GetService(DD->pAudioClient, IID_IAUDIORENDERCLIENT, (void**)&DD->pRenderClient))) { close_device(dev, "Could not create playback client"); return; } // Load the buffer with silence before starting the stream. if (SUCCEEDED(IAudioRenderClient_GetBuffer(DD->pRenderClient, DD->bufferSizeFrames / 2, &pData))) IAudioRenderClient_ReleaseBuffer(DD->pRenderClient, DD->bufferSizeFrames / 2, AUDCLNT_BUFFERFLAGS_SILENT); dev->handle = DD->pRenderClient; if (FAILED(IAudioClient_Start(DD->pAudioClient))) { close_device(dev, "Could not start"); return; } if (quisk_sound_state.verbose_sound) { QuiskPrintf(" Playback Wasapi buffer size %d frames\n", DD->bufferSizeFrames); QuiskPrintf(" Starting playback for %s\n", dev->stream_description); } } } void quisk_start_sound_wasapi (struct sound_dev ** pCapture, struct sound_dev ** pPlayback) { // Open the sound devices and start them. Called from the sound thread. struct sound_dev * pDev; if (CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) != S_OK) QuiskPrintf("CoInitializeEx failed\n"); if (FAILED(CoCreateInstance(CLSID_MMDEVICEENUMERATOR, NULL, CLSCTX_ALL, IID_IMMDEVICEENUMERATOR, (void**)&pEnumerator))) { QuiskPrintf("CoCreateInstance failed in start_sound_wasapi\n"); pEnumerator = NULL; return; } while (*pCapture) { pDev = *pCapture++; if (pDev->driver == DEV_DRIVER_WASAPI) { pDev->device_data = calloc(sizeof(struct dev_data_t), 1); open_wasapi_capture(pDev); } } while (*pPlayback) { pDev = *pPlayback++; if (pDev->driver == DEV_DRIVER_WASAPI) { pDev->device_data = calloc(sizeof(struct dev_data_t), 1); open_wasapi_playback(pDev, 0); } else if (pDev->driver == DEV_DRIVER_WASAPI2) { pDev->device_data = calloc(sizeof(struct dev_data_t), 1); open_wasapi_playback(pDev, 1); } } } void quisk_close_sound_wasapi(struct sound_dev ** pCapture, struct sound_dev ** pPlayback) { struct sound_dev * pDev; while (*pCapture) { pDev = *pCapture++; if (pDev->driver == DEV_DRIVER_WASAPI && pDev->device_data) { pDev->handle = NULL; Sleep(200); close_device(pDev, NULL); if (quisk_sound_state.verbose_sound) QuiskPrintf("Close %s\n", pDev->stream_description); } } while (*pPlayback) { pDev = *pPlayback++; if ((pDev->driver == DEV_DRIVER_WASAPI || pDev->driver == DEV_DRIVER_WASAPI2) && pDev->device_data) { pDev->handle = NULL; Sleep(200); close_device(pDev, NULL); if (quisk_sound_state.verbose_sound) QuiskPrintf("Close %s\n", pDev->stream_description); } } if (pEnumerator) IMMDeviceEnumerator_Release(pEnumerator); pEnumerator = NULL; CoUninitialize(); } PyObject * quisk_wasapi_sound_devices(PyObject * self, PyObject * args) // Called from the GUI thread { // Return a list of sound device data [pycapt, pyplay] PyObject * pylist, * pycapt, * pyplay, * pytup; HRESULT hr = S_OK; IMMDeviceEnumerator *pEnum = NULL; IMMDeviceCollection *pCollection = NULL; IMMDevice *pEndpoint = NULL; IPropertyStore *pProps = NULL; PROPVARIANT varName; LPWSTR pwszID = NULL; UINT i, count; if (!PyArg_ParseTuple (args, "")) return NULL; pylist = PyList_New(0); // list [pycapt, pyplay] pycapt = PyList_New(0); // list of capture devices (name, id, is_raw) pyplay = PyList_New(0); // list of play devices PyList_Append(pylist, pycapt); PyList_Append(pylist, pyplay); hr = CoCreateInstance(CLSID_MMDEVICEENUMERATOR, NULL, CLSCTX_ALL, IID_IMMDEVICEENUMERATOR, (void**)&pEnum); if (FAILED(hr)) { QuiskPrintf("CoCreateInstance failed in wasapi_sound_devices\n"); return pylist; } hr = IMMDeviceEnumerator_EnumAudioEndpoints(pEnum, eRender, DEVICE_STATE_ACTIVE, &pCollection); EXIT_ON_ERROR(hr) hr = IMMDeviceCollection_GetCount(pCollection, &count); EXIT_ON_ERROR(hr) for (i = 0; i < count; i++) { // Get pointer to eRender endpoint number i. hr = IMMDeviceCollection_Item(pCollection, i, &pEndpoint); EXIT_ON_ERROR(hr) hr = IMMDevice_GetId(pEndpoint, &pwszID); EXIT_ON_ERROR(hr) hr = IMMDevice_OpenPropertyStore(pEndpoint, STGM_READ, &pProps); EXIT_ON_ERROR(hr) PropVariantInit(&varName); hr = IPropertyStore_GetValue(pProps, &PKEY_Device_FriendlyName, &varName); EXIT_ON_ERROR(hr) // data items are (name, id, is_raw) pytup = PyTuple_New(3); PyTuple_SetItem(pytup, 0, PyUnicode_FromWideChar(varName.pwszVal, -1)); PyTuple_SetItem(pytup, 1, PyUnicode_FromWideChar(pwszID, -1)); PyTuple_SetItem(pytup, 2, PyInt_FromLong(1)); PyList_Append(pyplay, pytup); CoTaskMemFree(pwszID); pwszID = NULL; PropVariantClear(&varName); IPropertyStore_Release(pProps); pProps = NULL; IMMDevice_Release(pEndpoint); pEndpoint = NULL; } IMMDeviceCollection_Release(pCollection); pCollection = NULL; hr = IMMDeviceEnumerator_EnumAudioEndpoints(pEnum, eCapture, DEVICE_STATE_ACTIVE, &pCollection); EXIT_ON_ERROR(hr) hr = IMMDeviceCollection_GetCount(pCollection, &count); EXIT_ON_ERROR(hr) for (i = 0; i < count; i++) { // Get pointer to eCapture endpoint number i. hr = IMMDeviceCollection_Item(pCollection, i, &pEndpoint); EXIT_ON_ERROR(hr) hr = IMMDevice_GetId(pEndpoint, &pwszID); EXIT_ON_ERROR(hr) hr = IMMDevice_OpenPropertyStore(pEndpoint, STGM_READ, &pProps); EXIT_ON_ERROR(hr) PropVariantInit(&varName); hr = IPropertyStore_GetValue(pProps, &PKEY_Device_FriendlyName, &varName); EXIT_ON_ERROR(hr) // data items are (name, id, is_raw) pytup = PyTuple_New(3); PyTuple_SetItem(pytup, 0, PyUnicode_FromWideChar(varName.pwszVal, -1)); PyTuple_SetItem(pytup, 1, PyUnicode_FromWideChar(pwszID, -1)); PyTuple_SetItem(pytup, 2, PyInt_FromLong(1)); PyList_Append(pycapt, pytup); CoTaskMemFree(pwszID); pwszID = NULL; PropVariantClear(&varName); IPropertyStore_Release(pProps); pProps = NULL; IMMDevice_Release(pEndpoint); pEndpoint = NULL; } IMMDeviceCollection_Release(pCollection); IMMDeviceEnumerator_Release(pEnum); return pylist; Exit: if (pwszID) CoTaskMemFree(pwszID); if (pCollection) IMMDeviceCollection_Release(pCollection); if (pEndpoint) IMMDevice_Release(pEndpoint); if (pProps) IPropertyStore_Release(pProps); if (pEnum) IMMDeviceEnumerator_Release(pEnum); return pylist; } /****** MIDI ******/ static void midi_in_devices(PyObject * pylist) { // Return a list of MIDI In devices. UINT nMidiDeviceNum; MIDIINCAPS caps; UINT i; nMidiDeviceNum = midiInGetNumDevs(); for (i = 0; i < nMidiDeviceNum; ++i) { if (midiInGetDevCaps(i, &caps, sizeof(MIDIINCAPS)) == MMSYSERR_NOERROR) PyList_Append(pylist, PyUnicode_FromWideChar(caps.szPname, -1)); } } #define MIDI_MAX 6000 static char midi_chars[MIDI_MAX]; static int midi_length; static HANDLE MIDI_mutex; static void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) { int status, note, velocity; if (wMsg == MIM_DATA) { status = dwParam1 & 0xFF; note = (dwParam1 >> 8) & 0x7F; velocity = (dwParam1 >> 16) & 0x7F; while (WaitForSingleObject(MIDI_mutex, 0) == WAIT_TIMEOUT) ; if (midi_length <= MIDI_MAX - 3) { midi_chars[midi_length++] = status; midi_chars[midi_length++] = note; midi_chars[midi_length++] = velocity; } ReleaseMutex(MIDI_mutex); if (note == dwInstance) { // dwInstance is the CW note // ignore channel number if ((status & 0xF0) == 0x90) { // Note ON if (velocity) quisk_midi_cwkey = 1; else quisk_midi_cwkey = 0; } else if ((status & 0xF0) == 0x80) { // Note OFF quisk_midi_cwkey = 0; } } } } PyObject * quisk_wasapi_control_midi(PyObject * self, PyObject * args, PyObject * keywds) { /* Call with keyword arguments ONLY */ // midiOutShortMsg static char * kwlist[] = {"client", "device", "close_port", "get_event", "midi_cwkey_note", "get_in_names", "get_in_devices", NULL} ; int client, close_port, get_event, get_in_names, get_in_devices; static int midi_cwkey_note = -1; char * device; PyObject * pylist, * pybytes; static HMIDIIN hMidiDevice = NULL;; client = close_port = get_event = get_in_names = get_in_devices = -1; device = NULL; if (!PyArg_ParseTupleAndKeywords (args, keywds, "|isiiiii", kwlist, &client, &device, &close_port, &get_event, &midi_cwkey_note, &get_in_names, &get_in_devices)) return NULL; if (close_port == 1) { // shutdown if (hMidiDevice) { midiInStop(hMidiDevice); midiInClose(hMidiDevice); } hMidiDevice = NULL; quisk_midi_cwkey = 0; } if (get_in_devices == 1) { // return a list of MIDI devices; just a list of names pylist = PyList_New(0); midi_in_devices(pylist); return pylist; } if (get_in_names == 1) { // return a list of MIDI devices; just a list of names pylist = PyList_New(0); midi_in_devices(pylist); return pylist; } if (client >= 0) { // open client port if ( ! MIDI_mutex) MIDI_mutex = CreateMutex(NULL, FALSE, NULL); quisk_midi_cwkey = 0; if (hMidiDevice == NULL) { if (midiInOpen(&hMidiDevice, client, (DWORD_PTR)(void*)MidiInProc, midi_cwkey_note, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) { QuiskPrintf("Could not open MIDI device\n"); hMidiDevice = NULL; } else { midiInStart(hMidiDevice); if (quisk_sound_state.verbose_sound) QuiskPrintf("Open MIDI device %d\n", client); } } } if (get_event == 1) { // poll to get event if (midi_length != 0) { while (WaitForSingleObject(MIDI_mutex, 0) == WAIT_TIMEOUT) ; pybytes = PyByteArray_FromStringAndSize(midi_chars, midi_length); midi_length = 0; ReleaseMutex(MIDI_mutex); return pybytes; } } Py_INCREF (Py_None); return Py_None; } #else // No Wasapi available #include #include #include "quisk.h" PyObject * quisk_wasapi_sound_devices(PyObject * self, PyObject * args) // Called from the GUI thread { // Return a list of sound device data [pycapt, pyplay] return quisk_dummy_sound_devices(self, args); } void quisk_start_sound_wasapi (struct sound_dev ** pCapture, struct sound_dev ** pPlayback) { struct sound_dev * pDev; const char * msg = "No driver support for this device"; while (*pCapture) { pDev = *pCapture++; if (pDev->driver == DEV_DRIVER_WASAPI) { strMcpy(pDev->dev_errmsg, msg, QUISK_SC_SIZE); if (quisk_sound_state.verbose_sound) QuiskPrintf("%s\n", msg); } } while (*pPlayback) { pDev = *pPlayback++; if (pDev->driver == DEV_DRIVER_WASAPI) { strMcpy(pDev->dev_errmsg, msg, QUISK_SC_SIZE); if (quisk_sound_state.verbose_sound) QuiskPrintf("%s\n", msg); } else if (pDev->driver == DEV_DRIVER_WASAPI2) { strMcpy(pDev->dev_errmsg, msg, QUISK_SC_SIZE); if (quisk_sound_state.verbose_sound) QuiskPrintf("%s\n", msg); } } } void quisk_write_wasapi(struct sound_dev * dev, int nSamples, complex double * cSamples, double volume) {} int quisk_read_wasapi(struct sound_dev * dev, complex double * cSamples) { return 0; } void quisk_play_wasapi(struct sound_dev * dev, int nSamples, complex double * cSamples, double volume) {} void quisk_close_sound_wasapi(struct sound_dev ** pCapture, struct sound_dev ** pPlayback) {} // Note: Return values must be kept up to date! PyObject * quisk_wasapi_control_midi(PyObject * self, PyObject * args, PyObject * keywds) { static char * kwlist[] = {"client", "device", "close_port", "get_event", "midi_cwkey_note", "get_in_names", "get_in_devices", NULL} ; int client, close_port, get_event, get_in_names, get_in_devices; static int midi_cwkey_note = -1; char * device; PyObject * pylist; client = close_port = get_event = get_in_names = get_in_devices = -1; device = NULL; if (!PyArg_ParseTupleAndKeywords (args, keywds, "|isiiiii", kwlist, &client, &device, &close_port, &get_event, &midi_cwkey_note, &get_in_names, &get_in_devices)) return NULL; if (get_in_devices == 1) { // return a list of MIDI devices; just a list of names pylist = PyList_New(0); return pylist; } if (get_in_names == 1) { // return a list of MIDI devices; just a list of names pylist = PyList_New(0); return pylist; } Py_INCREF (Py_None); return Py_None; } #endif