/* 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/algorithm.h" #include "common/config-manager.h" #include "common/mutex.h" #include "sherlock/sherlock.h" #include "sherlock/music.h" #include "sherlock/scalpel/drivers/mididriver.h" #include "audio/audiostream.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; _midiOption = false; _midiMusicData = nullptr; _musicVolume = ConfMan.hasKey("music_volume") ? ConfMan.getInt("music_volume") : 255; _musicOn = false; if (IS_3DO) { // 3DO - uses digital samples for music _musicOn = ConfMan.hasKey("music_mute") ? !ConfMan.getBool("music_mute") : 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 = ConfMan.hasKey("music_mute") ? !ConfMan.getBool("music_mute") : 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; _nextSongName = _currentSongName = name; 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; 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; } } _midiMusicData = midiMusicData; _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); } _musicPlaying = true; return true; } void Music::stopMusic() { freeSong(); } void Music::startSong() { // No implementation needed for ScummVM } void Music::freeSong() { if (!IS_3DO) { if (_midiParser->isPlaying()) _midiParser->stopPlaying(); // Free the MIDI MUS data buffer _midiParser->unloadMusic(); } _midiMusicData = nullptr; _musicPlaying = false; } 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::setMusicVolume(int volume) { _musicVolume = volume; _musicOn = volume > 0; _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, volume); } void Music::getSongNames(Common::StringArray &songs) { songs.clear(); if (IS_SERRATED_SCALPEL) { if (IS_3DO) { Common::FSDirectory gameDirectory(ConfMan.get("path")); Common::FSDirectory *musicDirectory = gameDirectory.getSubDirectory("music"); Common::ArchiveMemberList files; musicDirectory->listMatchingMembers(files, "*_mw22.aifc"); for (Common::ArchiveMemberList::iterator i = files.begin(); i != files.end(); ++i) { Common::String name = (*i)->getName(); name.erase(name.size() - 10); songs.push_back(name); } } else { for (int i = 0; i < ARRAYSIZE(SONG_NAMES); i++) { songs.push_back(SONG_NAMES[i]); } } } else { Common::StringArray fileList; _vm->_res->getResourceNames("music.lib", fileList); for (Common::StringArray::iterator i = fileList.begin(); i != fileList.end(); ++i) { if ((*i).matchString("*.XMI", true)) { (*i).erase((*i).size() - 4); songs.push_back(*i); } } } Common::sort(songs.begin(), songs.end()); } void Music::checkSongProgress() { if (!isPlaying()) { playMusic(_nextSongName); } } } // End of namespace Sherlock