Bochs/bochs/iodev/sound/soundwin.cc
Volker Ruppert 0e73185efd Moved wave output support to a separate C++ class. The lowlevel sound module
creates the object and returns a pointer to it. TODO: The wavein and midiout
components should also move to separate classes. When finished, it should be
possible to configure the sound support more detailed (e.g. waveout with sdl,
midiout with alsa and the dummy driver for wavein). Creating a separate
module for the output to file(s) is also possible.
2015-02-13 11:30:46 +00:00

481 lines
13 KiB
C++

/////////////////////////////////////////////////////////////////////////
// $Id$
/////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2001-2015 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 defined(WIN32) && BX_SUPPORT_SOUNDLOW
#define LOG_THIS
// 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);
BX_INIT_MUTEX(mixer_mutex);
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::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(100);
return BX_SOUNDLOW_OK;
}
// bx_sound_windows_c class implemenzation
bx_sound_windows_c::bx_sound_windows_c()
:bx_sound_lowlevel_c()
{
MidiOpen = 0;
WaveInOpen = 0;
ismidiready = 1;
DataHandle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, size);
DataPointer = (Bit8u*) GlobalLock(DataHandle);
if (DataPointer == NULL)
BX_PANIC(("GlobalLock returned NULL-pointer"));
unsigned offset = 0;
MidiHeader = (LPMIDIHDR) newbuffer(sizeof(MIDIHDR));
MidiData = (LPSTR) newbuffer(BX_SOUND_WINDOWS_MAXSYSEXLEN);
WaveInHdr = (LPWAVEHDR) newbuffer(sizeof(WAVEHDR));
WaveInData = (LPSTR) newbuffer(BX_SOUNDLOW_WAVEPACKETSIZE+64);
if (WaveInData == NULL)
BX_PANIC(("Allocated memory was too small!"));
#undef size
#undef ALIGN
BX_INFO(("Sound lowlevel module 'win' initialized"));
}
bx_soundlow_waveout_c* bx_sound_windows_c::get_waveout()
{
if (waveout == NULL) {
waveout = new bx_soundlow_waveout_win_c();
}
return waveout;
}
bx_sound_windows_c::~bx_sound_windows_c()
{
GlobalUnlock(DataHandle);
GlobalFree(DataHandle);
}
int bx_sound_windows_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_sound_windows_c::midiready()
{
if (ismidiready == 0)
checkmidiready();
if (ismidiready == 1)
return BX_SOUNDLOW_OK;
else
return BX_SOUNDLOW_ERR;
}
int bx_sound_windows_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_sound_windows_c::closemidioutput()
{
UINT ret;
if (MidiOpen != 1)
return BX_SOUNDLOW_ERR;
ret = midiOutReset(MidiOut);
if (ismidiready == 0)
checkmidiready(); // to clear any pending SYSEX
ret = midiOutClose(MidiOut);
BX_DEBUG(("midiOutClose() = %d", ret));
MidiOpen = 0;
return (ret == 0) ? BX_SOUNDLOW_OK : BX_SOUNDLOW_ERR;
}
void bx_sound_windows_c::checkmidiready()
{
if ((MidiHeader->dwFlags & MHDR_DONE) != 0)
{
BX_DEBUG(("SYSEX message done, midi ready again"));
midiOutUnprepareHeader(MidiOut, MidiHeader, sizeof(*MidiHeader));
ismidiready = 1;
}
}
int bx_sound_windows_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_sound_windows_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_sound_windows_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_sound_windows_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_sound_windows_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;
}
int bx_sound_windows_c::closewaveinput()
{
stopwaverecord();
if (WaveInOpen) {
waveInClose(hWaveIn);
}
return BX_SOUNDLOW_OK;
}
void bx_sound_windows_c::record_timer_handler(void *this_ptr)
{
bx_sound_windows_c *class_ptr = (bx_sound_windows_c *) this_ptr;
class_ptr->record_timer();
}
void bx_sound_windows_c::record_timer(void)
{
record_handler(this, record_packet_size);
}
#endif // defined(WIN32)