diff options
Diffstat (limited to 'engines/sherlock/music.cpp')
-rw-r--r-- | engines/sherlock/music.cpp | 598 |
1 files changed, 598 insertions, 0 deletions
diff --git a/engines/sherlock/music.cpp b/engines/sherlock/music.cpp new file mode 100644 index 0000000000..49c48126f4 --- /dev/null +++ b/engines/sherlock/music.cpp @@ -0,0 +1,598 @@ +/* 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. + * + */ + +#include "common/config-manager.h" +#include "common/mutex.h" +#include "sherlock/sherlock.h" +#include "sherlock/music.h" +#include "sherlock/scalpel/drivers/mididriver.h" +// for Miles Audio (Sherlock Holmes 2) +#include "audio/miles.h" +// for 3DO digital music +#include "audio/decoders/aiff.h" + +namespace Sherlock { + +#define NUM_SONGS 45 + +/* This tells which song to play in each room, 0 = no song played */ +static const char ROOM_SONG[62] = { + 0, 20, 43, 6, 11, 2, 8, 15, 6, 28, + 6, 38, 7, 32, 16, 5, 8, 41, 9, 22, + 10, 23, 4, 39, 19, 24, 13, 27, 0, 30, + 3, 21, 26, 25, 16, 29, 1, 1, 18, 12, + 1, 17, 17, 31, 17, 34, 36, 7, 20, 20, + 33, 8, 44, 40, 42, 35, 0, 0, 0, 12, + 12 +}; + +static const char *const SONG_NAMES[NUM_SONGS] = { + "SINGERF", "CHEMIST", "TOBAC", "EQUEST", "MORTUARY", "DOCKS", "LSTUDY", + "LORD", "BOY", "PERFUM1", "BAKER1", "BAKER2", "OPERA1", "HOLMES", + "FFLAT", "OP1FLAT", "ZOO", "SROOM", "FLOWERS", "YARD", "TAXID", + "PUB1", "VICTIM", "RUGBY", "DORM", "SHERMAN", "LAWYER", "THEATRE", + "DETECT", "OPERA4", "POOL", "SOOTH", "ANNA1", "ANNA2", "PROLOG3", + "PAWNSHOP", "MUSICBOX", "MOZART1", "ROBHUNT", "PANCRAS1", "PANCRAS2", "LORDKILL", + "BLACKWEL", "RESCUE", "MAP" +}; + +MidiParser_SH::MidiParser_SH() { + _ppqn = 1; + setTempo(16667); + _data = nullptr; + _beats = 0; + _lastEvent = 0; + _trackEnd = nullptr; + + _musData = nullptr; + _musDataSize = 0; +} + +MidiParser_SH::~MidiParser_SH() { + Common::StackLock lock(_mutex); + unloadMusic(); + _driver = NULL; +} + +void MidiParser_SH::parseNextEvent(EventInfo &info) { + Common::StackLock lock(_mutex); + +// warning("parseNextEvent"); + + // there is no delta right at the start of the music data + // this order is essential, otherwise notes will get delayed or even go missing + if (_position._playPos != _tracks[0]) { + info.delta = *(_position._playPos++); + } else { + info.delta = 0; + } + + info.start = _position._playPos; + + info.event = *_position._playPos++; + //warning("Event %x", info.event); + _position._runningStatus = info.event; + + switch (info.command()) { + case 0xC: { // program change + int idx = *_position._playPos++; + info.basic.param1 = idx & 0x7f; + info.basic.param2 = 0; + } + break; + case 0xD: + info.basic.param1 = *_position._playPos++; + info.basic.param2 = 0; + break; + + case 0xB: + info.basic.param1 = *_position._playPos++; + info.basic.param2 = *_position._playPos++; + info.length = 0; + break; + + case 0x8: + case 0x9: + case 0xA: + case 0xE: + info.basic.param1 = *(_position._playPos++); + info.basic.param2 = *(_position._playPos++); + if (info.command() == 0x9 && info.basic.param2 == 0) { + // NoteOn with param2==0 is a NoteOff + info.event = info.channel() | 0x80; + } + info.length = 0; + break; + case 0xF: + if (info.event == 0xFF) { + error("SysEx META event 0xFF"); + + byte type = *(_position._playPos++); + switch(type) { + case 0x2F: + // End of Track + allNotesOff(); + stopPlaying(); + unloadMusic(); + return; + case 0x51: + warning("TODO: 0xFF / 0x51"); + return; + default: + warning("TODO: 0xFF / %x Unknown", type); + break; + } + } else if (info.event == 0xFC) { + // Official End-Of-Track signal + debugC(kDebugLevelMusic, "Music: System META event 0xFC"); + + byte type = *(_position._playPos++); + switch (type) { + case 0x80: // end of track, triggers looping + debugC(kDebugLevelMusic, "Music: META event triggered looping"); + jumpToTick(0, true, true, false); + break; + case 0x81: // end of track, stop playing + debugC(kDebugLevelMusic, "Music: META event triggered music stop"); + stopPlaying(); + unloadMusic(); + break; + default: + error("MidiParser_SH::parseNextEvent: Unknown META event 0xFC type %x", type); + break; + } + } else { + warning("TODO: %x / Unknown", info.event); + break; + } + break; + default: + warning("MidiParser_SH::parseNextEvent: Unsupported event code %x", info.event); + break; + }// switch (info.command()) +} + +bool MidiParser_SH::loadMusic(byte *musData, uint32 musDataSize) { + Common::StackLock lock(_mutex); + + debugC(kDebugLevelMusic, "Music: loadMusic()"); + unloadMusic(); + + _musData = musData; + _musDataSize = musDataSize; + + byte *headerPtr = _musData + 12; // skip over the already checked SPACE header + byte *pos = headerPtr; + + uint16 headerSize = READ_LE_UINT16(headerPtr); + assert(headerSize == 0x7F); // Security check + + // Skip over header + pos += headerSize; + + _lastEvent = 0; + _trackEnd = _musData + _musDataSize; + + _numTracks = 1; + _tracks[0] = pos; + + _ppqn = 1; + setTempo(16667); + setTrack(0); + + return true; +} + +void MidiParser_SH::unloadMusic() { + Common::StackLock lock(_mutex); + + if (_musData) { + delete[] _musData; + _musData = NULL; + _musDataSize = 0; + } + + MidiParser::unloadMusic(); +} + +/*----------------------------------------------------------------*/ + +Music::Music(SherlockEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) { + _midiDriver = NULL; + _midiParser = NULL; + _musicType = MT_NULL; + _musicPlaying = false; + _musicOn = false; + _midiOption = false; + _musicVolume = 0; + + if (IS_3DO) { + // 3DO - uses digital samples for music + _musicOn = true; + return; + } + + if (_vm->_interactiveFl) + _vm->_res->addToCache("MUSIC.LIB"); + + MidiDriver::DeviceHandle dev; + + if (IS_SERRATED_SCALPEL) { + // Serrated Scalpel: used an internal Electronic Arts .MUS music engine + _midiParser = new MidiParser_SH(); + dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32); + _musicType = MidiDriver::getMusicType(dev); + + switch (_musicType) { + case MT_ADLIB: + _midiDriver = MidiDriver_SH_AdLib_create(); + break; + case MT_MT32: + _midiDriver = MidiDriver_MT32_create(); + break; + case MT_GM: + if (ConfMan.getBool("native_mt32")) { + _midiDriver = MidiDriver_MT32_create(); + _musicType = MT_MT32; + } + break; + default: + // Create default one + // I guess we shouldn't do this anymore + //_midiDriver = MidiDriver::createMidi(dev); + break; + } + } else { + // Rose Tattooo: seems to use Miles Audio 3 + _midiParser = MidiParser::createParser_XMIDI(); + dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM); + _musicType = MidiDriver::getMusicType(dev); + + switch (_musicType) { + case MT_ADLIB: + // SAMPLE.AD -> regular AdLib instrument data + // SAMPLE.OPL -> OPL-3 instrument data + // although in case of Rose Tattoo both files are exactly the same + _midiDriver = Audio::MidiDriver_Miles_AdLib_create("SAMPLE.AD", "SAMPLE.OPL"); + break; + case MT_MT32: + // Sherlock Holmes 2 does not have a MT32 timbre file + _midiDriver = Audio::MidiDriver_Miles_MT32_create(""); + break; + case MT_GM: + if (ConfMan.getBool("native_mt32")) { + _midiDriver = Audio::MidiDriver_Miles_MT32_create(""); + _musicType = MT_MT32; + } else { + _midiDriver = MidiDriver::createMidi(dev); + _musicType = MT_GM; + } + break; + default: + // Do not create anything + break; + } + } + + if (_midiDriver) { + int ret = _midiDriver->open(); + if (ret == 0) { + // Reset is done inside our MIDI driver + _midiDriver->setTimerCallback(_midiParser, &_midiParser->timerCallback); + } + _midiParser->setMidiDriver(_midiDriver); + _midiParser->setTimerRate(_midiDriver->getBaseTempo()); + + if (IS_SERRATED_SCALPEL) { + if (_musicType == MT_MT32) { + // Upload patches + Common::SeekableReadStream *MT32driverStream = _vm->_res->load("MTHOM.DRV", "MUSIC.LIB"); + + if (!MT32driverStream) + error("Music: could not load MTHOM.DRV, critical"); + + byte *MT32driverData = new byte[MT32driverStream->size()]; + int32 MT32driverDataSize = MT32driverStream->size(); + assert(MT32driverData); + + MT32driverStream->read(MT32driverData, MT32driverDataSize); + delete MT32driverStream; + + assert(MT32driverDataSize > 12); + byte *MT32driverDataPtr = MT32driverData + 12; + MT32driverDataSize -= 12; + + MidiDriver_MT32_uploadPatches(_midiDriver, MT32driverDataPtr, MT32driverDataSize); + delete[] MT32driverData; + } + } + + _musicOn = true; + } +} + +Music::~Music() { + stopMusic(); + if (_midiDriver) { + _midiDriver->setTimerCallback(this, NULL); + } + if (_midiParser) { + _midiParser->stopPlaying(); + delete _midiParser; + _midiParser = nullptr; + } + if (_midiDriver) { + _midiDriver->close(); + delete _midiDriver; + } +} + +bool Music::loadSong(int songNumber) { + debugC(kDebugLevelMusic, "Music: loadSong()"); + + if(songNumber == 100) + songNumber = 55; + else if(songNumber == 70) + songNumber = 54; + + if((songNumber > 60) || (songNumber < 1)) + return false; + + songNumber = ROOM_SONG[songNumber]; + + if(songNumber == 0) + songNumber = 12; + + if((songNumber > NUM_SONGS) || (songNumber < 1)) + return false; + + Common::String songName = Common::String(SONG_NAMES[songNumber - 1]); + + freeSong(); // free any song that is currently loaded + stopMusic(); + + if (!playMusic(songName)) + return false; + + startSong(); + return true; +} + +bool Music::loadSong(const Common::String &songName) { + freeSong(); // free any song that is currently loaded + stopMusic(); + + if (!playMusic(songName)) + return false; + + startSong(); + return true; +} + +void Music::syncMusicSettings() { + _musicOn = !ConfMan.getBool("mute") && !ConfMan.getBool("music_mute"); +} + +bool Music::playMusic(const Common::String &name) { + if (!_musicOn) + return false; + + debugC(kDebugLevelMusic, "Music: playMusic('%s')", name.c_str()); + + if (!IS_3DO) { + // MIDI based + if (!_midiDriver) + return false; + + Common::String midiMusicName = (IS_SERRATED_SCALPEL) ? name + ".MUS" : name + ".XMI"; + Common::SeekableReadStream *stream = _vm->_res->load(midiMusicName, "MUSIC.LIB"); + + byte *midiMusicData = new byte[stream->size()]; + int32 midiMusicDataSize = stream->size(); + + stream->read(midiMusicData, midiMusicDataSize); + delete stream; + + // for dumping the music tracks +#if 0 + Common::DumpFile outFile; + outFile.open(name + ".RAW"); + outFile.write(data, stream->size()); + outFile.flush(); + outFile.close(); +#endif + + if (midiMusicDataSize < 14) { + warning("Music: not enough data in music file"); + delete[] midiMusicData; + return false; + } + + byte *dataPos = midiMusicData; + uint32 dataSize = midiMusicDataSize; + + if (IS_SERRATED_SCALPEL) { + if (memcmp(" ", dataPos, 12)) { + warning("Music: expected header not found in music file"); + delete[] midiMusicData; + return false; + } + dataPos += 12; + dataSize -= 12; + + if (dataSize < 0x7F) { + warning("Music: expected music header not found in music file"); + delete[] midiMusicData; + return false; + } + + uint16 headerSize = READ_LE_UINT16(dataPos); + if (headerSize != 0x7F) { + warning("Music: header is not as expected"); + delete[] midiMusicData; + return false; + } + } else { + if (memcmp("FORM", dataPos, 4)) { + warning("Music: expected header not found in music file"); + delete[] midiMusicData; + return false; + } + } + + if (IS_SERRATED_SCALPEL) { + // Pass the music data to the driver as well + // because channel mapping and a few other things inside the header + switch (_musicType) { + case MT_ADLIB: + MidiDriver_SH_AdLib_newMusicData(_midiDriver, dataPos, dataSize); + break; + + case MT_MT32: + MidiDriver_MT32_newMusicData(_midiDriver, dataPos, dataSize); + break; + + default: + // should never happen + break; + } + } + + _midiParser->loadMusic(midiMusicData, midiMusicDataSize); + + } else { + // 3DO: sample based + Audio::AudioStream *musicStream; + Common::String digitalMusicName = "music/" + name + "_MW22.aifc"; + + if (isPlaying()) { + _mixer->stopHandle(_digitalMusicHandle); + } + + Common::File *digitalMusicFile = new Common::File(); + if (!digitalMusicFile->open(digitalMusicName)) { + warning("playMusic: can not open 3DO music '%s'", digitalMusicName.c_str()); + return false; + } + + // Try to load the given file as AIFF/AIFC + musicStream = Audio::makeAIFFStream(digitalMusicFile, DisposeAfterUse::YES); + if (!musicStream) { + warning("playMusic: can not load 3DO music '%s'", digitalMusicName.c_str()); + return false; + } + _mixer->playStream(Audio::Mixer::kMusicSoundType, &_digitalMusicHandle, musicStream); + } + return true; +} + +void Music::stopMusic() { + freeSong(); +} + +void Music::startSong() { + if (!_musicOn) + return; + + // TODO + warning("TODO: Sound::startSong"); + _musicPlaying = true; +} + +void Music::freeSong() { + if (!IS_3DO) { + if (_midiParser->isPlaying()) + _midiParser->stopPlaying(); + + // Free the MIDI MUS data buffer + _midiParser->unloadMusic(); + } + + _musicPlaying = false; +} + +void Music::waitTimerRoland(uint time) { + // TODO + warning("TODO: Sound::waitTimerRoland"); +} + +bool Music::isPlaying() { + if (!IS_3DO) { + // MIDI based + return _midiParser->isPlaying(); + } else { + // 3DO: sample based + return _mixer->isSoundHandleActive(_digitalMusicHandle); + } +} + +// Returns the current music position in milliseconds +uint32 Music::getCurrentPosition() { + if (!IS_3DO) { + // MIDI based + return (_midiParser->getTick() * 1000) / 60; // translate tick to millisecond + } else { + // 3DO: sample based + return _mixer->getSoundElapsedTime(_digitalMusicHandle); + } +} + +// This is used to wait for the music in certain situations like especially the intro +// Note: the original game didn't do this, instead it just waited for certain amounts of time +// We do this, so that the intro graphics + music work together even on faster/slower hardware. +bool Music::waitUntilMSec(uint32 msecTarget, uint32 msecMax, uint32 additionalDelay, uint32 noMusicDelay) { + uint32 msecCurrent = 0; + + if (!isPlaying()) { + return _vm->_events->delay(noMusicDelay, true); + } + while (1) { + if (!isPlaying()) { // Music is not playing anymore -> we are done + if (additionalDelay > 0) { + if (!_vm->_events->delay(additionalDelay, true)) + return false; + } + return true; + } + + msecCurrent = getCurrentPosition(); + //warning("waitUntilMSec: %lx", msecCurrent); + + if ((!msecMax) || (msecCurrent <= msecMax)) { + if (msecCurrent >= msecTarget) { + if (additionalDelay > 0) { + if (!_vm->_events->delay(additionalDelay, true)) + return false; + } + return true; + } + } + if (!_vm->_events->delay(10, true)) + return false; + } +} + +void Music::setMIDIVolume(int volume) { + warning("TODO: Music::setMIDIVolume"); +} + +} // End of namespace Sherlock |