273 lines
8.4 KiB
Text
273 lines
8.4 KiB
Text
/*
|
|
* This file is part of sidplayfp, a console SID player.
|
|
*
|
|
* Copyright 2013 Leandro Nini
|
|
* Copyright 2001-2002 Simon White
|
|
* Copyright 2000 Jarno Paananen
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "audiodrv.h"
|
|
|
|
#ifdef HAVE_MMSYSTEM
|
|
|
|
#include <stdio.h>
|
|
#include <mmreg.h>
|
|
|
|
Audio_MMSystem::Audio_MMSystem() :
|
|
AudioBase("MMSYSTEM"),
|
|
waveHandle(0),
|
|
isOpen(false)
|
|
{
|
|
for ( int i = 0; i < MAXBUFBLOCKS; i++ )
|
|
{
|
|
blockHeaderHandles[i] = 0;
|
|
blockHandles[i] = 0;
|
|
blockHeaders[i] = NULL;
|
|
blocks[i] = NULL;
|
|
}
|
|
}
|
|
|
|
Audio_MMSystem::~Audio_MMSystem()
|
|
{
|
|
close();
|
|
}
|
|
|
|
const char* Audio_MMSystem::getErrorMessage(MMRESULT err)
|
|
{
|
|
switch (err)
|
|
{
|
|
default:
|
|
case MMSYSERR_ERROR: return "Unspecified error";
|
|
case MMSYSERR_BADDEVICEID: return "Device ID out of range";
|
|
case MMSYSERR_NOTENABLED: return "Driver failed enable";
|
|
case MMSYSERR_ALLOCATED: return "Device already allocated";
|
|
case MMSYSERR_INVALHANDLE: return "Device handle is invalid";
|
|
case MMSYSERR_NODRIVER: return "No device driver present";
|
|
case MMSYSERR_NOMEM: return "Memory allocation error";
|
|
case MMSYSERR_NOTSUPPORTED: return "Function isn't supported";
|
|
case MMSYSERR_BADERRNUM: return "Error value out of range";
|
|
case MMSYSERR_INVALFLAG: return "Invalid flag passed";
|
|
case MMSYSERR_INVALPARAM: return "Invalid parameter passed";
|
|
case MMSYSERR_HANDLEBUSY: return "Handle being used simultaneously on another thread (eg callback)";
|
|
case MMSYSERR_INVALIDALIAS: return "Specified alias not found";
|
|
case MMSYSERR_BADDB: return "Bad registry database";
|
|
case MMSYSERR_KEYNOTFOUND: return "Registry key not found";
|
|
case MMSYSERR_READERROR: return "Registry read error";
|
|
case MMSYSERR_WRITEERROR: return "Registry write error";
|
|
case MMSYSERR_DELETEERROR: return "Registry delete error";
|
|
case MMSYSERR_VALNOTFOUND: return "Registry value not found";
|
|
case MMSYSERR_NODRIVERCB: return "Driver does not call DriverCallback";
|
|
}
|
|
/*
|
|
TCHAR buffer[MAXERRORLENGTH];
|
|
waveOutGetErrorText(err, buffer, MAXERRORLENGTH);
|
|
*/
|
|
}
|
|
|
|
void Audio_MMSystem::checkResult(MMRESULT err)
|
|
{
|
|
if (err != MMSYSERR_NOERROR)
|
|
{
|
|
throw error(getErrorMessage(err));
|
|
}
|
|
}
|
|
|
|
bool Audio_MMSystem::open(AudioConfig &cfg)
|
|
{
|
|
WAVEFORMATEX wfm;
|
|
|
|
if (isOpen)
|
|
{
|
|
setError("Audio device already open.");
|
|
return false;
|
|
}
|
|
isOpen = true;
|
|
|
|
/* Initialise blocks */
|
|
memset (blockHandles, 0, sizeof (blockHandles));
|
|
memset (blockHeaders, 0, sizeof (blockHeaders));
|
|
memset (blockHeaderHandles, 0, sizeof (blockHeaderHandles));
|
|
|
|
// Format
|
|
memset (&wfm, 0, sizeof(WAVEFORMATEX));
|
|
wfm.wFormatTag = WAVE_FORMAT_PCM;
|
|
wfm.nChannels = cfg.channels;
|
|
wfm.nSamplesPerSec = cfg.frequency;
|
|
wfm.wBitsPerSample = 16;
|
|
wfm.nBlockAlign = wfm.wBitsPerSample / 8 * wfm.nChannels;
|
|
wfm.nAvgBytesPerSec = wfm.nSamplesPerSec * wfm.nBlockAlign;
|
|
wfm.cbSize = 0;
|
|
|
|
// Rev 1.3 (saw) - Calculate buffer to hold 250ms of data
|
|
bufSize = wfm.nSamplesPerSec / 4 * wfm.nBlockAlign;
|
|
|
|
try
|
|
{
|
|
cfg.bufSize = bufSize / 2;
|
|
checkResult(waveOutOpen(&waveHandle, WAVE_MAPPER, &wfm, 0, 0, 0));
|
|
|
|
_settings = cfg;
|
|
|
|
/* Allocate and lock memory for all mixing blocks: */
|
|
for (int i = 0; i < MAXBUFBLOCKS; i++ )
|
|
{
|
|
/* Allocate global memory for mixing block: */
|
|
if ( (blockHandles[i] = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE,
|
|
bufSize)) == NULL )
|
|
{
|
|
throw error("Can't allocate global memory.");
|
|
}
|
|
|
|
/* Lock mixing block memory: */
|
|
if ( (blocks[i] = (short *)GlobalLock(blockHandles[i])) == NULL )
|
|
{
|
|
throw error("Can't lock global memory.");
|
|
}
|
|
|
|
/* Allocate global memory for mixing block header: */
|
|
if ( (blockHeaderHandles[i] = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE,
|
|
sizeof(WAVEHDR))) == NULL )
|
|
{
|
|
throw error("Can't allocate global memory.");
|
|
}
|
|
|
|
/* Lock mixing block header memory: */
|
|
WAVEHDR *header;
|
|
if ( (header = blockHeaders[i] =
|
|
(WAVEHDR*)GlobalLock(blockHeaderHandles[i])) == NULL )
|
|
{
|
|
throw error("Can't lock global memory.");
|
|
}
|
|
|
|
/* Reset wave header fields: */
|
|
memset (header, 0, sizeof (WAVEHDR));
|
|
header->lpData = (char*)blocks[i];
|
|
header->dwBufferLength = bufSize;
|
|
header->dwFlags = WHDR_DONE; /* mark the block is done */
|
|
}
|
|
|
|
blockNum = 0;
|
|
_sampleBuffer = blocks[blockNum];
|
|
return true;
|
|
}
|
|
catch(error const &e)
|
|
{
|
|
setError(e.message());
|
|
|
|
close ();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool Audio_MMSystem::write()
|
|
{
|
|
if (!isOpen)
|
|
{
|
|
setError("Device not open.");
|
|
return false;
|
|
}
|
|
|
|
/* Reset wave header fields: */
|
|
blockHeaders[blockNum]->dwFlags = 0;
|
|
|
|
/* Prepare block header: */
|
|
checkResult(waveOutPrepareHeader(waveHandle, blockHeaders[blockNum], sizeof(WAVEHDR)));
|
|
|
|
checkResult(waveOutWrite(waveHandle, blockHeaders[blockNum], sizeof(WAVEHDR)));
|
|
|
|
/* Next block, circular buffer style, and I don't like modulo. */
|
|
blockNum++;
|
|
blockNum %= MAXBUFBLOCKS;
|
|
|
|
/* Wait for the next block to become free */
|
|
while ( !(blockHeaders[blockNum]->dwFlags & WHDR_DONE) )
|
|
Sleep(20);
|
|
|
|
checkResult(waveOutUnprepareHeader(waveHandle, blockHeaders[blockNum], sizeof(WAVEHDR)));
|
|
|
|
_sampleBuffer = blocks[blockNum];
|
|
return true;
|
|
}
|
|
|
|
// Rev 1.2 (saw) - Changed, see AudioBase.h
|
|
void Audio_MMSystem::reset()
|
|
{
|
|
if (!isOpen)
|
|
return;
|
|
|
|
// Stop play and kill the current music.
|
|
// Start new music data being added at the begining of
|
|
// the first buffer
|
|
checkResult(waveOutReset(waveHandle));
|
|
blockNum = 0;
|
|
_sampleBuffer = blocks[blockNum];
|
|
}
|
|
|
|
void Audio_MMSystem::close()
|
|
{
|
|
if ( !isOpen )
|
|
return;
|
|
|
|
isOpen = false;
|
|
_sampleBuffer = NULL;
|
|
|
|
/* Reset wave output device, stop playback, and mark all blocks done: */
|
|
if ( waveHandle )
|
|
{
|
|
waveOutReset(waveHandle);
|
|
|
|
/* Make sure all blocks are indeed done: */
|
|
int doneTimeout = 500;
|
|
|
|
for (;;) {
|
|
bool allDone = true;
|
|
for ( int i = 0; i < MAXBUFBLOCKS; i++ ) {
|
|
if ( blockHeaders[i] && (blockHeaders[i]->dwFlags & WHDR_DONE) == 0 )
|
|
allDone = false;
|
|
}
|
|
|
|
if ( allDone || (doneTimeout == 0) )
|
|
break;
|
|
doneTimeout--;
|
|
Sleep(20);
|
|
}
|
|
|
|
/* Unprepare all mixing blocks, unlock and deallocate
|
|
all mixing blocks and mixing block headers: */
|
|
for ( int i = 0; i < MAXBUFBLOCKS; i++ )
|
|
{
|
|
if ( blockHeaders[i] )
|
|
waveOutUnprepareHeader(waveHandle, blockHeaders[i], sizeof(WAVEHDR));
|
|
|
|
if ( blockHeaderHandles[i] )
|
|
{
|
|
GlobalUnlock(blockHeaderHandles[i]);
|
|
GlobalFree(blockHeaderHandles[i]);
|
|
}
|
|
if ( blockHandles[i] )
|
|
{
|
|
GlobalUnlock(blockHandles[i]);
|
|
GlobalFree(blockHandles[i]);
|
|
}
|
|
}
|
|
|
|
/* Close wave output device: */
|
|
waveOutClose(waveHandle);
|
|
}
|
|
}
|
|
|
|
#endif // HAVE_MMSYSTEM
|