f844e846b7
either libsamplerate or the SoX resampler library installed. If not installed, the data is only copied to the output buffer and sample rate of the output driver is changed similar to legacy code. Related changes: - Added check for libsamplerate or SoX resampler header files in configure script. - Added functions for converting source format to float (requied by resampler) and float to output format. - Added support for float type data in the audio buffer code. Buffer chain #0 receives float data from sendwavepacket() and buffer #1 receives data for mixing and output in format "16-bit signed little endian stereo". - ALSA: Disable builtin resampling feature if resampler is present to avoid doing it twice.
511 lines
14 KiB
C++
511 lines
14 KiB
C++
/////////////////////////////////////////////////////////////////////////
|
|
// $Id$
|
|
/////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (C) 2001-2017 The Bochs Project
|
|
//
|
|
// This library is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Lesser General Public
|
|
// License as published by the Free Software Foundation; either
|
|
// version 2 of the License, or (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
// This file (SOUNDWIN.CC) written and donated by Josef Drexler
|
|
|
|
// Define BX_PLUGGABLE in files that can be compiled into plugins. For
|
|
// platforms that require a special tag on exported symbols, BX_PLUGGABLE
|
|
// is used to know when we are exporting symbols and when we are importing.
|
|
#define BX_PLUGGABLE
|
|
|
|
#include "iodev.h"
|
|
#include "soundlow.h"
|
|
#include "soundwin.h"
|
|
|
|
#if BX_HAVE_SOUND_WIN && BX_SUPPORT_SOUNDLOW
|
|
|
|
#define LOG_THIS
|
|
|
|
#define SOUNDWIN_PACKETS_PER_SEC 20
|
|
|
|
// size is the total size of the midi header and buffer and the
|
|
// wave header and buffer, all aligned on a 16-byte boundary
|
|
|
|
#define ALIGN(size) ((size + 15) & ~15)
|
|
|
|
#define size ALIGN(sizeof(MIDIHDR)) \
|
|
+ ALIGN(sizeof(WAVEHDR)) * 2 \
|
|
+ ALIGN(BX_SOUND_WINDOWS_MAXSYSEXLEN) \
|
|
+ ALIGN(BX_SOUNDLOW_WAVEPACKETSIZE + 64)
|
|
|
|
// some data for the wave buffers
|
|
HANDLE DataHandle; // returned by GlobalAlloc()
|
|
Bit8u *DataPointer; // returned by GlobalLock()
|
|
|
|
// helper function
|
|
Bit8u* newbuffer(unsigned blksize)
|
|
{
|
|
static unsigned offset = 0;
|
|
Bit8u *ptr;
|
|
|
|
ptr = &(DataPointer[offset]);
|
|
if ((offset + ALIGN(blksize)) > size) {
|
|
return NULL;
|
|
} else {
|
|
offset += ALIGN(blksize);
|
|
return ptr;
|
|
}
|
|
}
|
|
|
|
// bx_soundlow_waveout_win_c class implemenzation
|
|
|
|
bx_soundlow_waveout_win_c::bx_soundlow_waveout_win_c()
|
|
:bx_soundlow_waveout_c()
|
|
{
|
|
WaveOutOpen = 0;
|
|
WaveOutHdr = (LPWAVEHDR) newbuffer(sizeof(WAVEHDR));
|
|
if (WaveOutHdr == NULL)
|
|
BX_PANIC(("Allocated memory was too small!"));
|
|
}
|
|
|
|
bx_soundlow_waveout_win_c::~bx_soundlow_waveout_win_c()
|
|
{
|
|
if (WaveOutOpen == 1) {
|
|
waveOutReset(hWaveOut);
|
|
waveOutClose(hWaveOut);
|
|
}
|
|
}
|
|
|
|
int bx_soundlow_waveout_win_c::openwaveoutput(const char *wavedev)
|
|
{
|
|
// could make the output device selectable,
|
|
// but currently only the wave mapper is supported
|
|
UNUSED(wavedev);
|
|
|
|
BX_DEBUG(("openwaveoutput(%s)", wavedev));
|
|
|
|
WaveDevice = (UINT) WAVEMAPPER;
|
|
|
|
set_pcm_params(&real_pcm_param);
|
|
pcm_callback_id = register_wave_callback(this, pcm_callback);
|
|
start_resampler_thread();
|
|
start_mixer_thread();
|
|
return BX_SOUNDLOW_OK;
|
|
}
|
|
|
|
int bx_soundlow_waveout_win_c::set_pcm_params(bx_pcm_param_t *param)
|
|
{
|
|
UINT ret;
|
|
PCMWAVEFORMAT waveformat;
|
|
|
|
BX_DEBUG(("set_pcm_params(): %u, %u, %u, %02x", param->samplerate, param->bits,
|
|
param->channels, param->format));
|
|
if (WaveOutOpen != 0) {
|
|
ret = waveOutReset(hWaveOut);
|
|
ret = waveOutClose(hWaveOut);
|
|
WaveOutOpen = 0;
|
|
}
|
|
|
|
// try three times to find a suitable format
|
|
for (int tries = 0; tries < 3; tries++) {
|
|
int frequency = real_pcm_param.samplerate;
|
|
bx_bool stereo = real_pcm_param.channels == 2;
|
|
int bits = real_pcm_param.bits;
|
|
int bps = (bits / 8) * (stereo + 1);
|
|
|
|
waveformat.wf.wFormatTag = WAVE_FORMAT_PCM;
|
|
waveformat.wf.nChannels = stereo + 1;
|
|
waveformat.wf.nSamplesPerSec = frequency;
|
|
waveformat.wf.nAvgBytesPerSec = frequency * bps;
|
|
waveformat.wf.nBlockAlign = bps;
|
|
waveformat.wBitsPerSample = bits;
|
|
|
|
ret = waveOutOpen(&(hWaveOut), WaveDevice, (LPWAVEFORMATEX)&(waveformat.wf), 0, 0, CALLBACK_NULL);
|
|
if (ret != 0) {
|
|
char errormsg[4*MAXERRORLENGTH+1];
|
|
waveOutGetErrorTextA(ret, errormsg, 4*MAXERRORLENGTH+1);
|
|
BX_DEBUG(("waveOutOpen: %s", errormsg));
|
|
switch (tries) {
|
|
case 0: // maybe try a different frequency
|
|
if (frequency < 15600)
|
|
frequency = 11025;
|
|
else if (frequency < 31200)
|
|
frequency = 22050;
|
|
else
|
|
frequency = 44100;
|
|
|
|
BX_DEBUG(("Couldn't open wave device (error %d), trying frequency %d", ret, frequency));
|
|
break;
|
|
|
|
case 1: // or something else
|
|
frequency = 11025;
|
|
stereo = 0;
|
|
bits = 8;
|
|
bps = 1;
|
|
|
|
BX_DEBUG(("Couldn't open wave device again (error %d), trying 11KHz, mono, 8bit", ret));
|
|
break;
|
|
|
|
case 2: // nope, doesn't work
|
|
BX_ERROR(("Couldn't open wave output device (error = %d)!", ret));
|
|
return BX_SOUNDLOW_ERR;
|
|
}
|
|
|
|
BX_DEBUG(("The format was: wFormatTag=%d, nChannels=%d, nSamplesPerSec=%d,",
|
|
waveformat.wf.wFormatTag, waveformat.wf.nChannels, waveformat.wf.nSamplesPerSec));
|
|
BX_DEBUG((" nAvgBytesPerSec=%d, nBlockAlign=%d, wBitsPerSample=%d",
|
|
waveformat.wf.nAvgBytesPerSec, waveformat.wf.nBlockAlign, waveformat.wBitsPerSample));
|
|
} else {
|
|
WaveOutOpen = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return BX_SOUNDLOW_OK;
|
|
}
|
|
|
|
int bx_soundlow_waveout_win_c::get_packetsize()
|
|
{
|
|
return (real_pcm_param.samplerate * 4 / SOUNDWIN_PACKETS_PER_SEC);
|
|
}
|
|
|
|
int bx_soundlow_waveout_win_c::output(int length, Bit8u data[])
|
|
{
|
|
UINT ret;
|
|
|
|
// prepare the wave header
|
|
WaveOutHdr->lpData = (LPSTR)data;
|
|
WaveOutHdr->dwBufferLength = length;
|
|
WaveOutHdr->dwBytesRecorded = length;
|
|
WaveOutHdr->dwUser = 0;
|
|
WaveOutHdr->dwFlags = 0;
|
|
WaveOutHdr->dwLoops = 1;
|
|
|
|
ret = waveOutPrepareHeader(hWaveOut, WaveOutHdr, sizeof(*WaveOutHdr));
|
|
if (ret != 0) {
|
|
BX_ERROR(("waveOutPrepareHeader(): error = %d", ret));
|
|
return BX_SOUNDLOW_ERR;
|
|
}
|
|
|
|
ret = waveOutWrite(hWaveOut, WaveOutHdr, sizeof(*WaveOutHdr));
|
|
if (ret != 0) {
|
|
char errormsg[4*MAXERRORLENGTH+1];
|
|
waveOutGetErrorTextA(ret, errormsg, 4*MAXERRORLENGTH+1);
|
|
BX_ERROR(("waveOutWrite(): %s", errormsg));
|
|
}
|
|
Sleep(1000 / SOUNDWIN_PACKETS_PER_SEC);
|
|
|
|
return BX_SOUNDLOW_OK;
|
|
}
|
|
|
|
// bx_soundlow_wavein_win_c class implemenzation
|
|
|
|
bx_soundlow_wavein_win_c::bx_soundlow_wavein_win_c()
|
|
:bx_soundlow_wavein_c()
|
|
{
|
|
WaveInOpen = 0;
|
|
WaveInHdr = (LPWAVEHDR) newbuffer(sizeof(WAVEHDR));
|
|
WaveInData = (LPSTR) newbuffer(BX_SOUNDLOW_WAVEPACKETSIZE+64);
|
|
if (WaveInData == NULL)
|
|
BX_PANIC(("Allocated memory was too small!"));
|
|
}
|
|
|
|
bx_soundlow_wavein_win_c::~bx_soundlow_wavein_win_c()
|
|
{
|
|
if (WaveInOpen == 1) {
|
|
waveInClose(hWaveIn);
|
|
}
|
|
}
|
|
|
|
int bx_soundlow_wavein_win_c::openwaveinput(const char *wavedev, sound_record_handler_t rh)
|
|
{
|
|
UNUSED(wavedev);
|
|
record_handler = rh;
|
|
if (rh != NULL) {
|
|
record_timer_index = bx_pc_system.register_timer(this, record_timer_handler, 1, 1, 0, "soundwin");
|
|
// record timer: inactive, continuous, frequency variable
|
|
}
|
|
recording = 0;
|
|
wavein_param.samplerate = 0;
|
|
return BX_SOUNDLOW_OK;
|
|
}
|
|
|
|
int bx_soundlow_wavein_win_c::recordnextpacket()
|
|
{
|
|
MMRESULT result;
|
|
|
|
WaveInHdr->lpData = (LPSTR)WaveInData;
|
|
WaveInHdr->dwBufferLength = record_packet_size;
|
|
WaveInHdr->dwBytesRecorded = 0;
|
|
WaveInHdr->dwUser = 0L;
|
|
WaveInHdr->dwFlags = 0L;
|
|
WaveInHdr->dwLoops = 0L;
|
|
waveInPrepareHeader(hWaveIn, WaveInHdr, sizeof(WAVEHDR));
|
|
result = waveInAddBuffer(hWaveIn, WaveInHdr, sizeof(WAVEHDR));
|
|
if (result) {
|
|
BX_ERROR(("Couldn't add buffer for recording (error = %d)", result));
|
|
return BX_SOUNDLOW_ERR;
|
|
} else {
|
|
result = waveInStart(hWaveIn);
|
|
if (result) {
|
|
BX_ERROR(("Couldn't start recording (error = %d)", result));
|
|
return BX_SOUNDLOW_ERR;
|
|
} else {
|
|
recording = 1;
|
|
return BX_SOUNDLOW_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
int bx_soundlow_wavein_win_c::startwaverecord(bx_pcm_param_t *param)
|
|
{
|
|
Bit64u timer_val;
|
|
Bit8u shift = 0;
|
|
MMRESULT result;
|
|
|
|
if (record_timer_index != BX_NULL_TIMER_HANDLE) {
|
|
if (param->bits == 16) shift++;
|
|
if (param->channels == 2) shift++;
|
|
record_packet_size = (param->samplerate / 10) << shift; // 0.1 sec
|
|
if (record_packet_size > BX_SOUNDLOW_WAVEPACKETSIZE) {
|
|
record_packet_size = BX_SOUNDLOW_WAVEPACKETSIZE;
|
|
}
|
|
timer_val = (Bit64u)record_packet_size * 1000000 / (param->samplerate << shift);
|
|
bx_pc_system.activate_timer(record_timer_index, (Bit32u)timer_val, 1);
|
|
}
|
|
// check if any of the properties have changed
|
|
if (memcmp(param, &wavein_param, sizeof(bx_pcm_param_t)) != 0) {
|
|
wavein_param = *param;
|
|
|
|
if (WaveInOpen) {
|
|
waveInClose(hWaveIn);
|
|
}
|
|
|
|
// Specify recording parameters
|
|
WAVEFORMATEX pFormat;
|
|
pFormat.wFormatTag = WAVE_FORMAT_PCM;
|
|
pFormat.nChannels = param->channels;
|
|
pFormat.nSamplesPerSec = param->samplerate;
|
|
pFormat.nAvgBytesPerSec = param->samplerate << shift;
|
|
pFormat.nBlockAlign = 1 << shift;
|
|
pFormat.wBitsPerSample = param->bits;
|
|
pFormat.cbSize = 0;
|
|
result = waveInOpen(&hWaveIn, WAVEMAPPER, &pFormat, 0L, 0L, WAVE_FORMAT_DIRECT);
|
|
if (result) {
|
|
BX_ERROR(("Couldn't open wave device for recording (error = %d)", result));
|
|
return BX_SOUNDLOW_ERR;
|
|
} else {
|
|
WaveInOpen = 1;
|
|
}
|
|
}
|
|
return recordnextpacket();
|
|
}
|
|
|
|
int bx_soundlow_wavein_win_c::getwavepacket(int length, Bit8u data[])
|
|
{
|
|
if (WaveInOpen && recording) {
|
|
do {} while (waveInUnprepareHeader(hWaveIn, WaveInHdr, sizeof(WAVEHDR)) == WAVERR_STILLPLAYING);
|
|
memcpy(data, WaveInData, length);
|
|
return recordnextpacket();
|
|
} else {
|
|
memset(data, 0, length);
|
|
return BX_SOUNDLOW_OK;
|
|
}
|
|
}
|
|
|
|
int bx_soundlow_wavein_win_c::stopwaverecord()
|
|
{
|
|
if (record_timer_index != BX_NULL_TIMER_HANDLE) {
|
|
bx_pc_system.deactivate_timer(record_timer_index);
|
|
}
|
|
if (WaveInOpen && recording) {
|
|
do {} while (waveInUnprepareHeader(hWaveIn, WaveInHdr, sizeof(WAVEHDR)) == WAVERR_STILLPLAYING);
|
|
recording = 0;
|
|
}
|
|
return BX_SOUNDLOW_OK;
|
|
}
|
|
|
|
void bx_soundlow_wavein_win_c::record_timer_handler(void *this_ptr)
|
|
{
|
|
bx_soundlow_wavein_win_c *class_ptr = (bx_soundlow_wavein_win_c *) this_ptr;
|
|
|
|
class_ptr->record_timer();
|
|
}
|
|
|
|
void bx_soundlow_wavein_win_c::record_timer(void)
|
|
{
|
|
record_handler(this, record_packet_size);
|
|
}
|
|
|
|
// bx_soundlow_midiout_win_c class implemenzation
|
|
|
|
bx_soundlow_midiout_win_c::bx_soundlow_midiout_win_c()
|
|
:bx_soundlow_midiout_c()
|
|
{
|
|
MidiOpen = 0;
|
|
ismidiready = 1;
|
|
MidiHeader = (LPMIDIHDR) newbuffer(sizeof(MIDIHDR));
|
|
MidiData = (LPSTR) newbuffer(BX_SOUND_WINDOWS_MAXSYSEXLEN);
|
|
if (MidiData == NULL)
|
|
BX_PANIC(("Allocated memory was too small!"));
|
|
}
|
|
|
|
bx_soundlow_midiout_win_c::~bx_soundlow_midiout_win_c()
|
|
{
|
|
closemidioutput();
|
|
}
|
|
|
|
int bx_soundlow_midiout_win_c::openmidioutput(const char *mididev)
|
|
{
|
|
UINT deviceid;
|
|
|
|
if (strlen(mididev) == 0) {
|
|
deviceid = (UINT) MIDIMAPPER;
|
|
} else {
|
|
deviceid = atoi(mididev);
|
|
if (((deviceid < 0) || (deviceid >= midiOutGetNumDevs())) &&
|
|
(deviceid != (UINT) MIDIMAPPER)) {
|
|
BX_ERROR(("MIDI device ID out of range - using default MIDI mapper"));
|
|
deviceid = (UINT) MIDIMAPPER;
|
|
}
|
|
}
|
|
MidiOpen = 0;
|
|
|
|
UINT ret = midiOutOpen(&MidiOut, deviceid, 0, 0, CALLBACK_NULL);
|
|
if (ret == 0)
|
|
MidiOpen = 1;
|
|
|
|
BX_DEBUG(("midiOutOpen() = %d, MidiOpen: %d", ret, MidiOpen));
|
|
|
|
return (MidiOpen == 1) ? BX_SOUNDLOW_OK : BX_SOUNDLOW_ERR;
|
|
}
|
|
|
|
int bx_soundlow_midiout_win_c::midiready()
|
|
{
|
|
if (ismidiready == 0)
|
|
checkmidiready();
|
|
|
|
if (ismidiready == 1)
|
|
return BX_SOUNDLOW_OK;
|
|
else
|
|
return BX_SOUNDLOW_ERR;
|
|
}
|
|
|
|
int bx_soundlow_midiout_win_c::sendmidicommand(int delta, int command, int length, Bit8u data[])
|
|
{
|
|
UINT ret;
|
|
|
|
if (MidiOpen != 1)
|
|
return BX_SOUNDLOW_ERR;
|
|
|
|
if ((command == 0xf0) || (command == 0xf7) || (length > 3))
|
|
{
|
|
BX_DEBUG(("SYSEX started, length %d", length));
|
|
ismidiready = 0; // until the buffer is done
|
|
memcpy(MidiData, data, length);
|
|
MidiHeader->lpData = MidiData;
|
|
MidiHeader->dwBufferLength = BX_SOUND_WINDOWS_MAXSYSEXLEN;
|
|
MidiHeader->dwBytesRecorded = 0;
|
|
MidiHeader->dwUser = 0;
|
|
MidiHeader->dwFlags = 0;
|
|
ret = midiOutPrepareHeader(MidiOut, MidiHeader, sizeof(*MidiHeader));
|
|
if (ret != 0)
|
|
BX_ERROR(("midiOutPrepareHeader(): error = %d", ret));
|
|
ret = midiOutLongMsg(MidiOut, MidiHeader, sizeof(*MidiHeader));
|
|
if (ret != 0)
|
|
BX_ERROR(("midiOutLongMsg(): error = %d", ret));
|
|
}
|
|
else
|
|
{
|
|
DWORD msg = command;
|
|
|
|
for (int i = 0; i<length; i++)
|
|
msg |= (data[i] << (8 * (i + 1)));
|
|
|
|
ret = midiOutShortMsg(MidiOut, msg);
|
|
BX_DEBUG(("midiOutShortMsg(%x) = %d", msg, ret));
|
|
}
|
|
|
|
return (ret == 0) ? BX_SOUNDLOW_OK : BX_SOUNDLOW_ERR;
|
|
}
|
|
|
|
int bx_soundlow_midiout_win_c::closemidioutput()
|
|
{
|
|
UINT ret;
|
|
|
|
if (MidiOpen == 1) {
|
|
ret = midiOutReset(MidiOut);
|
|
if (ismidiready == 0)
|
|
checkmidiready(); // to clear any pending SYSEX
|
|
ret = midiOutClose(MidiOut);
|
|
BX_DEBUG(("midiOutClose() = %d", ret));
|
|
}
|
|
return BX_SOUNDLOW_OK;
|
|
}
|
|
|
|
void bx_soundlow_midiout_win_c::checkmidiready()
|
|
{
|
|
if ((MidiHeader->dwFlags & MHDR_DONE) != 0) {
|
|
BX_DEBUG(("SYSEX message done, midi ready again"));
|
|
midiOutUnprepareHeader(MidiOut, MidiHeader, sizeof(*MidiHeader));
|
|
ismidiready = 1;
|
|
}
|
|
}
|
|
|
|
// bx_sound_windows_c class implemenzation
|
|
|
|
bx_sound_windows_c::bx_sound_windows_c()
|
|
:bx_sound_lowlevel_c()
|
|
{
|
|
DataHandle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, size);
|
|
DataPointer = (Bit8u*) GlobalLock(DataHandle);
|
|
|
|
if (DataPointer == NULL)
|
|
BX_PANIC(("GlobalLock returned NULL-pointer"));
|
|
|
|
#undef size
|
|
#undef ALIGN
|
|
|
|
BX_INFO(("Sound lowlevel module 'win' initialized"));
|
|
}
|
|
|
|
bx_sound_windows_c::~bx_sound_windows_c()
|
|
{
|
|
GlobalUnlock(DataHandle);
|
|
GlobalFree(DataHandle);
|
|
}
|
|
|
|
bx_soundlow_waveout_c* bx_sound_windows_c::get_waveout()
|
|
{
|
|
if (waveout == NULL) {
|
|
waveout = new bx_soundlow_waveout_win_c();
|
|
}
|
|
return waveout;
|
|
}
|
|
|
|
bx_soundlow_wavein_c* bx_sound_windows_c::get_wavein()
|
|
{
|
|
if (wavein == NULL) {
|
|
wavein = new bx_soundlow_wavein_win_c();
|
|
}
|
|
return wavein;
|
|
}
|
|
|
|
bx_soundlow_midiout_c* bx_sound_windows_c::get_midiout()
|
|
{
|
|
if (midiout == NULL) {
|
|
midiout = new bx_soundlow_midiout_win_c();
|
|
}
|
|
return midiout;
|
|
}
|
|
|
|
#endif // BX_HAVE_SOUND_WIN
|