diff options
Diffstat (limited to 'engines/sci/sound/music.cpp')
-rw-r--r-- | engines/sci/sound/music.cpp | 573 |
1 files changed, 573 insertions, 0 deletions
diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp new file mode 100644 index 0000000000..fe8c0fed09 --- /dev/null +++ b/engines/sci/sound/music.cpp @@ -0,0 +1,573 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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. + * + * $URL$ + * $Id$ + * + */ + +#include "sound/audiostream.h" +#include "common/config-manager.h" + +#include "sci/sci.h" +#include "sci/console.h" +#include "sci/resource.h" +#include "sci/engine/kernel.h" +#include "sci/engine/state.h" +#include "sci/sound/midiparser_sci.h" +#include "sci/sound/music.h" +#include "sci/sound/softseq/pcjr.h" + +namespace Sci { + +SciMusic::SciMusic(SciVersion soundVersion) + : _soundVersion(soundVersion), _soundOn(true) { + + // Reserve some space in the playlist, to avoid expensive insertion + // operations + _playList.reserve(10); +} + +SciMusic::~SciMusic() { + if (_pMidiDrv) { + _pMidiDrv->close(); + delete _pMidiDrv; + } +} + +void SciMusic::init() { + // system init + _pMixer = g_system->getMixer(); + _pMixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt( + "sfx_volume")); + _pMixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, + ConfMan.getInt("speech_volume")); + _pMixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, + ConfMan.getInt("music_volume")); + // SCI sound init + _dwTempo = 0; + + const MidiDriverDescription *md = MidiDriver::findMusicDriver(ConfMan.get("music_driver")); + _midiType = md ? md->id : MD_AUTO; + + switch (_midiType) { + case MD_ADLIB: + // FIXME: There's no Amiga sound option, so we hook it up to Adlib + if (((SciEngine *)g_engine)->getPlatform() == Common::kPlatformAmiga) + _pMidiDrv = MidiPlayer_Amiga_create(); + else + _pMidiDrv = MidiPlayer_Adlib_create(); + break; + case MD_PCJR: + _pMidiDrv = new MidiPlayer_PCJr(); + break; + case MD_PCSPK: + _pMidiDrv = new MidiPlayer_PCSpeaker(); + break; + case MD_MT32: + // TODO + default: + warning("Unhandled MIDI type, switching to Adlib"); + _midiType = MD_ADLIB; + _pMidiDrv = MidiPlayer_Adlib_create(); + break; + } + + if (_pMidiDrv) { + _pMidiDrv->open(); + _pMidiDrv->setTimerCallback(this, &miditimerCallback); + _dwTempo = _pMidiDrv->getBaseTempo(); + } else + warning("Can't initialise music driver"); + _bMultiMidi = ConfMan.getBool("multi_midi"); +} + +void SciMusic::clearPlayList() { + _pMixer->stopAll(); + + _mutex.lock(); + while (!_playList.empty()) { + soundStop(_playList[0]); + soundKill(_playList[0]); + } + _mutex.unlock(); +} + +void SciMusic::miditimerCallback(void *p) { + SciMusic *aud = (SciMusic *)p; + + Common::StackLock lock(aud->_mutex); + aud->onTimer(); +} + +void SciMusic::soundSetSoundOn(bool soundOnFlag) { + Common::StackLock lock(_mutex); + + _soundOn = soundOnFlag; + _pMidiDrv->playSwitch(soundOnFlag); +} + +uint16 SciMusic::soundGetVoices() { + Common::StackLock lock(_mutex); + + return _pMidiDrv->getPolyphony(); +} + +MusicEntry *SciMusic::getSlot(reg_t obj) { + Common::StackLock lock(_mutex); + + const MusicList::iterator end = _playList.end(); + for (MusicList::iterator i = _playList.begin(); i != end; ++i) { + if ((*i)->soundObj == obj) + return *i; + } + + return NULL; +} + +void SciMusic::setReverb(byte reverb) { + _reverb = reverb; + + // TODO: actually set reverb for MT-32 + + // A good test case for this are the first two rooms in Longbow: + // reverb is set for the first room (the cave) and is subsequently + // cleared when Robin exits the cave +} + +void SciMusic::resetDriver() { + Common::StackLock lock(_mutex); + + _pMidiDrv->close(); + _pMidiDrv->open(); + _pMidiDrv->setTimerCallback(this, &miditimerCallback); +} + +static int f_compare(const void *arg1, const void *arg2) { + return ((const MusicEntry *)arg2)->prio - ((const MusicEntry *)arg1)->prio; +} + +void SciMusic::sortPlayList() { + MusicEntry ** pData = _playList.begin(); + qsort(pData, _playList.size(), sizeof(MusicEntry *), &f_compare); +} + +#if 0 +void SciMusic::patchSysEx(byte * addr, byte *pdata, int len) { + byte *buff = new byte[7 + len + 1]; + uint16 chk = 0; + int i; + + buff[0] = 0x41; + buff[1] = 0x10; + buff[2] = 0x16; + buff[3] = 0x12; + buff[4] = addr[0]; + buff[5] = addr[1]; + buff[6] = addr[2]; + for (i = 0; i < len; i++) { + buff[7 + i] = pdata[i]; + chk += pdata[i]; + } + chk += addr[0] + addr[1] + addr[2]; + buff[7 + i] = 128 - chk % 128; + _pMidiDrv->sysEx(buff, len + 8); + delete[] buff; +} + +void SciMusic::patchUpdateAddr(byte *addr, int len) { + addr[2] += len; + if (addr[2] >= 0x7F) { + addr[1]++; + addr[2] -= 0x80; + } +} +#endif + +// FIXME: This should be done at the driver level +#if 0 +void SciMusic::loadPatch() { + if (_midiType == MD_MT32) + loadPatchMT32(); +} +#endif + +#if 0 +// currently loads patch 1.pat for Roland/MT-32 device +void SciMusic::loadPatchMT32() { + //byte sysText[] = { 0x20, 0, 0 }; + byte sysMem[] = { 0x5, 0, 0 }; // patch memory + byte sysRhytm[] = { 0x3, 0x1, 0x10 }; // rhytm + byte sysMsg3[15] = { 0x41, 0x10, 0x16, 0x12, 0x52, 0, 0xA, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x20, 0x80 }; + byte sysTimbre[] = { 0x8, 0, 0 }; // timbre memory + byte sysSystem[] = { 0x10, 0, 4 }; // partial reserve & midi channel + byte arr[3][11]; + + Resource *res = ((SciEngine *)g_engine)->getResourceManager()->findResource(ResourceId(kResourceTypePatch, 1), 0); + + if (res) { + byte *pData = res->data, *p; + // welcome message + //patchSysEx(sysText, pData + 20, 20); + // reading reverb mode, time and level + p = pData + 74; + for (int i = 0; i < 11; i++) { + arr[0][i] = *p++; + arr[1][i] = *p++; + arr[2][i] = *p++; + } + // sub_657 - patch memory + for (int i = 0; i < 48; i++) { + patchSysEx(sysMem, p, 8); + patchUpdateAddr(sysMem, 8); + p += 8; + } + // sub_696 - timbre + byte dl = *p++, cl = 0; + while (dl--) { + patchSysEx(sysTimbre, p, 14); // common area + patchUpdateAddr(sysTimbre, 14); + patchSysEx(sysTimbre, p + 14, 58);// partial 1 + patchUpdateAddr(sysTimbre, 58); + patchSysEx(sysTimbre, p + 72, 58);// partial 2 + patchUpdateAddr(sysTimbre, 58); + patchSysEx(sysTimbre, p + 130, 58);// partial 3 + patchUpdateAddr(sysTimbre, 58); + patchSysEx(sysTimbre, p + 188, 58);// partial 4 + patchUpdateAddr(sysTimbre, 58); + p += 246; + cl += 2; + sysTimbre[1] = cl; + sysTimbre[2] = 0; + } + // patch memory or rhytm + uint16 flag = READ_BE_UINT16(p); + p += 2; + if (flag == 0xABCD) { + // sub_657 + for (int i = 0; i < 48; i++) { + patchSysEx(sysMem, p, 8); + patchUpdateAddr(sysMem, 8); + p += 8; + } + } else if (flag == 0xDCBA) { + // sub_756 + for (int i = 0; i < 64; i++) { + patchSysEx(sysRhytm, p, 4); + patchUpdateAddr(sysRhytm, 4); + p += 4; + } + patchSysEx(sysSystem, p, 18); + } + // after-init text message + //patchSysEx(sysText, pData, 20); + // some final sysex + _pMidiDrv->sysEx(sysMsg3, 15); + // releasing patch resource + //g_sci->ResMgr.ResUnload(SCI_RES_PATCH, 1); + debug("MT-32 patch loaded"); + } +} +#endif + + +void SciMusic::soundInitSnd(MusicEntry *pSnd) { + SoundResource::Track *track = NULL; + int channelFilterMask = 0; + + switch (_midiType) { + case MD_PCSPK: + track = pSnd->soundRes->getTrackByType(SoundResource::TRACKTYPE_SPEAKER); + break; + case MD_PCJR: + track = pSnd->soundRes->getTrackByType(SoundResource::TRACKTYPE_TANDY); + break; + case MD_ADLIB: + track = pSnd->soundRes->getTrackByType(SoundResource::TRACKTYPE_ADLIB); + break; + case MD_MT32: + track = pSnd->soundRes->getTrackByType(SoundResource::TRACKTYPE_MT32); + break; + default: + // Should never occur + error("soundInitSnd: Unknown MIDI type"); + break; + } + + if (track) { + // If MIDI device is selected but there is no digital track in sound resource + // try to use adlib's digital sample if possible + if (_bMultiMidi && pSnd->soundRes->getTrackByType(SoundResource::TRACKTYPE_ADLIB)->digitalChannelNr != -1) + track = pSnd->soundRes->getTrackByType(SoundResource::TRACKTYPE_ADLIB); + // Play digital sample + if (track->digitalChannelNr != -1) { + byte *channelData = track->channels[track->digitalChannelNr].data; + delete pSnd->pStreamAud; + pSnd->pStreamAud = Audio::makeLinearInputStream(channelData, track->digitalSampleSize, track->digitalSampleRate, + Audio::Mixer::FLAG_UNSIGNED, 0, 0); + pSnd->soundType = Audio::Mixer::kSFXSoundType; + pSnd->hCurrentAud = Audio::SoundHandle(); + } else { + // play MIDI track + _mutex.lock(); + pSnd->soundType = Audio::Mixer::kMusicSoundType; + if (pSnd->pMidiParser == NULL) { + pSnd->pMidiParser = new MidiParser_SCI(_soundVersion); + pSnd->pMidiParser->setMidiDriver(_pMidiDrv); + pSnd->pMidiParser->setTimerRate(_dwTempo); + pSnd->pMidiParser->property(MidiParser::mpCenterPitchWheelOnUnload, 1); + } + + pSnd->pauseCounter = 0; + + // Find out what channels to filter for SCI0 + channelFilterMask = pSnd->soundRes->getChannelFilterMask(_pMidiDrv->getPlayMask(_soundVersion)); + pSnd->pMidiParser->loadMusic(track, pSnd, channelFilterMask, _soundVersion); + + // Fast forward to the last position and perform associated events when loading + pSnd->pMidiParser->jumpToTick(pSnd->ticker, true); + _mutex.unlock(); + } + } +} + +void SciMusic::onTimer() { + const MusicList::iterator end = _playList.end(); + for (MusicList::iterator i = _playList.begin(); i != end; ++i) + (*i)->onTimer(); +} + +void SciMusic::soundPlay(MusicEntry *pSnd) { + _mutex.lock(); + + uint sz = _playList.size(), i; + // searching if sound is already in _playList + for (i = 0; i < sz && _playList[i] != pSnd; i++) + ; + if (i == sz) {// not found + _playList.push_back(pSnd); + sortPlayList(); + } + + _mutex.unlock(); // unlock to perform mixer-related calls + + if (pSnd->pStreamAud && !_pMixer->isSoundHandleActive(pSnd->hCurrentAud)) { + // Are we supposed to loop the stream? + if (pSnd->loop > 1) + pSnd->pStreamAud->setNumLoops(pSnd->loop); + else + pSnd->pStreamAud->setNumLoops(1); + _pMixer->playInputStream(pSnd->soundType, &pSnd->hCurrentAud, + pSnd->pStreamAud, -1, pSnd->volume, 0, false); + } else { + _mutex.lock(); + if (pSnd->pMidiParser) { + pSnd->pMidiParser->setVolume(pSnd->volume); + if (pSnd->status == kSoundStopped) + pSnd->pMidiParser->jumpToTick(0); + } + _mutex.unlock(); + } + + pSnd->status = kSoundPlaying; +} + +void SciMusic::soundStop(MusicEntry *pSnd) { + pSnd->status = kSoundStopped; + if (pSnd->pStreamAud) + _pMixer->stopHandle(pSnd->hCurrentAud); + + _mutex.lock(); + if (pSnd->pMidiParser) + pSnd->pMidiParser->stop(); + _mutex.unlock(); +} + +void SciMusic::soundSetVolume(MusicEntry *pSnd, byte volume) { + assert(volume <= MUSIC_VOLUME_MAX); + if (pSnd->pStreamAud) { + _pMixer->setChannelVolume(pSnd->hCurrentAud, volume * 2); // Mixer is 0-255, SCI is 0-127 + } else if (pSnd->pMidiParser) { + _mutex.lock(); + pSnd->pMidiParser->setVolume(volume); + _mutex.unlock(); + } +} + +void SciMusic::soundSetPriority(MusicEntry *pSnd, byte prio) { + Common::StackLock lock(_mutex); + + pSnd->prio = prio; + sortPlayList(); +} + +void SciMusic::soundKill(MusicEntry *pSnd) { + pSnd->status = kSoundStopped; + + _mutex.lock(); + if (pSnd->pMidiParser) { + pSnd->pMidiParser->unloadMusic(); + delete pSnd->pMidiParser; + pSnd->pMidiParser = NULL; + } + _mutex.unlock(); + + if (pSnd->pStreamAud) { + _pMixer->stopHandle(pSnd->hCurrentAud); + pSnd->pStreamAud = NULL; + } + + _mutex.lock(); + uint sz = _playList.size(), i; + // Remove sound from playlist + for (i = 0; i < sz; i++) { + if (_playList[i] == pSnd) { + delete _playList[i]->soundRes; + delete _playList[i]; + _playList.remove_at(i); + break; + } + } + _mutex.unlock(); +} + +void SciMusic::soundPause(MusicEntry *pSnd) { + pSnd->pauseCounter++; + if (pSnd->status != kSoundPlaying) + return; + pSnd->status = kSoundPaused; + if (pSnd->pStreamAud) { + _pMixer->pauseHandle(pSnd->hCurrentAud, true); + } else { + _mutex.lock(); + if (pSnd->pMidiParser) + pSnd->pMidiParser->pause(); + _mutex.unlock(); + } +} + +void SciMusic::soundResume(MusicEntry *pSnd) { + if (pSnd->pauseCounter > 0) + pSnd->pauseCounter--; + if (pSnd->pauseCounter != 0) + return; + if (pSnd->status != kSoundPaused) + return; + soundPlay(pSnd); +} + +uint16 SciMusic::soundGetMasterVolume() { + return (_pMixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) + 8) * 0xF / Audio::Mixer::kMaxMixerVolume; +} + +void SciMusic::soundSetMasterVolume(uint16 vol) { + vol = vol & 0xF; // 0..15 + vol = vol * Audio::Mixer::kMaxMixerVolume / 0xF; + // "master volume" is music and SFX only, speech (audio resources) are supposed to be unaffected + _pMixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, vol); + _pMixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, vol); +} + +void SciMusic::printPlayList(Console *con) { + Common::StackLock lock(_mutex); + + const char *musicStatus[] = { "Stopped", "Initialized", "Paused", "Playing" }; + + const MusicList::iterator end = _playList.end(); + for (MusicList::iterator i = _playList.begin(); i != end; ++i) { + con->DebugPrintf("%d: %04x:%04x, priority: %d, status: %s\n", i, + PRINT_REG((*i)->soundObj), (*i)->prio, + musicStatus[(*i)->status]); + } +} + +MusicEntry::MusicEntry() { + soundObj = NULL_REG; + + soundRes = 0; + resnum = 0; + + dataInc = 0; + ticker = 0; + signal = 0; + prio = 0; + loop = 0; + volume = MUSIC_VOLUME_DEFAULT; + hold = 0; + + pauseCounter = 0; + sampleLoopCounter = 0; + + fadeTo = 0; + fadeStep = 0; + fadeTicker = 0; + fadeTickerStep = 0; + fadeSetVolume = false; + fadeCompleted = false; + + status = kSoundStopped; + + soundType = Audio::Mixer::kMusicSoundType; + + pStreamAud = 0; + pMidiParser = 0; +} + +MusicEntry::~MusicEntry() { +} + +void MusicEntry::onTimer() { + if (status != kSoundPlaying) + return; + + // Fade MIDI and digital sound effects + if (fadeStep) + doFade(); + + // Only process MIDI streams in this thread, not digital sound effects + if (pMidiParser) { + pMidiParser->onTimer(); + ticker = (uint16)pMidiParser->getTick(); + } +} + +void MusicEntry::doFade() { + if (fadeTicker) + fadeTicker--; + else { + int16 fadeVolume = volume; + fadeTicker = fadeTickerStep; + fadeVolume += fadeStep; + if (((fadeStep > 0) && (fadeVolume >= fadeTo)) || ((fadeStep < 0) && (fadeVolume <= fadeTo))) { + fadeVolume = fadeTo; + fadeStep = 0; + fadeCompleted = true; + } + volume = fadeVolume; + + // Only process MIDI streams in this thread, not digital sound effects + if (pMidiParser) + pMidiParser->setVolume(volume); + fadeSetVolume = true; // set flag so that SoundCommandParser::cmdUpdateCues will set the volume of the stream + } +} + +} // End of namespace Sci |