/* * 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 #include 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