#include #include #include #include "quisk.h" #include "dsound.h" //#include #include //#include //#include // This module provides sound card access using Direct Sound HRESULT errFound, errOpen; extern HWND quisk_mainwin_handle; static GUID IEEE = {0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; static GUID PCMM = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; static PyObject * MakePyUnicode(LPCTSTR txt) { // return a Python Unicode object PyObject * py_unicode; #ifdef UNICODE size_t wstr_size = wcslen(txt); py_unicode = PyUnicode_DecodeUTF16((const char *)txt, wstr_size * sizeof(WCHAR), "replace", NULL); #else int wstr_size = MultiByteToWideChar(CP_ACP, 0, txt, -1, NULL, 0); LPWSTR wstr = (LPWSTR)malloc(sizeof(WCHAR) * (wstr_size + 1)); MultiByteToWideChar(CP_ACP, 0, txt, -1, wstr, wstr_size); py_unicode = PyUnicode_DecodeUTF16((const char *)wstr, wstr_size * sizeof(WCHAR), "replace", NULL); free(wstr); #endif return py_unicode; } static int match_name(LPCTSTR lpszDesc, const char * name) { int found; PyObject * py_unicode; PyObject * py_substring; Py_ssize_t length, index; py_unicode = MakePyUnicode(lpszDesc); if ( ! py_unicode) return 0; py_substring = PyUnicode_DecodeUTF8(name, strlen(name), "replace"); if ( ! py_substring) { Py_DECREF(py_unicode); return 0; } #if PY_MAJOR_VERSION >= 3 if (PyUnicode_READY(py_unicode) == 0) length = PyUnicode_GET_LENGTH(py_unicode); else length = 0; #else length = PyUnicode_GET_SIZE(py_unicode); #endif if (length <= 0) { Py_DECREF(py_unicode); Py_DECREF(py_substring); return 0; } index = PyUnicode_Find(py_unicode, py_substring, 0, length, 1); if (index >= 0) found = 1; else if (index == -1) found = 0; else { // error PyErr_Clear(); found = 0; } Py_DECREF(py_unicode); Py_DECREF(py_substring); return found; } static BOOL CALLBACK DSEnumNames(LPGUID lpGUID, LPCTSTR lpszDesc, LPCTSTR lpszDrvName, LPVOID pyseq) { //char * buf = (char *)malloc(2000); //strcpy (buf, "\xc9vir\xf6n"); //strcat(buf, (char *)lpszDesc); //PyObject * py_string = py_str_utf8((LPCTSTR)buf); PyObject * py_unicode = MakePyUnicode(lpszDesc); PyList_Append((PyObject *)pyseq, py_unicode); //free(buf); Py_DECREF(py_unicode); return TRUE; } static BOOL CALLBACK DsEnumPlay(LPGUID lpGUID, LPCTSTR lpszDesc, LPCTSTR lpszDrvName, LPVOID dev) { // Open the play device if the name is found in the description LPDIRECTSOUND8 DsDev; if (match_name(lpszDesc, ((struct sound_dev *)dev)->name)) { errFound = DS_OK; errOpen = DirectSoundCreate8(lpGUID, &DsDev, NULL); if (errOpen == DS_OK) { ((struct sound_dev *)dev)->handle = DsDev; } return FALSE; // Stop iteration } else { return TRUE; } } static BOOL CALLBACK DsEnumCapture(LPGUID lpGUID, LPCTSTR lpszDesc, LPCTSTR lpszDrvName, LPVOID dev) { // Open the capture device if the name is found in the description LPDIRECTSOUNDCAPTURE8 DsDev; //char * buf = (char *)malloc(2000); //strcpy (buf, "\xc9vir\xf6n"); //strcat(buf, (char *)lpszDesc); //PyObject * py_string = py_str_utf8((LPCTSTR)buf); if (match_name(lpszDesc, ((struct sound_dev *)dev)->name)) { errFound = DS_OK; errOpen = DirectSoundCaptureCreate8(lpGUID, &DsDev, NULL); if (errOpen == DS_OK) ((struct sound_dev *)dev)->handle = DsDev; return FALSE; // Stop iteration } else { return TRUE; } } static void MakeWFext(int use_new, int use_float, struct sound_dev * dev, WAVEFORMATEXTENSIBLE * pwfex) { // fill in a WAVEFORMATEXTENSIBLE structure if (use_float) dev->sample_bytes = 4; if (use_new) { pwfex->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; pwfex->Format.cbSize = 22; pwfex->Samples.wValidBitsPerSample = dev->sample_bytes * 8; if (dev->num_channels == 1) pwfex->dwChannelMask = SPEAKER_FRONT_LEFT; else pwfex->dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; if (use_float) { pwfex->SubFormat = IEEE; dev->use_float = 1; } else { pwfex->SubFormat = PCMM; dev->use_float = 0; } } else { pwfex->Format.cbSize = 0; if (use_float) { pwfex->Format.wFormatTag = 0x03; //WAVE_FORMAT_IEEE; dev->use_float = 1; } else { pwfex->Format.wFormatTag = WAVE_FORMAT_PCM; dev->use_float = 0; } } pwfex->Format.nChannels = dev->num_channels; pwfex->Format.nSamplesPerSec = dev->sample_rate; pwfex->Format.nAvgBytesPerSec = dev->num_channels * dev->sample_rate * dev->sample_bytes; dev->play_buf_size = pwfex->Format.nAvgBytesPerSec; pwfex->Format.nBlockAlign = dev->num_channels * dev->sample_bytes; pwfex->Format.wBitsPerSample = dev->sample_bytes * 8; } static int quisk_open_capture(struct sound_dev * dev) { // Open the soundcard for capture. Return non-zero for error. LPDIRECTSOUNDCAPTUREBUFFER ptBuf; DSCBUFFERDESC dscbd; HRESULT hr; WAVEFORMATEXTENSIBLE wfex; dev->handle = NULL; dev->buffer = NULL; dev->started = 0; dev->dataPos = 0; dev->portaudio_index = -1; if ( ! dev->name[0]) // Check for null play name; not an error return 0; errFound = ~DS_OK; DirectSoundCaptureEnumerate((LPDSENUMCALLBACK)DsEnumCapture, dev); if (errFound != DS_OK) { snprintf (quisk_sound_state.err_msg, SC_SIZE, "DirectSound capture device name %s not found", dev->name); return 1; } if (errOpen != DS_OK) { snprintf (quisk_sound_state.err_msg, SC_SIZE, "DirectSound capture device %s open failed", dev->name); return 1; } dev->sample_bytes = 4; MakeWFext (1, 0, dev, &wfex); // fill in wfex memset(&dscbd, 0, sizeof(DSCBUFFERDESC)); dscbd.dwSize = sizeof(DSCBUFFERDESC); dscbd.dwFlags = 0; dscbd.dwBufferBytes = dev->play_buf_size; // one second buffer dscbd.lpwfxFormat = (WAVEFORMATEX *)&wfex; hr = IDirectSoundCapture_CreateCaptureBuffer( (LPDIRECTSOUNDCAPTURE8)dev->handle, &dscbd, &ptBuf, NULL); if (hr == DS_OK) { dev->buffer = ptBuf; } else { snprintf (quisk_sound_state.err_msg, SC_SIZE, "DirectSound capture device %s buffer create failed (0x%lX)", dev->name, hr); return 1; } ptBuf = (LPDIRECTSOUNDCAPTUREBUFFER)dev->buffer; hr = IDirectSoundCaptureBuffer8_Start(ptBuf, DSCBSTART_LOOPING); if (hr != DS_OK) { #if DEBUG_IO printf("Capture start error 0x%lX", hr); #endif snprintf (quisk_sound_state.err_msg, SC_SIZE, "DirectSound capture device %s capture start failed", dev->name); return 1; } #if DEBUG_IO printf("Created capture buffer size %d bytes for device %s, descr %s\n", dev->play_buf_size, dev->name, dev->stream_description); #endif return 0; } static int quisk_open_playback(struct sound_dev * dev) { // Open the soundcard for playback. Return non-zero for error. LPDIRECTSOUNDBUFFER ptBuf; WAVEFORMATEXTENSIBLE wfex; DSBUFFERDESC dsbdesc; HRESULT hr; dev->handle = NULL; dev->buffer = NULL; dev->started = 0; dev->oldPlayPos = 0; dev->play_delay = 0; dev->dataPos = 0; dev->portaudio_index = -1; dev->sample_bytes = 2; if ( ! dev->name[0]) // Check for null play name; not an error return 0; errFound = ~DS_OK; DirectSoundEnumerate((LPDSENUMCALLBACK)DsEnumPlay, dev); if (errFound != DS_OK) { snprintf (quisk_sound_state.err_msg, SC_SIZE, "DirectSound play device name %s not found", dev->name); return 1; } if (errOpen != DS_OK) { snprintf (quisk_sound_state.err_msg, SC_SIZE, "DirectSound play device %s open failed", dev->name); return 1; } hr = IDirectSound_SetCooperativeLevel ((LPDIRECTSOUND8)dev->handle, quisk_mainwin_handle, DSSCL_PRIORITY); if (hr != DS_OK) { snprintf (quisk_sound_state.err_msg, SC_SIZE, "DirectSound play device %s cooperative level failed", dev->name); return 1; } dev->sample_bytes = 4; MakeWFext (1, 0, dev, &wfex); // fill in wfex memset(&dsbdesc, 0, sizeof(DSBUFFERDESC)); dsbdesc.dwSize = sizeof(DSBUFFERDESC); dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2|DSBCAPS_GLOBALFOCUS; dsbdesc.dwBufferBytes = dev->play_buf_size; // one second buffer dsbdesc.lpwfxFormat = (LPWAVEFORMATEX)&wfex; hr = IDirectSound_CreateSoundBuffer( (LPDIRECTSOUND8)dev->handle, &dsbdesc, &ptBuf, NULL); if (hr == DS_OK) { dev->buffer = ptBuf; } else { snprintf (quisk_sound_state.err_msg, SC_SIZE, "DirectSound play device %s buffer create failed (0x%X)", dev->name, (unsigned int)hr); return 1; } #if DEBUG_IO printf("Created play buffer size %d bytes for device %s, descr %s\n", dev->play_buf_size, dev->name, dev->stream_description); #endif return 0; } PyObject * quisk_sound_devices(PyObject * self, PyObject * args) { // Return a list of DirectSound device names PyObject * pylist, * pycapt, * pyplay; if (!PyArg_ParseTuple (args, "")) return NULL; // Each pycapt and pyplay is a device name pylist = PyList_New(0); // list [pycapt, pyplay] pycapt = PyList_New(0); // list of capture devices pyplay = PyList_New(0); // list of play devices PyList_Append(pylist, pycapt); PyList_Append(pylist, pyplay); DirectSoundCaptureEnumerate((LPDSENUMCALLBACK)DSEnumNames, pycapt); DirectSoundEnumerate((LPDSENUMCALLBACK)DSEnumNames, pyplay); return pylist; } void quisk_start_sound_alsa (struct sound_dev ** pCapture, struct sound_dev ** pPlayback) { struct sound_dev * pDev; if (quisk_sound_state.err_msg[0]) return; // prior error // DirectX must open the playback device before the (same) capture device while (1) { pDev = *pPlayback++; if ( ! pDev) break; if (quisk_open_playback(pDev)) return; // error } while (1) { pDev = *pCapture++; if ( ! pDev) break; if (quisk_open_capture(pDev)) return; // error } } void quisk_close_sound_alsa(struct sound_dev ** pCapture, struct sound_dev ** pPlayback) { struct sound_dev * pDev; while (*pPlayback) { pDev = *pPlayback; if (pDev->buffer) IDirectSoundBuffer8_Stop((LPDIRECTSOUNDBUFFER)pDev->buffer); pDev->handle = NULL; pPlayback++; } while (*pCapture) { pDev = *pCapture; if (pDev->buffer) IDirectSoundCaptureBuffer_Stop((LPDIRECTSOUNDCAPTUREBUFFER)pDev->buffer); pDev->handle = NULL; pCapture++; } } int quisk_read_alsa(struct sound_dev * dev, complex double * cSamples) { // cSamples can be NULL to discard samples LPDIRECTSOUNDCAPTUREBUFFER ptBuf = (LPDIRECTSOUNDCAPTUREBUFFER)dev->buffer; HRESULT hr; DWORD readPos, captPos; LPVOID pt1, pt2; DWORD i, n1, n2; short si, sq, * pts; float fi, fq, * ptf; int li, lq, * ptl; // int must be 32 bits int ii, qq, nSamples; int bytes, frames, poll_size, millisecs, bytes_per_frame; if ( ! dev->handle || ! dev->buffer) return 0; bytes_per_frame = dev->num_channels * dev->sample_bytes; hr = IDirectSoundCaptureBuffer8_GetCurrentPosition(ptBuf, &captPos, &readPos); if (hr != DS_OK) { #if DEBUG_IO printf ("Get CurrentPosition error 0x%lX\n", hr); #endif dev->dev_error++; return 0; } // printf("dataPos %d\n", dev->dataPos); if ( ! dev->started) { dev->started = 1; dev->dataPos = readPos; } if (readPos >= dev->dataPos) bytes = readPos - dev->dataPos; else bytes = readPos - dev->dataPos + dev->play_buf_size; frames = bytes / bytes_per_frame; // frames available to read poll_size = dev->read_frames; millisecs = (poll_size - frames) * 1000 / dev->sample_rate; // time to read remaining poll size if (millisecs > 0) { // wait for additional frames #if DEBUG_IO > 2 printf ("Wait %d millisecs for more samples\n", millisecs); #endif Sleep(millisecs); hr = IDirectSoundCaptureBuffer8_GetCurrentPosition(ptBuf, &captPos, &readPos); if (hr != DS_OK) { #if DEBUG_IO printf ("Get CurrentPosition two error 0x%lX\n", hr); #endif dev->dev_error++; return 0; } if (readPos >= dev->dataPos) bytes = readPos - dev->dataPos; else bytes = readPos - dev->dataPos + dev->play_buf_size; } frames = bytes / bytes_per_frame; // frames available to read dev->dev_latency = frames; bytes = frames * bytes_per_frame; // round to frames if ( ! bytes) { return 0; } i = poll_size * bytes_per_frame * 4; // Limit size of read if (i > 0 && bytes > i) { // zero poll_size is allowed bytes = i; frames = bytes / bytes_per_frame; } if (IDirectSoundCaptureBuffer8_Lock(ptBuf, dev->dataPos, bytes, &pt1, &n1, &pt2, &n2, 0) != DS_OK) { dev->dev_error++; #if DEBUG_IO printf ("DirecctX capture lock error bytes %d\n", bytes); #endif return 0; } //printf ("%d %d %d %d\n", dev->channel_I, dev->channel_Q, bytes_per_frame, dev->num_channels); #if DEBUG_IO printf("%s read %4d bytes %4d frames from %9d to (%9lu %9lu) diff %9lu\n", dev->name, bytes, frames, dev->dataPos, readPos, captPos, captPos - readPos); #endif #if DEBUG_IO if (bytes != n1 + n2) printf ("Lock not equal to bytes\n"); #endif dev->dataPos += bytes; dev->dataPos = dev->dataPos % dev->play_buf_size; nSamples = 0; switch (dev->sample_bytes + dev->use_float) { case 2: pts = (short *)pt1; frames = (n1 + n2) / bytes_per_frame; bytes = 0; while (frames) { si = pts[dev->channel_I]; sq = pts[dev->channel_Q]; pts += dev->num_channels; if (si >= CLIP16 || si <= -CLIP16) dev->overrange++; // assume overrange returns max int if (sq >= CLIP16 || sq <= -CLIP16) dev->overrange++; ii = si << 16; qq = sq << 16; if (nSamples < SAMP_BUFFER_SIZE * 8 / 10) { if (cSamples) cSamples[nSamples] = ii + I * qq; nSamples++; } bytes += bytes_per_frame; frames--; if (bytes == n1) pts = (short *)pt2; } break; case 4: ptl = (int *)pt1; frames = (n1 + n2) / bytes_per_frame; bytes = 0; while (frames) { li = ptl[dev->channel_I]; lq = ptl[dev->channel_Q]; ptl += dev->num_channels; if (li >= CLIP32 || li <= -CLIP32) dev->overrange++; // assume overrange returns max int if (lq >= CLIP32 || lq <= -CLIP32) dev->overrange++; if (nSamples < SAMP_BUFFER_SIZE * 8 / 10) { if (cSamples) cSamples[nSamples] = li + I * lq; nSamples++; } bytes += bytes_per_frame; frames--; if (bytes == n1) ptl = (int *)pt2; } break; case 5: // use IEEE float ptf = (float *)pt1; frames = (n1 + n2) / bytes_per_frame; bytes = 0; while (frames) { fi = ptf[dev->channel_I]; fq = ptf[dev->channel_Q]; ptf += dev->num_channels; if (fabsf(fi) >= 1.0 || fabsf(fq) >= 1.0) dev->overrange++; // assume overrange returns maximum if (nSamples < SAMP_BUFFER_SIZE * 8 / 10) { if (cSamples) cSamples[nSamples] = (fi + I * fq) * 16777215; nSamples++; } bytes += bytes_per_frame; frames--; if (bytes == n1) { ptf = (float *)pt2; } } break; } IDirectSoundCaptureBuffer8_Unlock(ptBuf, pt1, n1, pt2, n2); return nSamples; } void quisk_play_alsa(struct sound_dev * dev, int nSamples, complex double * cSamples, int report_latency, double volume) { LPDIRECTSOUNDBUFFER ptBuf = (LPDIRECTSOUNDBUFFER)dev->buffer; DWORD playPos, writePos; // hardware index into buffer LPVOID pt1, pt2; DWORD n1, n2; short * pts; float * ptf; int * ptl; // int must be 32 bits int n, unavail, count, frames, bytes, pass, bytes_per_frame; if ( ! dev->handle || ! dev->buffer) return; bytes_per_frame = dev->num_channels * dev->sample_bytes; // Note: writePos moves ahead with playPos; it is not associated with write activity if (IDirectSoundBuffer8_GetCurrentPosition(ptBuf, &playPos, &writePos) != DS_OK) { #if DEBUG_IO printf ("Bad GetCurrentPosition\n"); #endif quisk_sound_state.write_error++; dev->dev_error++; playPos = writePos = 0; } unavail = (int)writePos - (int)playPos; // Must not write to this region if (unavail < 0) unavail += dev->play_buf_size; count = (int)playPos - dev->oldPlayPos; // number of bytes played if (count < 0) count += dev->play_buf_size; // assume no wrap-around beyond play_buf_size dev->oldPlayPos = playPos; dev->play_delay -= count; // bytes in buffer available to play dev->dev_latency = dev->play_delay / bytes_per_frame; if (report_latency) // Report latency for main playback device quisk_sound_state.latencyPlay = dev->dev_latency; #if DEBUG_IO if (nSamples || count) printf ("DirectX playPos %6d writePos %6d no-write %6d dev->dev_latency %6d data_pos %6d samples %6d\n", (int)playPos, (int)writePos, unavail, dev->dev_latency, dev->dataPos, nSamples); #endif switch(dev->started) { case 0: // Starting state; wait for buffer to fill before starting play if (dev->dev_latency + nSamples >= dev->latency_frames) { IDirectSoundBuffer8_Play (ptBuf, 0, 0, DSBPLAY_LOOPING); dev->started = 1; #if DEBUG_IO printf ("Start DirectX play at dev->latency_frames %d\n", dev->latency_frames); #endif } break; case 1: // Normal run state // Measure the space available to write samples frames = (dev->play_buf_size - dev->play_delay - unavail) / bytes_per_frame; // Check for underrun n = unavail / bytes_per_frame + dev->latency_frames * 2 / 10 - nSamples; // minimum frames if (dev->dev_latency < n) { quisk_sound_state.underrun_error++; dev->dev_underrun++; n += dev->latency_frames * 2 / 10; while (n-- > 0) cSamples[nSamples++] = 0; // add zero samples #if DEBUG_IO printf ("Underrun error, frames %d\n", dev->dev_latency); #endif } // Check if play buffer is too full else if (dev->dev_latency > dev->latency_frames * 18 / 10 || nSamples >= frames) { quisk_sound_state.write_error++; dev->dev_error++; nSamples = 0; dev->started = 2; #if DEBUG_IO printf("Discard %d samples\n", nSamples); #endif } break; case 2: // Buffer is too full; wait for it to drain nSamples = 0; if (dev->dev_latency <= dev->latency_frames) { dev->started = 1; #if DEBUG_IO printf("Resume adding samples\n"); #endif } break; } bytes = nSamples * bytes_per_frame; if (bytes <= 0) return; // write our data bytes at our data position dataPos if (IDirectSoundBuffer8_Lock(ptBuf, dev->dataPos, bytes, &pt1, &n1, &pt2, &n2, 0) != DS_OK) { #if DEBUG_IO printf ("DirectX play lock error\n"); #endif quisk_sound_state.write_error++; dev->dev_error++; return; } dev->dataPos += bytes; // update data write position dev->dataPos = dev->dataPos % dev->play_buf_size; dev->play_delay += bytes; // bytes available to play pass = 0; n = 0; switch (dev->sample_bytes + dev->use_float) { case 2: pts = (short *)pt1; // Start writing at pt1 frames = n1 / bytes_per_frame; for (n = 0; n < nSamples && pass < 2; n++) { pts[dev->channel_I] = (short)(volume * creal(cSamples[n]) / 65536); pts[dev->channel_Q] = (short)(volume * cimag(cSamples[n]) / 65536); pts += dev->num_channels; if (--frames <= 0) { pass++; // change to pt2 pts = (short *)pt2; frames = n2 / bytes_per_frame; } } break; case 4: ptl = (int *)pt1; // Start writing at pt1 frames = n1 / bytes_per_frame; for (n = 0; n < nSamples && pass < 2; n++) { ptl[dev->channel_I] = (int)(volume * creal(cSamples[n])); ptl[dev->channel_Q] = (int)(volume * cimag(cSamples[n])); ptl += dev->num_channels; if (--frames <= 0) { pass++; // change to pt2 ptl = (int *)pt2; frames = n2 / bytes_per_frame; } } break; case 5: // use IEEE float ptf = (float *)pt1; // Start writing at pt1 frames = n1 / bytes_per_frame; for (n = 0; n < nSamples && pass < 2; n++) { ptf[dev->channel_I] = (volume * creal(cSamples[n]) / CLIP32); ptf[dev->channel_Q] = (volume * cimag(cSamples[n]) / CLIP32); ptf += dev->num_channels; if (--frames <= 0) { pass++; // change to pt2 ptf = (float *)pt2; frames = n2 / bytes_per_frame; } } break; } IDirectSoundBuffer8_Unlock(ptBuf, pt1, n1, pt2, n2); } void quisk_play_portaudio(struct sound_dev * dev, int j, complex double * samp, int i, double volume) { } void quisk_start_sound_portaudio(struct sound_dev ** pCapture, struct sound_dev ** pPlayback) { } void quisk_close_sound_portaudio(void) { } int quisk_read_portaudio(struct sound_dev * dev, complex double * samp) { return 0; } int quisk_read_pulseaudio(struct sound_dev * dev, complex double * samp) { return 0; } void quisk_play_pulseaudio(struct sound_dev * dev, int j, complex double * samp, int i, double volume) { } void quisk_start_sound_pulseaudio(struct sound_dev ** pCapture, struct sound_dev ** pPlayback) { } void quisk_close_sound_pulseaudio() { } void quisk_mixer_set(char * card_name, int numid, PyObject * value, char * err_msg, int err_size) { err_msg[0] = 0; } PyObject * quisk_pa_sound_devices(PyObject * self, PyObject * args) { // Return a list of PulseAudio device names [pycapt, pyplay] PyObject * pylist, * pycapt, * pyplay; if (!PyArg_ParseTuple (args, "")) return NULL; pylist = PyList_New(0); // list [pycapt, pyplay] pycapt = PyList_New(0); // list of capture devices pyplay = PyList_New(0); // list of play devices PyList_Append(pylist, pycapt); PyList_Append(pylist, pyplay); return pylist; }