diff options
Diffstat (limited to 'engines')
25 files changed, 1678 insertions, 134 deletions
diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp index 045fd9dac5..e5875a8353 100644 --- a/engines/agos/midi.cpp +++ b/engines/agos/midi.cpp @@ -42,6 +42,8 @@ MidiPlayer::MidiPlayer() { _driver = 0; _map_mt32_to_gm = false; + _adlibPatches = NULL; + _enable_sfx = true; _current = 0; @@ -68,6 +70,7 @@ MidiPlayer::~MidiPlayer() { } _driver = NULL; clearConstructs(); + unloadAdlibPatches(); } int MidiPlayer::open(int gameType) { @@ -85,6 +88,12 @@ int MidiPlayer::open(int gameType) { if (_nativeMT32) _driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); + /* Disabled due to not sounding right, and low volume level + if (gameType == GType_SIMON1 && MidiDriver::getMusicType(dev) == MT_ADLIB) { + loadAdlibPatches(); + } + */ + _map_mt32_to_gm = (gameType != GType_SIMON2 && !_nativeMT32); int ret = _driver->open(); @@ -114,8 +123,10 @@ void MidiPlayer::send(uint32 b) { else if (_current == &_music) volume = volume * _musicVolume / 255; b = (b & 0xFF00FFFF) | (volume << 16); - } else if ((b & 0xF0) == 0xC0 && _map_mt32_to_gm) { - b = (b & 0xFFFF00FF) | (MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8); + } else if ((b & 0xF0) == 0xC0) { + if (_map_mt32_to_gm && !_adlibPatches) { + b = (b & 0xFFFF00FF) | (MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8); + } } else if ((b & 0xFFF0) == 0x007BB0) { // Only respond to an All Notes Off if this channel // has already been allocated. @@ -144,7 +155,16 @@ void MidiPlayer::send(uint32 b) { else if (_current == &_music) _current->channel[9]->volume(_current->volume[9] * _musicVolume / 255); } - _current->channel[channel]->send(b); + + if ((b & 0xF0) == 0xC0 && _adlibPatches) { + // NOTE: In the percussion channel, this function is a + // no-op. Any percussion instruments you hear may + // be the stock ones from adlib.cpp. + _driver->sysEx_customInstrument(_current->channel[channel]->getNumber(), 'ADL ', _adlibPatches + 30 * ((b >> 8) & 0xFF)); + } else { + _current->channel[channel]->send(b); + } + if ((b & 0xFFF0) == 0x79B0) { // We have received a "Reset All Controllers" message // and passed it on to the MIDI driver. This may or may @@ -355,6 +375,47 @@ void MidiPlayer::resetVolumeTable() { } } +void MidiPlayer::loadAdlibPatches() { + Common::File ibk; + + if (!ibk.open("mt_fm.ibk")) + return; + + if (ibk.readUint32BE() == 0x49424b1a) { + _adlibPatches = new byte[128 * 30]; + byte *ptr = _adlibPatches; + + memset(_adlibPatches, 0, 128 * 30); + + for (int i = 0; i < 128; i++) { + byte instr[16]; + + ibk.read(instr, 16); + + ptr[0] = instr[0]; // Modulator Sound Characteristics + ptr[1] = instr[2]; // Modulator Scaling/Output Level + ptr[2] = ~instr[4]; // Modulator Attack/Decay + ptr[3] = ~instr[6]; // Modulator Sustain/Release + ptr[4] = instr[8]; // Modulator Wave Select + ptr[5] = instr[1]; // Carrier Sound Characteristics + ptr[6] = instr[3]; // Carrier Scaling/Output Level + ptr[7] = ~instr[5]; // Carrier Attack/Delay + ptr[8] = ~instr[7]; // Carrier Sustain/Release + ptr[9] = instr[9]; // Carrier Wave Select + ptr[10] = instr[10]; // Feedback/Connection + + // The remaining six bytes are reserved for future use + + ptr += 30; + } + } +} + +void MidiPlayer::unloadAdlibPatches() { + delete[] _adlibPatches; + _adlibPatches = NULL; +} + static const int simon1_gmf_size[] = { 8900, 12166, 2848, 3442, 4034, 4508, 7064, 9730, 6014, 4742, 3138, 6570, 5384, 8909, 6457, 16321, 2742, 8968, 4804, 8442, 7717, diff --git a/engines/agos/midi.h b/engines/agos/midi.h index 398e445535..7e78bfef28 100644 --- a/engines/agos/midi.h +++ b/engines/agos/midi.h @@ -77,11 +77,15 @@ protected: byte _queuedTrack; bool _loopQueuedTrack; + byte *_adlibPatches; + protected: static void onTimer(void *data); void clearConstructs(); void clearConstructs(MusicInfo &info); void resetVolumeTable(); + void loadAdlibPatches(); + void unloadAdlibPatches(); public: bool _enable_sfx; diff --git a/engines/sherlock/module.mk b/engines/sherlock/module.mk index cee48ae2d3..9ebe416c4b 100644 --- a/engines/sherlock/module.mk +++ b/engines/sherlock/module.mk @@ -3,6 +3,7 @@ MODULE := engines/sherlock MODULE_OBJS = \ scalpel/darts.o \ scalpel/scalpel.o \ + scalpel/drivers/adlib.o \ scalpel/scalpel_scene.o \ scalpel/scalpel_user_interface.o \ scalpel/settings.o \ @@ -16,6 +17,7 @@ MODULE_OBJS = \ inventory.o \ journal.o \ map.o \ + music.o \ objects.o \ people.o \ resources.o \ diff --git a/engines/sherlock/music.cpp b/engines/sherlock/music.cpp new file mode 100644 index 0000000000..3e31bcc83d --- /dev/null +++ b/engines/sherlock/music.cpp @@ -0,0 +1,363 @@ +/* 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 "sherlock/sherlock.h" +#include "sherlock/music.h" +#include "sherlock/scalpel/drivers/mididriver.h" + +namespace Sherlock { + +#define NUM_SONGS 45 + +#define USE_SCI_MIDI_PLAYER 1 + +/* 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; +} + +void MidiParser_SH::parseNextEvent(EventInfo &info) { +// warning("parseNextEvent"); + + // An attempt to remap MT32 instruments to GMIDI. Only partially successful, it still + // does not sound even close to the real MT32. Oddly enough, on the actual hardware MT32 + // and SB sound very differently. + static const byte mt32Map[128] = { + 0, 1, 0, 2, 4, 4, 5, 3, /* 0-7 */ + 16, 17, 18, 16, 16, 19, 20, 21, /* 8-15 */ + 6, 6, 6, 7, 7, 7, 8, 112, /* 16-23 */ + 62, 62, 63, 63 , 38, 38, 39, 39, /* 24-31 */ + 88, 95, 52, 98, 97, 99, 14, 54, /* 32-39 */ + 102, 96, 53, 102, 81, 100, 14, 80, /* 40-47 */ + 48, 48, 49, 45, 41, 40, 42, 42, /* 48-55 */ + 43, 46, 45, 24, 25, 28, 27, 104, /* 56-63 */ + 32, 32, 34, 33, 36, 37, 35, 35, /* 64-71 */ + 79, 73, 72, 72, 74, 75, 64, 65, /* 72-79 */ + 66, 67, 71, 71, 68, 69, 70, 22, /* 80-87 */ + 56, 59, 57, 57, 60, 60, 58, 61, /* 88-95 */ + 61, 11, 11, 98, 14, 9, 14, 13, /* 96-103 */ + 12, 107, 107, 77, 78, 78, 76, 76, /* 104-111 */ + 47, 117, 127, 118, 118, 116, 115, 119, /* 112-119 */ + 115, 112, 55, 124, 123, 0, 14, 117 /* 120-127 */ + }; + + + info.start = _position._playPos; + info.delta = 0; + + 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; + // don't do this here, it breaks adlib + //info.basic.param1 = mt32Map[idx & 0x7f]; // remap MT32 to GM + 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) { + 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) { + allNotesOff(); + stopPlaying(); + unloadMusic(); + return; + } else { + warning("TODO: %x / Unknown", info.event); + break; + } + break; + default: + warning("MidiParser_SH::parseNextEvent: Unsupported event code %x", info.event); + break; + }// switch (info.command()) + + info.delta = *(_position._playPos++); +} + +bool MidiParser_SH::loadMusic(byte *data, uint32 size) { + warning("loadMusic"); + unloadMusic(); + + byte *headerPtr = data; + byte *pos = data; + uint16 headerSize = READ_LE_UINT16(headerPtr); + assert(headerSize == 0x7F); + + // Skip over header + pos += headerSize; + + _lastEvent = 0; + _trackEnd = data + size; + + _numTracks = 1; + _tracks[0] = pos; + + _ppqn = 1; + setTempo(16667); + setTrack(0); + + return true; +} + +/*----------------------------------------------------------------*/ + +Music::Music(SherlockEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) { + if (_vm->_interactiveFl) + _vm->_res->addToCache("MUSIC.LIB"); + + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM); + + _musicType = MidiDriver::getMusicType(dev); + +#if USE_SCI_MIDI_PLAYER + _pMidiDrv = NULL; +#endif + _driver = NULL; + + switch (_musicType) { + case MT_ADLIB: +#if USE_SCI_MIDI_PLAYER + _pMidiDrv = MidiPlayer_AdLib_create(); +#else + _driver = MidiDriver_AdLib_create(); +#endif + break; + default: + _driver = MidiDriver::createMidi(dev); + break; + } +#if USE_SCI_MIDI_PLAYER + if (_pMidiDrv) { + assert(_pMidiDrv); + int ret = _pMidiDrv->open(); + if (ret == 0) { + _pMidiDrv->setTimerCallback(&_midiParser, &_midiParser.timerCallback); + } + _midiParser.setMidiDriver(_pMidiDrv); + _midiParser.setTimerRate(_pMidiDrv->getBaseTempo()); + } +#endif + + if (_driver) { + assert(_driver); + + int ret = _driver->open(); + if (ret == 0) { + _driver->sendGMReset(); + _driver->setTimerCallback(&_midiParser, &_midiParser.timerCallback); + } + _midiParser.setMidiDriver(_driver); + _midiParser.setTimerRate(_driver->getBaseTempo()); + } + + _musicPlaying = false; + _musicOn = true; +} + +bool Music::loadSong(int songNumber) { + warning("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]) + ".MUS"; + + freeSong(); // free any song that is currently loaded + + if (!playMusic(songName)) + return false; + + stopMusic(); + startSong(); + return true; +} + +bool Music::loadSong(const Common::String &songName) { + warning("TODO: Music::loadSong"); + return false; +} + +void Music::syncMusicSettings() { + _musicOn = !ConfMan.getBool("mute") && !ConfMan.getBool("music_mute"); +} + +bool Music::playMusic(const Common::String &name) { + if (!_musicOn) + return false; + + warning("Sound::playMusic %s", name.c_str()); + Common::SeekableReadStream *stream = _vm->_res->load(name, "MUSIC.LIB"); + + byte *data = new byte[stream->size()]; + int32 dataSize = stream->size(); + assert(data); + + stream->read(data, dataSize); + + // 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 (dataSize < 14) { + warning("not enough data in music file"); + return false; + } + + byte *dataPos = data; + if (memcmp(" ", dataPos, 12)) { + warning("Expected header not found in music file"); + return false; + } + dataPos += 12; + dataSize -= 12; + + uint16 headerSize = READ_LE_UINT16(dataPos); + if (headerSize != 0x7F) { + warning("music header is not as expected"); + return false; + } + + if (_musicType == MT_ADLIB) { + if (_driver) + MidiDriver_AdLib_newMusicData(_driver, dataPos, dataSize); +#if USE_SCI_MIDI_PLAYER + if (_pMidiDrv) + MidiPlayer_AdLib_newMusicData(_pMidiDrv, dataPos, dataSize); +#endif + } + + _midiParser.loadMusic(dataPos, dataSize); + return true; +} + +void Music::stopMusic() { + // TODO + warning("TODO: Sound::stopMusic"); + + _musicPlaying = false; +} + +void Music::startSong() { + if (!_musicOn) + return; + + // TODO + warning("TODO: Sound::startSong"); + _musicPlaying = true; +} + +void Music::freeSong() { + // TODO + warning("TODO: Sound::freeSong"); +} + +void Music::waitTimerRoland(uint time) { + // TODO + warning("TODO: Sound::waitTimerRoland"); +}} // End of namespace Sherlock + diff --git a/engines/sherlock/music.h b/engines/sherlock/music.h new file mode 100644 index 0000000000..c3710603cc --- /dev/null +++ b/engines/sherlock/music.h @@ -0,0 +1,107 @@ +/* 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. + * + */ + +#ifndef SHERLOCK_MUSIC_H +#define SHERLOCK_MUSIC_H + +#include "audio/midiplayer.h" +#include "audio/midiparser.h" +//#include "audio/mididrv.h" +#include "sherlock/scalpel/drivers/mididriver.h" + +namespace Sherlock { + +class SherlockEngine; + +class MidiParser_SH : public MidiParser { +protected: + virtual void parseNextEvent(EventInfo &info); + + uint8 _beats; + uint8 _lastEvent; + byte *_data; + byte *_trackEnd; +public: + MidiParser_SH(); + virtual bool loadMusic(byte *data, uint32 size); +}; + +class Music { +private: + SherlockEngine *_vm; + Audio::Mixer *_mixer; + MidiParser_SH _midiParser; + MidiPlayer *_pMidiDrv; + MidiDriver *_driver; + +public: + bool _musicPlaying; + bool _musicOn; + +private: + MusicType _musicType; + +public: + Music(SherlockEngine *vm, Audio::Mixer *mixer); + + /** + * Saves sound-related settings + */ + void syncMusicSettings(); + + /** + * Load a specified song + */ + bool loadSong(int songNumber); + + /** + * Load a specified song + */ + bool loadSong(const Common::String &songName); + + /** + * Start playing a song + */ + void startSong(); + + /** + * Free any currently loaded song + */ + void freeSong(); + + /** + * Play the specified music resource + */ + bool playMusic(const Common::String &name); + + /** + * Stop playing the music + */ + void stopMusic(); + + void waitTimerRoland(uint time); +}; + +} // End of namespace Sherlock + +#endif + diff --git a/engines/sherlock/scalpel/darts.cpp b/engines/sherlock/scalpel/darts.cpp index b567d58ab4..8d78335a55 100644 --- a/engines/sherlock/scalpel/darts.cpp +++ b/engines/sherlock/scalpel/darts.cpp @@ -377,13 +377,13 @@ void Darts::erasePowerBars() { int Darts::doPowerBar(const Common::Point &pt, byte color, int goToPower, bool isVertical) { Events &events = *_vm->_events; Screen &screen = *_vm->_screen; - Sound &sound = *_vm->_sound; + Music &music = *_vm->_music; bool done; int idx = 0; events.clearEvents(); - if (sound._musicOn) - sound.waitTimerRoland(10); + if (music._musicOn) + music.waitTimerRoland(10); else events.delay(100); @@ -410,9 +410,9 @@ int Darts::doPowerBar(const Common::Point &pt, byte color, int goToPower, bool i screen.slamArea(pt.x + idx, pt.y, 1, 8); } - if (sound._musicOn) { + if (music._musicOn) { if (!(idx % 3)) - sound.waitTimerRoland(1); + music.waitTimerRoland(1); } else if (!(idx % 8)) events.wait(1); diff --git a/engines/sherlock/scalpel/drivers/adlib.cpp b/engines/sherlock/scalpel/drivers/adlib.cpp new file mode 100644 index 0000000000..7ee5256138 --- /dev/null +++ b/engines/sherlock/scalpel/drivers/adlib.cpp @@ -0,0 +1,532 @@ +/* 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 "sherlock/sherlock.h" +#include "sherlock/scalpel/drivers/mididriver.h" + +#include "common/file.h" +#include "common/system.h" +#include "common/textconsole.h" + +#include "audio/fmopl.h" +#include "audio/softsynth/emumidi.h" + +namespace Sherlock { + +#define USE_SCI_MIDI_PLAYER 1 + +#define SHERLOCK_ADLIB_VOICES_COUNT 9 +#define SHERLOCK_ADLIB_NOTES_COUNT 96 + +byte adlib_Operator1Register[SHERLOCK_ADLIB_VOICES_COUNT] = { + 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12 +}; + +byte adlib_Operator2Register[SHERLOCK_ADLIB_VOICES_COUNT] = { + 0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15 +}; + +struct adlib_InstrumentEntry { + byte reg20op1; + byte reg40op1; + byte reg60op1; + byte reg80op1; + byte regE0op1; + byte reg20op2; + byte reg40op2; + byte reg60op2; + byte reg80op2; + byte regE0op2; + byte regC0; + byte frequencyAdjust; +}; + +// hardcoded, dumped from ADHOM.DRV +const adlib_InstrumentEntry adlib_instrumentTable[] = { + { 0x71, 0x89, 0x51, 0x11, 0x00, 0x61, 0x23, 0x42, 0x15, 0x01, 0x02, 0xF4 }, + { 0x22, 0x20, 0x97, 0x89, 0x00, 0xA2, 0x1F, 0x70, 0x07, 0x00, 0x0A, 0xF4 }, + { 0x70, 0x1A, 0x64, 0x13, 0x00, 0x20, 0x1F, 0x53, 0x46, 0x00, 0x0E, 0xF4 }, + { 0xB6, 0x4A, 0xB6, 0x32, 0x00, 0x11, 0x2B, 0xD1, 0x31, 0x00, 0x0E, 0xE8 }, + { 0x71, 0x8B, 0x51, 0x11, 0x00, 0x61, 0x20, 0x32, 0x35, 0x01, 0x02, 0xF4 }, + { 0x71, 0x8A, 0x51, 0x11, 0x00, 0x61, 0x20, 0x32, 0x25, 0x01, 0x02, 0xF4 }, + { 0x23, 0x0F, 0xF4, 0x04, 0x02, 0x2F, 0x25, 0xF0, 0x43, 0x00, 0x06, 0xE8 }, + { 0x71, 0x1C, 0x71, 0x03, 0x00, 0x21, 0x1F, 0x54, 0x17, 0x00, 0x0E, 0xF4 }, + { 0x71, 0x8A, 0x6E, 0x17, 0x00, 0x25, 0x27, 0x6B, 0x0E, 0x00, 0x02, 0xF4 }, + { 0x71, 0x1D, 0x81, 0x03, 0x00, 0x21, 0x1F, 0x64, 0x17, 0x00, 0x0E, 0xF4 }, + { 0x01, 0x4B, 0xF1, 0x50, 0x00, 0x01, 0x23, 0xD2, 0x76, 0x00, 0x06, 0xF4 }, + { 0x2F, 0xCA, 0xF8, 0xE5, 0x00, 0x21, 0x1F, 0xC0, 0xFF, 0x00, 0x00, 0xF4 }, + { 0x29, 0xCD, 0xF0, 0x91, 0x00, 0x21, 0x1F, 0xE0, 0x86, 0x00, 0x02, 0xF4 }, + { 0x24, 0xD0, 0xF0, 0x01, 0x00, 0x21, 0x1F, 0xE0, 0x86, 0x00, 0x02, 0xF4 }, + { 0x23, 0xC8, 0xF0, 0x01, 0x00, 0x21, 0x1F, 0xE0, 0x86, 0x00, 0x02, 0xF4 }, + { 0x64, 0xC9, 0xB0, 0x01, 0x00, 0x61, 0x1F, 0xF0, 0x86, 0x00, 0x02, 0xF4 }, + { 0x33, 0x85, 0xA1, 0x10, 0x00, 0x15, 0x9F, 0x72, 0x23, 0x00, 0x08, 0xF4 }, + { 0x31, 0x85, 0xA1, 0x10, 0x00, 0x15, 0x9F, 0x73, 0x33, 0x00, 0x08, 0xF4 }, + { 0x31, 0x81, 0xA1, 0x30, 0x00, 0x16, 0x9F, 0xC2, 0x74, 0x00, 0x08, 0xF4 }, + { 0x03, 0x8A, 0xF0, 0x7B, 0x00, 0x02, 0x9F, 0xF4, 0x7B, 0x00, 0x08, 0xF4 }, + { 0x03, 0x8A, 0xF0, 0x7B, 0x00, 0x01, 0x9F, 0xF4, 0x7B, 0x00, 0x08, 0xF4 }, + { 0x23, 0x8A, 0xF2, 0x7B, 0x00, 0x01, 0x9F, 0xF4, 0x7B, 0x00, 0x08, 0xF4 }, + { 0x32, 0x80, 0x01, 0x10, 0x00, 0x12, 0x9F, 0x72, 0x33, 0x00, 0x08, 0xF4 }, + { 0x32, 0x80, 0x01, 0x10, 0x00, 0x14, 0x9F, 0x73, 0x33, 0x00, 0x08, 0xF4 }, + { 0x31, 0x16, 0x73, 0x8E, 0x00, 0x21, 0x1F, 0x80, 0x9E, 0x00, 0x0E, 0xF4 }, + { 0x30, 0x16, 0x73, 0x7E, 0x00, 0x21, 0x1F, 0x80, 0x9E, 0x00, 0x0E, 0x00 }, + { 0x31, 0x94, 0x33, 0x73, 0x00, 0x21, 0x1F, 0xA0, 0x97, 0x00, 0x0E, 0xF4 }, + { 0x31, 0x94, 0xD3, 0x73, 0x00, 0x21, 0x20, 0xA0, 0x97, 0x00, 0x0E, 0xF4 }, + { 0x31, 0x45, 0xF1, 0x53, 0x00, 0x32, 0x1F, 0xF2, 0x27, 0x00, 0x06, 0xF4 }, + { 0x13, 0x0C, 0xF2, 0x01, 0x00, 0x15, 0x2F, 0xF2, 0xB6, 0x00, 0x08, 0xF4 }, + { 0x11, 0x0C, 0xF2, 0x01, 0x00, 0x11, 0x1F, 0xF2, 0xB6, 0x00, 0x08, 0xF4 }, + { 0x11, 0x0A, 0xFE, 0x04, 0x00, 0x11, 0x1F, 0xF2, 0xBD, 0x00, 0x08, 0xF4 }, + { 0x16, 0x4D, 0xFA, 0x11, 0x00, 0xE1, 0x20, 0xF1, 0xF1, 0x00, 0x08, 0xF4 }, + { 0x16, 0x40, 0xBA, 0x11, 0x00, 0xF1, 0x20, 0x24, 0x31, 0x00, 0x08, 0xF4 }, + { 0x61, 0xA7, 0x72, 0x8E, 0x00, 0xE1, 0x9F, 0x50, 0x1A, 0x00, 0x02, 0xF4 }, + { 0x18, 0x4D, 0x32, 0x13, 0x00, 0xE1, 0x20, 0x51, 0xE3, 0x00, 0x08, 0xF4 }, + { 0x17, 0xC0, 0x12, 0x41, 0x00, 0x31, 0x9F, 0x13, 0x31, 0x00, 0x06, 0xF4 }, + { 0x03, 0x8F, 0xF5, 0x55, 0x00, 0x21, 0x9F, 0xF3, 0x33, 0x00, 0x00, 0xF4 }, + { 0x13, 0x4D, 0xFA, 0x11, 0x00, 0xE1, 0x20, 0xF1, 0xF1, 0x00, 0x08, 0xF4 }, + { 0x11, 0x43, 0x20, 0x15, 0x00, 0xF1, 0x20, 0x31, 0xF8, 0x00, 0x08, 0xF4 }, + { 0x11, 0x03, 0x82, 0x97, 0x00, 0xE4, 0x60, 0xF0, 0xF2, 0x00, 0x08, 0xF4 }, + { 0x05, 0x40, 0xD1, 0x53, 0x00, 0x14, 0x1F, 0x51, 0x71, 0x00, 0x06, 0xF4 }, + { 0xF1, 0x01, 0x77, 0x17, 0x00, 0x21, 0x1F, 0x81, 0x18, 0x00, 0x02, 0xF4 }, + { 0xF1, 0x18, 0x32, 0x11, 0x00, 0xE1, 0x1F, 0xF1, 0x13, 0x00, 0x00, 0xF4 }, + { 0x73, 0x48, 0xF1, 0x53, 0x00, 0x71, 0x1F, 0xF1, 0x06, 0x00, 0x08, 0xF4 }, + { 0x71, 0x8D, 0x71, 0x11, 0x00, 0x61, 0x5F, 0x72, 0x15, 0x00, 0x06, 0xF4 }, + { 0xD7, 0x4F, 0xF2, 0x61, 0x00, 0xD2, 0x1F, 0xF1, 0xB2, 0x00, 0x08, 0xF4 }, + { 0x01, 0x11, 0xF0, 0xFF, 0x00, 0x01, 0x1F, 0xF0, 0xF8, 0x00, 0x0A, 0xF4 }, + { 0x31, 0x8B, 0x41, 0x11, 0x00, 0x61, 0x1F, 0x22, 0x13, 0x00, 0x06, 0xF4 }, + { 0x71, 0x1C, 0x71, 0x03, 0x00, 0x21, 0x1F, 0x64, 0x07, 0x00, 0x0E, 0xF4 }, + { 0x31, 0x8B, 0x41, 0x11, 0x00, 0x61, 0x1F, 0x32, 0x15, 0x00, 0x02, 0xF4 }, + { 0x71, 0x1C, 0xFD, 0x13, 0x00, 0x21, 0x1F, 0xE7, 0xD6, 0x00, 0x0E, 0xF4 }, + { 0x71, 0x1C, 0x51, 0x03, 0x00, 0x21, 0x1F, 0x54, 0x67, 0x00, 0x0E, 0xF4 }, + { 0x71, 0x1C, 0x51, 0x03, 0x00, 0x21, 0x1F, 0x54, 0x17, 0x00, 0x0E, 0xF4 }, + { 0x71, 0x1C, 0x54, 0x15, 0x00, 0x21, 0x1F, 0x53, 0x49, 0x00, 0x0E, 0xF4 }, + { 0x71, 0x56, 0x51, 0x03, 0x00, 0x61, 0x1F, 0x54, 0x17, 0x00, 0x0E, 0xF4 }, + { 0x71, 0x1C, 0x51, 0x03, 0x00, 0x21, 0x1F, 0x54, 0x17, 0x00, 0x0E, 0xF4 }, + { 0x02, 0x29, 0xF5, 0x75, 0x00, 0x01, 0x9F, 0xF2, 0xF3, 0x00, 0x00, 0xF4 }, + { 0x02, 0x29, 0xF0, 0x75, 0x00, 0x01, 0x9F, 0xF4, 0x33, 0x00, 0x00, 0xF4 }, + { 0x01, 0x49, 0xF1, 0x53, 0x00, 0x11, 0x1F, 0xF1, 0x74, 0x00, 0x06, 0xF4 }, + { 0x01, 0x89, 0xF1, 0x53, 0x00, 0x11, 0x1F, 0xF1, 0x74, 0x00, 0x06, 0xF4 }, + { 0x02, 0x89, 0xF1, 0x53, 0x00, 0x11, 0x1F, 0xF1, 0x74, 0x00, 0x06, 0xF4 }, + { 0x02, 0x80, 0xF1, 0x53, 0x00, 0x11, 0x1F, 0xF1, 0x74, 0x00, 0x06, 0xF4 }, + { 0x01, 0x40, 0xF1, 0x53, 0x00, 0x08, 0x5F, 0xF1, 0x53, 0x00, 0x00, 0xF4 }, + { 0x21, 0x15, 0xD3, 0x2C, 0x00, 0x21, 0x9F, 0xC3, 0x2C, 0x00, 0x0A, 0xF4 }, + { 0x01, 0x18, 0xD4, 0xF2, 0x00, 0x21, 0x9F, 0xC4, 0x8A, 0x00, 0x0A, 0xF4 }, + { 0x01, 0x4E, 0xF0, 0x7B, 0x00, 0x11, 0x1F, 0xF4, 0xC8, 0x00, 0x04, 0xF4 }, + { 0x01, 0x44, 0xF0, 0xAB, 0x00, 0x11, 0x1F, 0xF3, 0xAB, 0x00, 0x04, 0xF4 }, + { 0x53, 0x0E, 0xF4, 0xC8, 0x00, 0x11, 0x1F, 0xF1, 0xBB, 0x00, 0x04, 0xF4 }, + { 0x53, 0x0B, 0xF2, 0xC8, 0x00, 0x11, 0x1F, 0xF2, 0xC5, 0x00, 0x04, 0xF4 }, + { 0x21, 0x15, 0xB4, 0x4C, 0x00, 0x21, 0x1F, 0x94, 0xAC, 0x00, 0x0A, 0xF4 }, + { 0x21, 0x15, 0x94, 0x1C, 0x00, 0x21, 0x1F, 0x64, 0xAC, 0x00, 0x0A, 0xF4 }, + { 0x22, 0x1B, 0x97, 0x89, 0x00, 0xA2, 0x1F, 0x70, 0x07, 0x00, 0x0A, 0xF4 }, + { 0x21, 0x19, 0x77, 0xBF, 0x00, 0xA1, 0x9F, 0x60, 0x2A, 0x00, 0x06, 0xF4 }, + { 0xA1, 0x13, 0xD6, 0xAF, 0x00, 0xE2, 0x9F, 0x60, 0x2A, 0x00, 0x02, 0xF4 }, + { 0xA2, 0x1D, 0x95, 0x24, 0x00, 0xE2, 0x9F, 0x60, 0x2A, 0x00, 0x02, 0xF4 }, + { 0x32, 0x9A, 0x51, 0x19, 0x00, 0x61, 0x9F, 0x60, 0x39, 0x00, 0x0C, 0xF4 }, + { 0xA4, 0x12, 0xF4, 0x30, 0x00, 0xE2, 0x9F, 0x60, 0x2A, 0x00, 0x02, 0xF4 }, + { 0x21, 0x16, 0x63, 0x0E, 0x00, 0x21, 0x1F, 0x63, 0x0E, 0x00, 0x0C, 0xF4 }, + { 0x31, 0x16, 0x63, 0x0A, 0x00, 0x21, 0x1F, 0x63, 0x0B, 0x00, 0x0C, 0xF4 }, + { 0x21, 0x1B, 0x63, 0x0A, 0x00, 0x21, 0x1F, 0x63, 0x0B, 0x00, 0x0C, 0xF4 }, + { 0x20, 0x1B, 0x63, 0x0A, 0x00, 0x21, 0x1F, 0x63, 0x0B, 0x00, 0x0C, 0xF4 }, + { 0x32, 0x1C, 0x82, 0x18, 0x00, 0x61, 0x9F, 0x60, 0x07, 0x00, 0x0C, 0xF4 }, + { 0x32, 0x18, 0x61, 0x14, 0x00, 0xE1, 0x9F, 0x72, 0x16, 0x00, 0x0C, 0xF4 }, + { 0x31, 0xC0, 0x77, 0x17, 0x00, 0x22, 0x1F, 0x6B, 0x09, 0x00, 0x02, 0xF4 }, + { 0x71, 0xC3, 0x8E, 0x17, 0x00, 0x22, 0x24, 0x8B, 0x0E, 0x00, 0x02, 0xF4 }, + { 0x70, 0x8D, 0x6E, 0x17, 0x00, 0x22, 0x1F, 0x6B, 0x0E, 0x00, 0x02, 0xF4 }, + { 0x24, 0x4F, 0xF2, 0x06, 0x00, 0x31, 0x1F, 0x52, 0x06, 0x00, 0x0E, 0xF4 }, + { 0x31, 0x1B, 0x64, 0x07, 0x00, 0x61, 0x1F, 0xD0, 0x67, 0x00, 0x0E, 0xF4 }, + { 0x31, 0x1B, 0x61, 0x06, 0x00, 0x61, 0x1F, 0xD2, 0x36, 0x00, 0x0C, 0xF4 }, + { 0x31, 0x1F, 0x31, 0x06, 0x00, 0x61, 0x1F, 0x50, 0x36, 0x00, 0x0C, 0xF4 }, + { 0x31, 0x1F, 0x41, 0x06, 0x00, 0x61, 0x1F, 0xA0, 0x36, 0x00, 0x0C, 0xF4 }, + { 0x21, 0x9A, 0x53, 0x56, 0x00, 0x21, 0x9F, 0xA0, 0x16, 0x00, 0x0E, 0xF4 }, + { 0x21, 0x9A, 0x53, 0x56, 0x00, 0x21, 0x9F, 0xA0, 0x16, 0x00, 0x0E, 0xF4 }, + { 0x61, 0x19, 0x53, 0x58, 0x00, 0x21, 0x1F, 0xA0, 0x18, 0x00, 0x0C, 0xF4 }, + { 0x61, 0x19, 0x73, 0x57, 0x00, 0x21, 0x1F, 0xA0, 0x17, 0x00, 0x0C, 0xF4 }, + { 0x21, 0x1B, 0x71, 0xA6, 0x00, 0x21, 0x1F, 0xA1, 0x96, 0x00, 0x0E, 0xF4 }, + { 0x85, 0x91, 0xF5, 0x44, 0x00, 0xA1, 0x1F, 0xF0, 0x45, 0x00, 0x06, 0xF4 }, + { 0x07, 0x51, 0xF5, 0x33, 0x00, 0x61, 0x1F, 0xF0, 0x25, 0x00, 0x06, 0xF4 }, + { 0x13, 0x8C, 0xFF, 0x21, 0x00, 0x11, 0x9F, 0xFF, 0x03, 0x00, 0x0E, 0xF4 }, + { 0x38, 0x8C, 0xF3, 0x0D, 0x00, 0xB1, 0x5F, 0xF5, 0x33, 0x00, 0x0E, 0xF4 }, + { 0x87, 0x91, 0xF5, 0x55, 0x00, 0x22, 0x1F, 0xF0, 0x54, 0x00, 0x06, 0xF4 }, + { 0xB6, 0x4A, 0xB6, 0x32, 0x00, 0x11, 0x2B, 0xD1, 0x31, 0x00, 0x0E, 0xF4 }, + { 0x04, 0x00, 0xFE, 0xF0, 0x00, 0xC2, 0x1F, 0xF6, 0xB5, 0x00, 0x0E, 0xF4 }, + { 0x05, 0x4E, 0xDA, 0x15, 0x00, 0x01, 0x9F, 0xF0, 0x13, 0x00, 0x0A, 0xF4 }, + { 0x31, 0x44, 0xF2, 0x9A, 0x00, 0x32, 0x1F, 0xF0, 0x27, 0x00, 0x06, 0xF4 }, + { 0xB0, 0xC4, 0xA4, 0x02, 0x00, 0xD7, 0x9F, 0x40, 0x42, 0x00, 0x00, 0xF4 }, + { 0xCA, 0x84, 0xF0, 0xF0, 0x00, 0xCF, 0x1F, 0x59, 0x62, 0x00, 0x0C, 0xF4 }, + { 0x30, 0x35, 0xF5, 0xF0, 0x00, 0x35, 0x1F, 0xF0, 0x9B, 0x00, 0x02, 0xF4 }, + { 0x63, 0x0F, 0xF4, 0x04, 0x02, 0x6F, 0x1F, 0xF0, 0x43, 0x00, 0x06, 0xF4 }, + { 0x07, 0x40, 0x09, 0x53, 0x00, 0x05, 0x1F, 0xF6, 0x94, 0x00, 0x0E, 0xF4 }, + { 0x09, 0x4E, 0xDA, 0x25, 0x00, 0x01, 0x1F, 0xF1, 0x15, 0x00, 0x0A, 0xF4 }, + { 0x04, 0x00, 0xF3, 0xA0, 0x02, 0x04, 0x1F, 0xF8, 0x46, 0x00, 0x0E, 0xF4 }, + { 0x07, 0x00, 0xF0, 0xF0, 0x00, 0x00, 0x1F, 0x5C, 0xDC, 0x00, 0x0E, 0xF4 }, + { 0x1F, 0x1E, 0xE5, 0x5B, 0x00, 0x0F, 0x1F, 0x5D, 0xFA, 0x00, 0x0E, 0xF4 }, + { 0x11, 0x8A, 0xF1, 0x11, 0x00, 0x01, 0x5F, 0xF1, 0xB3, 0x00, 0x06, 0xF4 }, + { 0x00, 0x40, 0xD1, 0x53, 0x00, 0x00, 0x1F, 0xF2, 0x56, 0x00, 0x0E, 0xF4 }, + { 0x32, 0x44, 0xF8, 0xFF, 0x00, 0x11, 0x1F, 0xF5, 0x7F, 0x00, 0x0E, 0xF4 }, + { 0x00, 0x40, 0x09, 0x53, 0x00, 0x02, 0x1F, 0xF7, 0x94, 0x00, 0x0E, 0xF4 }, + { 0x11, 0x86, 0xF2, 0xA8, 0x00, 0x01, 0x9F, 0xA0, 0xA8, 0x00, 0x08, 0xF4 }, + { 0x00, 0x50, 0xF2, 0x70, 0x00, 0x13, 0x1F, 0xF2, 0x72, 0x00, 0x0E, 0xF4 }, + { 0xF0, 0x00, 0x11, 0x11, 0x00, 0xE0, 0xDF, 0x11, 0x11, 0x00, 0x0E, 0xF4 } +}; + +// hardcoded, dumped from ADHOM.DRV +uint16 adlib_FrequencyLookUpTable[SHERLOCK_ADLIB_NOTES_COUNT] = { + 0x0158, 0x016C, 0x0182, 0x0199, 0x01B1, 0x01CB, 0x01E6, 0x0203, 0x0222, 0x0242, + 0x0265, 0x0289, 0x0558, 0x056C, 0x0582, 0x0599, 0x05B1, 0x05CB, 0x05E6, 0x0603, + 0x0622, 0x0642, 0x0665, 0x0689, 0x0958, 0x096C, 0x0982, 0x0999, 0x09B1, 0x09CB, + 0x09E6, 0x0A03, 0x0A22, 0x0A42, 0x0A65, 0x0A89, 0x0D58, 0x0D6C, 0x0D82, 0x0D99, + 0x0DB1, 0x0DCB, 0x0DE6, 0x0E03, 0x0E22, 0x0E42, 0x0E65, 0x0E89, 0x1158, 0x116C, + 0x1182, 0x1199, 0x11B1, 0x11CB, 0x11E6, 0x1203, 0x1222, 0x1242, 0x1265, 0x1289, + 0x1558, 0x156C, 0x1582, 0x1599, 0x15B1, 0x15CB, 0x15E6, 0x1603, 0x1622, 0x1642, + 0x1665, 0x1689, 0x1958, 0x196C, 0x1982, 0x1999, 0x19B1, 0x19CB, 0x19E6, 0x1A03, + 0x1A22, 0x1A42, 0x1A65, 0x1A89, 0x1D58, 0x1D6C, 0x1D82, 0x1D99, 0x1DB1, 0x1DCB, + 0x1DE6, 0x1E03, 0x1E22, 0x1E42, 0x1E65, 0x1E89 +}; + +class MidiDriver_AdLib : public MidiDriver_Emulated { +public: + MidiDriver_AdLib(Audio::Mixer *mixer) + : MidiDriver_Emulated(mixer), _masterVolume(15), _rhythmKeyMap(0), _opl(0) { + memset(_voiceChannelMapping, 0, sizeof(_voiceChannelMapping)); + } + virtual ~MidiDriver_AdLib() { } + + // MidiDriver + int open(); + void close(); + void send(uint32 b); + MidiChannel *allocateChannel() { return NULL; } + MidiChannel *getPercussionChannel() { return NULL; } + + // AudioStream + bool isStereo() const { return false; } + int getRate() const { return _mixer->getOutputRate(); } + int getPolyphony() const { return SHERLOCK_ADLIB_VOICES_COUNT; } + bool hasRhythmChannel() const { return false; } + + // MidiDriver_Emulated + void generateSamples(int16 *buf, int len); + + void setVolume(byte volume); + virtual uint32 property(int prop, uint32 param); + + bool useRhythmChannel() const { return _rhythmKeyMap != NULL; } + + void newMusicData(byte *musicData, int32 musicDataSize); + +private: + struct adlib_ChannelEntry { + bool inUse; + const adlib_InstrumentEntry *currentInstrumentPtr; + byte currentNote; + byte currentA0hReg; + byte currentB0hReg; + + adlib_ChannelEntry() : inUse(false), currentInstrumentPtr(NULL), currentNote(0), + currentA0hReg(0), currentB0hReg(0) { } + }; + + OPL::OPL *_opl; + int _masterVolume; + byte *_rhythmKeyMap; + + // points to a MIDI channel for each of the new voice channels + byte _voiceChannelMapping[SHERLOCK_ADLIB_VOICES_COUNT]; + + // stores information about all FM voice channels + adlib_ChannelEntry _channels[SHERLOCK_ADLIB_VOICES_COUNT]; + + void programChange(byte channel, byte parameter); + void setRegister(int reg, int value); + void noteOn(byte channel, byte note, byte velocity); + void noteOff(byte channel, byte note); + void voiceOnOff(byte FMVoiceChannel, bool KeyOn, byte note, byte velocity); +}; + +#if USE_SCI_MIDI_PLAYER +class MidiPlayer_AdLib : public MidiPlayer { +public: + MidiPlayer_AdLib() : MidiPlayer() { _driver = new MidiDriver_AdLib(g_system->getMixer()); } + ~MidiPlayer_AdLib() { + delete _driver; + _driver = 0; + } + + int open(); + void close(); + + byte getPlayId() const; + int getPolyphony() const { return SHERLOCK_ADLIB_VOICES_COUNT; } + bool hasRhythmChannel() const { return false; } + void setVolume(byte volume) { static_cast<MidiDriver_AdLib *>(_driver)->setVolume(volume); } + + //int getLastChannel() const { return (static_cast<const MidiDriver_AdLib *>(_driver)->useRhythmChannel() ? 8 : 15); } + + void newMusicData(byte *musicData, int32 musicDataSize) { static_cast<MidiDriver_AdLib *>(_driver)->newMusicData(musicData, musicDataSize); } +}; +#endif + +int MidiDriver_AdLib::open() { + int rate = _mixer->getOutputRate(); + + debug(3, "ADLIB: Starting driver"); + + _opl = OPL::Config::create(OPL::Config::kOpl2); + + if (!_opl) + return -1; + + _opl->init(rate); + + setRegister(0xBD, 0); + setRegister(0x08, 0); + setRegister(0x01, 0x20); + + MidiDriver_Emulated::open(); + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO); + + return 0; +} + +void MidiDriver_AdLib::close() { + _mixer->stopHandle(_mixerSoundHandle); + + delete _opl; + delete[] _rhythmKeyMap; +} + +void MidiDriver_AdLib::setVolume(byte volume) { + _masterVolume = volume; + //renewNotes(-1, true); +} + +// Called when a music track got loaded into memory +void MidiDriver_AdLib::newMusicData(byte *musicData, int32 musicDataSize) { + assert(musicDataSize >= 0x7F); + // MIDI Channel <-> FM Voice Channel mapping at offset 0x22 of music data + memcpy(&_voiceChannelMapping, musicData + 0x22, 9); + + // reset OPL here? + // reset current channel data + memset(&_channels, 0, sizeof(_channels)); +} + +// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php +void MidiDriver_AdLib::send(uint32 b) { + byte command = b & 0xf0; + byte channel = b & 0xf; + byte op1 = (b >> 8) & 0xff; + byte op2 = (b >> 16) & 0xff; + + switch (command) { + case 0x80: + noteOff(channel, op1); + break; + case 0x90: + noteOn(channel, op1, op2); + break; + case 0xb0: // Control change + // Doesn't seem to be implemented in the Sherlock Holmes adlib driver + break; + case 0xc0: // Program Change + programChange(channel, op1); + break; + case 0xa0: // Polyphonic key pressure (aftertouch) + case 0xd0: // Channel pressure (aftertouch) + // Aftertouch doesn't seem to be implemented in the Sherlock Holmes adlib driver + break; + case 0xe0: + // TODO: Implement this, occurs right in the intro, second scene + warning("pitch bend change"); + break; + case 0xf0: // SysEx + warning("SysEx: %lx", b); + break; + default: + warning("ADLIB: Unknown event %02x", command); + } +} + +void MidiDriver_AdLib::generateSamples(int16 *data, int len) { + _opl->readBuffer(data, len); +} + +void MidiDriver_AdLib::noteOn(byte MIDIchannel, byte note, byte velocity) { + if (velocity == 0) + return noteOff(MIDIchannel, note); + + if (MIDIchannel != 9) { + // Not Percussion + for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) { + if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) { + if (!_channels[FMvoiceChannel].inUse) { + _channels[FMvoiceChannel].inUse = true; + _channels[FMvoiceChannel].currentNote = note; + + voiceOnOff(FMvoiceChannel, true, note, velocity); + return; + } + } + } + } + warning("MIDI channel not mapped/all FM voice channels busy %d", MIDIchannel); +} + +void MidiDriver_AdLib::noteOff(byte MIDIchannel, byte note) { + for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) { + if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) { + if (_channels[FMvoiceChannel].currentNote == note) { + _channels[FMvoiceChannel].inUse = false; + + voiceOnOff(FMvoiceChannel, false, note, 0); + return; + } + } + } +} + +void MidiDriver_AdLib::voiceOnOff(byte FMvoiceChannel, bool keyOn, byte note, byte velocity) { + byte frequencyOffset = 0; + uint16 frequency = 0; + byte op2RegAdjust = 0; + byte regValue40h = 0; + byte regValueA0h = 0; + byte regValueB0h = 0; + + // Look up frequency + if (_channels[FMvoiceChannel].currentInstrumentPtr) { + frequencyOffset = note + _channels[FMvoiceChannel].currentInstrumentPtr->frequencyAdjust; + } else { + frequencyOffset = note; + } + if (frequencyOffset >= SHERLOCK_ADLIB_NOTES_COUNT) { + error("bad note!"); + } + frequency = adlib_FrequencyLookUpTable[frequencyOffset]; + + if (keyOn) { + // adjust register 40h + if (_channels[FMvoiceChannel].currentInstrumentPtr) { + regValue40h = _channels[FMvoiceChannel].currentInstrumentPtr->reg40op2; + } + regValue40h = regValue40h - (velocity >> 3); + op2RegAdjust = adlib_Operator2Register[FMvoiceChannel]; + setRegister(0x40 + op2RegAdjust, regValue40h); + } + + regValueA0h = frequency & 0xFF; + regValueB0h = frequency >> 8; + if (keyOn) { + regValueB0h |= 0x20; // set Key-On flag + } + + setRegister(0xA0 + FMvoiceChannel, regValueA0h); + setRegister(0xB0 + FMvoiceChannel, regValueB0h); + _channels[FMvoiceChannel].currentA0hReg = regValueA0h; + _channels[FMvoiceChannel].currentB0hReg = regValueB0h; +} + +void MidiDriver_AdLib::programChange(byte MIDIchannel, byte op1) { + const adlib_InstrumentEntry *instrumentPtr; + byte op1Reg = 0; + byte op2Reg = 0; + + // setup instrument + instrumentPtr = &adlib_instrumentTable[op1]; + //warning("program change for MIDI channel %d, instrument id %d", MIDIchannel, op1); + + for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) { + if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) { + + op1Reg = adlib_Operator1Register[FMvoiceChannel]; + op2Reg = adlib_Operator2Register[FMvoiceChannel]; + + setRegister(0x20 + op1Reg, instrumentPtr->reg20op1); + setRegister(0x40 + op1Reg, instrumentPtr->reg40op1); + setRegister(0x60 + op1Reg, instrumentPtr->reg60op1); + setRegister(0x80 + op1Reg, instrumentPtr->reg80op1); + setRegister(0xE0 + op1Reg, instrumentPtr->regE0op1); + + setRegister(0x20 + op2Reg, instrumentPtr->reg20op2); + setRegister(0x40 + op2Reg, instrumentPtr->reg40op2); + setRegister(0x60 + op2Reg, instrumentPtr->reg60op2); + setRegister(0x80 + op2Reg, instrumentPtr->reg80op2); + setRegister(0xE0 + op2Reg, instrumentPtr->regE0op2); + + setRegister(0xC0 + FMvoiceChannel, instrumentPtr->regC0); + + // Remember instrument + _channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr; + } + } +} +void MidiDriver_AdLib::setRegister(int reg, int value) { + _opl->write(0x220, reg); + _opl->write(0x221, value); +} + +uint32 MidiDriver_AdLib::property(int prop, uint32 param) { +#if 0 + switch(prop) { + case MIDI_PROP_MASTER_VOLUME: + if (param != 0xffff) + _masterVolume = param; + return _masterVolume; + default: + break; + } +#endif + return 0; +} + +#if USE_SCI_MIDI_PLAYER +int MidiPlayer_AdLib::open() { + return static_cast<MidiDriver_AdLib *>(_driver)->open(); +} + +void MidiPlayer_AdLib::close() { + if (_driver) { + _driver->close(); + } +} + +byte MidiPlayer_AdLib::getPlayId() const { + return 0x00; +} + +MidiPlayer *MidiPlayer_AdLib_create() { + return new MidiPlayer_AdLib(); +} + +void MidiPlayer_AdLib_newMusicData(MidiPlayer *driver, byte *musicData, int32 musicDataSize) { + static_cast<MidiPlayer_AdLib *>(driver)->newMusicData(musicData, musicDataSize); +} +#endif + +MidiDriver *MidiDriver_AdLib_create() { + return new MidiDriver_AdLib(g_system->getMixer()); +} + +void MidiDriver_AdLib_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize) { + static_cast<MidiDriver_AdLib *>(driver)->newMusicData(musicData, musicDataSize); +} + +} // End of namespace Sci diff --git a/engines/sherlock/scalpel/drivers/mididriver.h b/engines/sherlock/scalpel/drivers/mididriver.h new file mode 100644 index 0000000000..f1366f8ebc --- /dev/null +++ b/engines/sherlock/scalpel/drivers/mididriver.h @@ -0,0 +1,91 @@ +/* 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. + * + */ + +#ifndef SHERLOCK_SOFTSEQ_MIDIDRIVER_H +#define SHERLOCK_SOFTSEQ_MIDIDRIVER_H + +#include "sherlock/sherlock.h" +//#include "audio/mididrv.h" +#include "common/error.h" + +namespace Sherlock { + +#define USE_SCI_MIDIPLAYER 1 + +#if USE_SCI_MIDIPLAYER +enum { + MIDI_CHANNELS = 16, + MIDI_PROP_MASTER_VOLUME = 0 +}; + +#define MIDI_RHYTHM_CHANNEL 9 + +class MidiPlayer : public MidiDriver_BASE { +protected: + MidiDriver *_driver; + int8 _reverb; + +public: + MidiPlayer() : _driver(0), _reverb(-1) { } + + virtual int open() { return _driver->open(); } + virtual void close() { _driver->close(); } + virtual void send(uint32 b) { _driver->send(b); } + virtual uint32 getBaseTempo() { return _driver->getBaseTempo(); } + virtual bool hasRhythmChannel() const = 0; + virtual void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { _driver->setTimerCallback(timer_param, timer_proc); } + + virtual byte getPlayId() const = 0; + virtual int getPolyphony() const = 0; + virtual int getFirstChannel() const { return 0; } + //virtual int getLastChannel() const { return 15; } + + virtual void setVolume(byte volume) { + if(_driver) + _driver->property(MIDI_PROP_MASTER_VOLUME, volume); + } + + virtual int getVolume() { + return _driver ? _driver->property(MIDI_PROP_MASTER_VOLUME, 0xffff) : 0; + } + + // Returns the current reverb, or -1 when no reverb is active + int8 getReverb() const { return _reverb; } + // Sets the current reverb, used mainly in MT-32 + virtual void setReverb(int8 reverb) { _reverb = reverb; } + + // Special stuff for Sherlock Holmes +// virtual void newMusicData(byte *musicData, int32 musicDataSize); + +//protected: +}; + +extern MidiPlayer *MidiPlayer_AdLib_create(); +extern void MidiPlayer_AdLib_newMusicData(MidiPlayer *driver, byte *musicData, int32 musicDataSize); +#endif + +extern MidiDriver *MidiDriver_AdLib_create(); +extern void MidiDriver_AdLib_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize); + +} // End of namespace Sci + +#endif // SHERLOCK_SOFTSEQ_MIDIDRIVER_H diff --git a/engines/sherlock/scalpel/scalpel.cpp b/engines/sherlock/scalpel/scalpel.cpp index 6b72188146..304445df76 100644 --- a/engines/sherlock/scalpel/scalpel.cpp +++ b/engines/sherlock/scalpel/scalpel.cpp @@ -22,6 +22,7 @@ #include "sherlock/scalpel/scalpel.h" #include "sherlock/sherlock.h" +#include "sherlock/music.h" #include "sherlock/animation.h" #include "engines/util.h" @@ -432,13 +433,13 @@ void ScalpelEngine::showOpening() { return; _events->clearEvents(); - _sound->stopMusic(); + _music->stopMusic(); } bool ScalpelEngine::showCityCutscene() { byte palette[PALETTE_SIZE]; - _sound->playMusic("prolog1.mus"); + _music->playMusic("prolog1.mus"); _animation->_gfxLibraryFilename = "title.lib"; _animation->_soundLibraryFilename = "title.snd"; bool finished = _animation->play("26open1", 1, 255, true, 2); @@ -509,7 +510,7 @@ bool ScalpelEngine::showCityCutscene() { bool ScalpelEngine::showAlleyCutscene() { byte palette[PALETTE_SIZE]; - _sound->playMusic("prolog2.mus"); + _music->playMusic("prolog2.mus"); _animation->_gfxLibraryFilename = "TITLE.LIB"; _animation->_soundLibraryFilename = "TITLE.SND"; @@ -548,7 +549,7 @@ bool ScalpelEngine::showStreetCutscene() { _animation->_gfxLibraryFilename = "TITLE.LIB"; _animation->_soundLibraryFilename = "TITLE.SND"; - _sound->playMusic("PROLOG3.MUS"); + _music->playMusic("PROLOG3.MUS"); bool finished = _animation->play("14KICK", 1, 3, true, 2); @@ -594,7 +595,7 @@ bool ScalpelEngine::scrollCredits() { } bool ScalpelEngine::showOfficeCutscene() { - _sound->playMusic("PROLOG4.MUS"); + _music->playMusic("PROLOG4.MUS"); _animation->_gfxLibraryFilename = "TITLE2.LIB"; _animation->_soundLibraryFilename = "TITLE.SND"; @@ -672,16 +673,12 @@ void ScalpelEngine::showLBV(const Common::String &filename) { void ScalpelEngine::startScene() { if (_scene->_goToScene == OVERHEAD_MAP || _scene->_goToScene == OVERHEAD_MAP2) { // Show the map - if (_sound->_musicOn) { - if (_sound->loadSong(100)) { - if (_sound->_music) - _sound->startSong(); - } - } + if (_music->_musicOn && _music->loadSong(100)) + _music->startSong(); _scene->_goToScene = _map->show(); - _sound->freeSong(); + _music->freeSong(); _people->_hSavedPos = Common::Point(-1, -1); _people->_hSavedFacing = -1; } @@ -697,10 +694,8 @@ void ScalpelEngine::startScene() { case RESCUE_ANNA: case MOOREHEAD_DEATH: case BRUMWELL_SUICIDE: - if (_sound->_musicOn && _sound->loadSong(_scene->_goToScene)) { - if (_sound->_music) - _sound->startSong(); - } + if (_music->_musicOn && _music->loadSong(_scene->_goToScene)) + _music->startSong(); switch (_scene->_goToScene) { case BLACKWOOD_CAPTURE: @@ -800,7 +795,7 @@ void ScalpelEngine::startScene() { } // Free any song from the previous scene - _sound->freeSong(); + _music->freeSong(); break; case EXIT_GAME: diff --git a/engines/sherlock/scalpel/settings.cpp b/engines/sherlock/scalpel/settings.cpp index 44597862f5..aa8033d25e 100644 --- a/engines/sherlock/scalpel/settings.cpp +++ b/engines/sherlock/scalpel/settings.cpp @@ -58,6 +58,7 @@ void Settings::drawInteface(bool flag) { People &people = *_vm->_people; Screen &screen = *_vm->_screen; Sound &sound = *_vm->_sound; + Music &music = *_vm->_music; UserInterface &ui = *_vm->_ui; Common::String tempStr; @@ -74,7 +75,7 @@ void Settings::drawInteface(bool flag) { screen.makeButton(Common::Rect(SETUP_POINTS[0][0], SETUP_POINTS[0][1], SETUP_POINTS[0][2], SETUP_POINTS[0][1] + 10), SETUP_POINTS[0][3] - screen.stringWidth("Exit") / 2, "Exit"); - tempStr = Common::String::format("Music %s", SETUP_STRS0[sound._music]); + tempStr = Common::String::format("Music %s", SETUP_STRS0[music._musicOn]); screen.makeButton(Common::Rect(SETUP_POINTS[1][0], SETUP_POINTS[1][1], SETUP_POINTS[1][2], SETUP_POINTS[1][1] + 10), SETUP_POINTS[1][3] - screen.stringWidth(tempStr) / 2, tempStr); @@ -138,6 +139,7 @@ int Settings::drawButtons(const Common::Point &pt, int _key) { Events &events = *_vm->_events; People &people = *_vm->_people; Screen &screen = *_vm->_screen; + Music &music = *_vm->_music; Sound &sound = *_vm->_sound; UserInterface &ui = *_vm->_ui; int found = -1; @@ -157,7 +159,7 @@ int Settings::drawButtons(const Common::Point &pt, int _key) { // Print the button text switch (idx) { case 1: - tempStr = Common::String::format("Music %s", SETUP_STRS0[sound._music]); + tempStr = Common::String::format("Music %s", SETUP_STRS0[music._musicOn]); screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), color, true, tempStr); break; case 2: @@ -211,6 +213,7 @@ void Settings::show(SherlockEngine *vm) { Scene &scene = *vm->_scene; Screen &screen = *vm->_screen; Sound &sound = *vm->_sound; + Music &music = *vm->_music; Talk &talk = *vm->_talk; ScalpelUserInterface &ui = *(ScalpelUserInterface *)vm->_ui; bool updateConfig = false; @@ -259,14 +262,11 @@ void Settings::show(SherlockEngine *vm) { if ((found == 1 && events._released) || ui._key == 'M') { // Toggle music - if (sound._music) { - sound.stopSound(); - sound._music = false; - } - else { - sound._music = true; - sound.startSong(); - } + music._musicOn = !music._musicOn; + if (!music._musicOn) + music.stopMusic(); + else + music.startSong(); updateConfig = true; settings.drawInteface(true); diff --git a/engines/sherlock/scene.cpp b/engines/sherlock/scene.cpp index 0f0187e215..76fb7ae3a7 100644 --- a/engines/sherlock/scene.cpp +++ b/engines/sherlock/scene.cpp @@ -228,7 +228,7 @@ void Scene::freeScene() { _vm->_talk->freeTalkVars(); _vm->_inventory->freeInv(); - _vm->_sound->freeSong(); + _vm->_music->freeSong(); _vm->_sound->freeLoadedSounds(); if (!_loadingSavedGame) @@ -254,6 +254,7 @@ void Scene::freeScene() { bool Scene::loadScene(const Common::String &filename) { Events &events = *_vm->_events; Map &map = *_vm->_map; + Music &music = *_vm->_music; People &people = *_vm->_people; Resources &res = *_vm->_res; SaveManager &saves = *_vm->_saves; @@ -296,10 +297,10 @@ bool Scene::loadScene(const Common::String &filename) { // If it's a new song, then start it up if (sound._currentSongName.compareToIgnoreCase(sound._nextSongName)) { - if (sound.loadSong(sound._nextSongName)) { + if (music.loadSong(sound._nextSongName)) { sound.setMIDIVolume(sound._musicVolume); - if (sound._musicOn) - sound.startSong(); + if (music._musicOn) + music.startSong(); } } } @@ -608,10 +609,8 @@ bool Scene::loadScene(const Common::String &filename) { checkInventory(); // Handle starting any music for the scene - if (IS_SERRATED_SCALPEL && sound._musicOn && sound.loadSong(_currentScene)) { - if (sound._music) - sound.startSong(); - } + if (IS_SERRATED_SCALPEL && music._musicOn && music.loadSong(_currentScene)) + music.startSong(); // Load walking images if not already loaded people.loadWalk(); diff --git a/engines/sherlock/sherlock.cpp b/engines/sherlock/sherlock.cpp index 03c10554e8..d3a409a67b 100644 --- a/engines/sherlock/sherlock.cpp +++ b/engines/sherlock/sherlock.cpp @@ -36,6 +36,7 @@ SherlockEngine::SherlockEngine(OSystem *syst, const SherlockGameDescription *gam _inventory = nullptr; _journal = nullptr; _map = nullptr; + _music = nullptr; _people = nullptr; _res = nullptr; _saves = nullptr; @@ -57,6 +58,7 @@ SherlockEngine::~SherlockEngine() { delete _events; delete _journal; delete _map; + delete _music; delete _people; delete _saves; delete _scene; @@ -89,6 +91,7 @@ void SherlockEngine::initialize() { _events = new Events(this); _inventory = new Inventory(this); _map = new Map(this); + _music = new Music(this, _mixer); _journal = new Journal(this); _people = new People(this); _saves = new SaveManager(this, _targetName); @@ -217,7 +220,7 @@ void SherlockEngine::loadConfig() { void SherlockEngine::saveConfig() { ConfMan.setBool("mute", !_sound->_digitized); - ConfMan.setBool("music_mute", !_sound->_music); + ConfMan.setBool("music_mute", !_music->_musicOn); ConfMan.setBool("speech_mute", !_sound->_voices); ConfMan.setInt("font", _screen->fontNumber()); @@ -234,6 +237,7 @@ void SherlockEngine::syncSoundSettings() { // Load sound-related settings _sound->syncSoundSettings(); + _music->syncMusicSettings(); } void SherlockEngine::synchronize(Common::Serializer &s) { diff --git a/engines/sherlock/sherlock.h b/engines/sherlock/sherlock.h index 467f20e381..e71c729893 100644 --- a/engines/sherlock/sherlock.h +++ b/engines/sherlock/sherlock.h @@ -38,6 +38,7 @@ #include "sherlock/inventory.h" #include "sherlock/journal.h" #include "sherlock/map.h" +#include "sherlock/music.h" #include "sherlock/people.h" #include "sherlock/resources.h" #include "sherlock/saveload.h" @@ -104,6 +105,7 @@ public: Inventory *_inventory; Journal *_journal; Map *_map; + Music *_music; People *_people; Resources *_res; SaveManager *_saves; diff --git a/engines/sherlock/sound.cpp b/engines/sherlock/sound.cpp index 279dd44157..e7f4fe8a9a 100644 --- a/engines/sherlock/sound.cpp +++ b/engines/sherlock/sound.cpp @@ -52,17 +52,16 @@ static const uint8 creativeADPCM_AdjustMap[64] = { Sound::Sound(SherlockEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) { _digitized = false; - _music = false; _voices = 0; _diskSoundPlaying = false; _soundPlaying = false; _soundIsOn = &_soundPlaying; _curPriority = 0; + _digiBuf = nullptr; _midiDrvLoaded = false; _musicVolume = 0; _soundOn = true; - _musicOn = true; _speechOn = true; _vm->_res->addToCache("MUSIC.LIB"); @@ -86,7 +85,6 @@ Sound::Sound(SherlockEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) { void Sound::syncSoundSettings() { _digitized = !ConfMan.getBool("mute"); - _music = !ConfMan.getBool("mute") && !ConfMan.getBool("music_mute"); _voices = !ConfMan.getBool("mute") && !ConfMan.getBool("speech_mute") ? 1 : 0; } @@ -209,63 +207,11 @@ void Sound::stopSound() { _mixer->stopHandle(_effectsHandle); } -void Sound::playMusic(const Common::String &name) { - // TODO - warning("Sound::playMusic %s", name.c_str()); - Common::SeekableReadStream *stream = _vm->_res->load(name, "MUSIC.LIB"); - - byte *data = new byte[stream->size()]; - byte *ptr = data; - stream->read(ptr, stream->size()); - Common::DumpFile outFile; - outFile.open(name + ".RAW"); - outFile.write(data, stream->size()); - outFile.flush(); - outFile.close(); - delete[] data; - - stopMusic(); - startSong(); -} - -void Sound::stopMusic() { - // TODO - warning("TODO: Sound::stopMusic"); -} - -bool Sound::loadSong(int songNumber) { - // TODO - warning("TODO: Sound::loadSong"); - return false; -} - -bool Sound::loadSong(const Common::String &name) { - // TODO - warning("TODO: Sound::loadSong"); - return false; -} - - -void Sound::startSong() { - // TODO - warning("TODO: Sound::startSong"); -} - -void Sound::freeSong() { - // TODO - warning("TODO: Sound::freeSong"); -} - void Sound::stopSndFuncPtr(int v1, int v2) { // TODO warning("TODO: Sound::stopSndFuncPtr"); } -void Sound::waitTimerRoland(uint time) { - // TODO - warning("TODO: Sound::waitTimerRoland"); -} - void Sound::freeDigiSound() { delete[] _digiBuf; _digiBuf = nullptr; diff --git a/engines/sherlock/sound.h b/engines/sherlock/sound.h index 06450ff50d..e1c0777763 100644 --- a/engines/sherlock/sound.h +++ b/engines/sherlock/sound.h @@ -49,10 +49,8 @@ private: byte decodeSample(byte sample, byte& reference, int16& scale); public: bool _digitized; - bool _music; int _voices; bool _soundOn; - bool _musicOn; bool _speechOn; bool _diskSoundPlaying; bool _soundPlaying; @@ -94,34 +92,7 @@ public: */ void stopSound(); - /** - * Load a specified song - */ - bool loadSong(int songNumber); - bool loadSong(const Common::String &name); - - /** - * Start playing a song - */ - void startSong(); - - /** - * Free any currently loaded song - */ - void freeSong(); - - /** - * Play the specified music resource - */ - void playMusic(const Common::String &name); - - /** - * Stop playing the music - */ - void stopMusic(); - void stopSndFuncPtr(int v1, int v2); - void waitTimerRoland(uint time); void freeDigiSound(); void setMIDIVolume(int volume); }; diff --git a/engines/tsage/core.cpp b/engines/tsage/core.cpp index 3105a9008e..c1c4c27e32 100644 --- a/engines/tsage/core.cpp +++ b/engines/tsage/core.cpp @@ -1521,7 +1521,7 @@ void ScenePalette::changeBackground(const Rect &bounds, FadeMode fadeMode) { } Rect tempRect = bounds; - if (g_vm->getGameID() != GType_Ringworld) + if (g_vm->getGameID() != GType_Ringworld && g_vm->getGameID() != GType_Sherlock1) tempRect.setHeight(T2_GLOBALS._interfaceY); g_globals->_screenSurface.copyFrom(g_globals->_sceneManager._scene->_backSurface, @@ -2806,7 +2806,7 @@ void SceneObject::updateScreen() { srcRect.right = ((srcRect.right + 3) / 4) * 4; srcRect.clip(g_globals->_sceneManager._scene->_sceneBounds); - if (g_vm->getGameID() != GType_Ringworld) { + if (g_vm->getGameID() != GType_Ringworld && g_vm->getGameID() != GType_Sherlock1) { if (T2_GLOBALS._uiElements._visible) srcRect.bottom = MIN<int16>(srcRect.bottom, T2_GLOBALS._interfaceY); } diff --git a/engines/tsage/detection.cpp b/engines/tsage/detection.cpp index 9d61b4d182..388967931d 100644 --- a/engines/tsage/detection.cpp +++ b/engines/tsage/detection.cpp @@ -62,6 +62,7 @@ static const PlainGameDescriptor tSageGameTitles[] = { { "ringworld", "Ringworld: Revenge of the Patriarch" }, { "blueforce", "Blue Force" }, { "ringworld2", "Return to Ringworld" }, + { "sherlock-logo", "The Lost Files of Sherlock Holmes (Logo)" }, { 0, 0 } }; diff --git a/engines/tsage/detection_tables.h b/engines/tsage/detection_tables.h index da283a27e7..1dfc3e6fd2 100644 --- a/engines/tsage/detection_tables.h +++ b/engines/tsage/detection_tables.h @@ -185,6 +185,22 @@ static const tSageGameDescription gameDescriptions[] = { GType_Ringworld2, GF_CD | GF_ALT_REGIONS | GF_DEMO }, + + // The Lost Files of Sherlock Holmes - The Case of the Serrated Scalpel (Logo) + { + { + "sherlock-logo", + "", + AD_ENTRY1s("sf3.rlb", "153f9b93eda4e95578e31be30e69b5e5", 50419), + Common::EN_ANY, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO0() + }, + GType_Sherlock1, + GF_FLOPPY + }, + { AD_TABLE_END_MARKER, 0, 0 } }; diff --git a/engines/tsage/globals.cpp b/engines/tsage/globals.cpp index e75febfdbc..1be3e2b6da 100644 --- a/engines/tsage/globals.cpp +++ b/engines/tsage/globals.cpp @@ -27,6 +27,7 @@ #include "tsage/ringworld/ringworld_logic.h" #include "tsage/ringworld2/ringworld2_logic.h" #include "tsage/ringworld2/ringworld2_scenes0.h" +#include "tsage/sherlock/sherlock_logo.h" #include "tsage/staticres.h" namespace TsAGE { @@ -156,6 +157,12 @@ Globals::Globals() : _dialogCenter(160, 140), _gfxManagerInstance(_screenSurface _game = new Ringworld2::Ringworld2Game(); _sceneHandler = new Ringworld2::SceneHandlerExt(); break; + + case GType_Sherlock1: + _inventory = nullptr; + _sceneHandler = new Sherlock::SherlockSceneHandler(); + _game = new Sherlock::SherlockLogo(); + break; } } diff --git a/engines/tsage/graphics.cpp b/engines/tsage/graphics.cpp index ce24c76290..156503fb51 100644 --- a/engines/tsage/graphics.cpp +++ b/engines/tsage/graphics.cpp @@ -1289,7 +1289,8 @@ void GfxManager::setDefaults() { _font._edgeSize = Common::Point(1, 1); _font._colors = g_globals->_fontColors; - _font.setFontNumber(g_globals->_gfxFontNumber); + if (g_globals->_gfxFontNumber >= 0) + _font.setFontNumber(g_globals->_gfxFontNumber); } void GfxManager::activate() { diff --git a/engines/tsage/module.mk b/engines/tsage/module.mk index d62f398c20..e23b157a95 100644 --- a/engines/tsage/module.mk +++ b/engines/tsage/module.mk @@ -47,6 +47,7 @@ MODULE_OBJS := \ ringworld2/ringworld2_vampire.o \ saveload.o \ scenes.o \ + sherlock/sherlock_logo.o \ sound.o \ staticres.o \ tsage.o \ diff --git a/engines/tsage/sherlock/sherlock_logo.cpp b/engines/tsage/sherlock/sherlock_logo.cpp new file mode 100644 index 0000000000..437fdc6d94 --- /dev/null +++ b/engines/tsage/sherlock/sherlock_logo.cpp @@ -0,0 +1,356 @@ +/* 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 "tsage/sherlock/sherlock_logo.h" +#include "tsage/scenes.h" +#include "tsage/tsage.h" + +namespace TsAGE { + +namespace Sherlock { + +void SherlockLogo::start() { + GLOBALS._gfxFontNumber = -1; + GLOBALS.gfxManager().setDefaults(); + + // Start the demo's single scene + g_globals->_sceneManager.changeScene(1); + + g_globals->_events.setCursor(CURSOR_NONE); +} + +Scene *SherlockLogo::createScene(int sceneNumber) { + // The demo only has a single scene, so ignore the scene number and always return it + return new SherlockLogoScene(); +} + +bool SherlockLogo::canLoadGameStateCurrently() { + return false; +} + +bool SherlockLogo::canSaveGameStateCurrently() { + return false; +} + +void SherlockLogo::processEvent(Event &event) { + if (event.eventType == EVENT_BUTTON_DOWN || (event.eventType == EVENT_KEYPRESS && + event.kbd.keycode == Common::KEYCODE_ESCAPE)) + quitGame(); +} + +void SherlockLogo::quitGame() { + g_vm->quitGame(); +} + +/*--------------------------------------------------------------------------*/ + +void SherlockSceneHandler::postInit(SceneObjectList *OwnerList) { + _delayTicks = 2; + + GLOBALS._soundManager.postInit(); + GLOBALS._soundManager.buildDriverList(true); + GLOBALS._soundManager.installConfigDrivers(); + + GLOBALS._sceneManager.setNewScene(10); + GLOBALS._game->start(); +} + +/*--------------------------------------------------------------------------*/ + +void Object::setVisage(const Common::String &name) { + int visageNum = atoi(name.c_str()); + SceneObject::setVisage(visageNum); +} + +/*--------------------------------------------------------------------------*/ + +void SherlockLogoScene::Action1::signal() { + SherlockLogoScene &scene = *(SherlockLogoScene *)GLOBALS._sceneManager._scene; + + switch (_actionIndex++) { + case 0: + // Load scene palette + GLOBALS._scenePalette.loadPalette(1111); + GLOBALS._scenePalette.loadPalette(1); + GLOBALS._scenePalette.refresh(); + setDelay(1); + break; + + case 1: + // Fade in the spotlight background + GLOBALS._scenePalette.addFader(scene._palette1._palette, 256, 6, this); + break; + + case 2: + // First half of square, circle, and triangle bouncing + scene._object1.postInit(); + scene._object1.setVisage("0016.vis"); + scene._object1._strip = 1; + scene._object1._frame = 1; + scene._object1.setPosition(Common::Point(169, 107)); + scene._object1.changeZoom(100); + scene._object1._numFrames = 7; + scene._object1.animate(ANIM_MODE_5, this); + break; + + case 3: + // Remainder of bouncing square, circle, and triangle coming to rest + scene._object1._strip = 2; + scene._object1._frame = 1; + scene._object1.changeZoom(100); + scene._object1.animate(ANIM_MODE_4, 11, 1, this); + break; + + case 4: + // Fade out background without fading out the shapes + GLOBALS._scenePalette.addFader(scene._palette2._palette, 256, 6, this); + break; + + case 5: + scene._backSurface.fillRect(scene._sceneBounds, 0); + scene._gfxManager2.activate(); + scene._gfxManager2.fillRect(scene._sceneBounds, 0); + scene._gfxManager2.deactivate(); + //word_2B4AA = 3; + setDelay(10); + break; + + case 6: + GLOBALS._scenePalette.loadPalette(12); + GLOBALS._scenePalette.refresh(); + setDelay(1); + break; + + case 7: + // Animation of shapes expanding upwards to form larger EA logo + scene._object1.setVisage("0012.vis"); + scene._object1._strip = 1; + scene._object1._frame = 1; + scene._object1.changeZoom(100); + scene._object1.setPosition(Common::Point(170, 142)); + scene._object1._numFrames = 7; + scene._object1.animate(ANIM_MODE_5, nullptr); + ADD_MOVER(scene._object1, 158, 71); + break; + + case 8: + GLOBALS._scenePalette.addFader(scene._palette3._palette, 256, 40, this); + break; + + case 9: + // Show 'Electronic Arts' company name + scene._object2.postInit(nullptr); + scene._object2.setVisage("0014.vis"); + scene._object2._strip = 1; + scene._object2._frame = 1; + scene._object2.setPosition(Common::Point(152, 98)); + scene._object2.changeZoom(100); + scene._object2.animate(ANIM_MODE_NONE, nullptr); + setDelay(120); + break; + + case 10: + // Remainder of steps is positioning and sizing hand cursorin an arc + scene._object3.postInit(); + scene._object3.setVisage("0018.vis"); + scene._object3._strip = 1; + scene._object3._frame = 1; + scene._object3.setPosition(Common::Point(33, 91)); + scene._object3.changeZoom(100); + scene._object3.animate(ANIM_MODE_NONE, nullptr); + setDelay(5); + break; + + case 11: + scene._object3._frame = 2; + scene._object3.setPosition(Common::Point(44, 124)); + setDelay(5); + break; + + case 12: + scene._object3._frame = 3; + scene._object3.setPosition(Common::Point(64, 153)); + setDelay(5); + break; + + case 13: + scene._object3._frame = 4; + scene._object3.setPosition(Common::Point(87, 174)); + setDelay(5); + break; + + case 14: + scene._object3._frame = 5; + scene._object3.setPosition(Common::Point(114, 191)); + setDelay(5); + break; + + case 15: + scene._object3._frame = 6; + scene._object3.setPosition(Common::Point(125, 184)); + setDelay(5); + break; + + case 16: + scene._object3._frame = 7; + scene._object3.setPosition(Common::Point(154, 187)); + setDelay(5); + break; + + case 17: + scene._object3._frame = 8; + scene._object3.setPosition(Common::Point(181, 182)); + setDelay(5); + break; + + case 18: + scene._object3._frame = 9; + scene._object3.setPosition(Common::Point(191, 167)); + setDelay(5); + break; + + case 19: + scene._object3._frame = 10; + scene._object3.setPosition(Common::Point(190, 150)); + setDelay(5); + break; + + case 20: + scene._object3._frame = 11; + scene._object3.setPosition(Common::Point(182, 139)); + setDelay(5); + break; + + case 21: + scene._object3._frame = 11; + scene._object3.setPosition(Common::Point(170, 130)); + setDelay(5); + break; + + case 22: + scene._object3._frame = 11; + scene._object3.setPosition(Common::Point(158, 121)); + setDelay(8); + break; + + case 23: + // Show a highlighting of the company name + scene._object3.hide(); + scene._object4.show(); + scene._object4.setPosition(Common::Point(155, 94)); + setDelay(8); + break; + + case 24: + scene._object4._frame = 2; + scene._object4.setPosition(Common::Point(155, 94)); + setDelay(8); + break; + + case 25: + scene._object2.remove(); + setDelay(1); + break; + + case 26: + scene._object4._frame = 3; + scene._object4.setPosition(Common::Point(155, 94)); + setDelay(8); + break; + + case 27: + scene._object4._frame = 4; + scene._object4.setPosition(Common::Point(155, 94)); + setDelay(8); + break; + break; + + case 28: + scene._object4._frame = 5; + scene._object4.setPosition(Common::Point(155, 94)); + setDelay(8); + break; + break; + + case 29: + scene._object4._frame = 6; + scene._object4.setPosition(Common::Point(155, 94)); + setDelay(8); + break; + break; + + case 30: + scene._object4._frame = 7; + scene._object4.setPosition(Common::Point(155, 94)); + setDelay(8); + break; + break; + + case 31: + scene._object4._frame = 8; + scene._object4.setPosition(Common::Point(155, 94)); + setDelay(8); + break; + + case 32: + setDelay(180); + break; + + default: + scene.finish(); + remove(); + break; + } +} + +/*--------------------------------------------------------------------------*/ + +void SherlockLogoScene::postInit(SceneObjectList *OwnerList) { + loadScene(10); + Scene::postInit(OwnerList); + + _palette1.loadPalette(1111); + _palette1.loadPalette(10); + _palette2.loadPalette(1111); + _palette2.loadPalette(1); + _palette3.loadPalette(1111); + _palette3.loadPalette(14); + + _object4.postInit(); + _object4.setVisage("0019.vis"); + _object4._strip = 1; + _object4._frame = 1; + _object4.setPosition(Common::Point(155, 94)); + _object4.changeZoom(100); + _object4.animate(ANIM_MODE_NONE, nullptr); + _object4.hide(); + + setAction(&_action1); +} + +void SherlockLogoScene::finish() { + g_vm->quitGame(); +} + +} // End of namespace Sherlock + +} // End of namespace TsAGE diff --git a/engines/tsage/sherlock/sherlock_logo.h b/engines/tsage/sherlock/sherlock_logo.h new file mode 100644 index 0000000000..95fc0e272f --- /dev/null +++ b/engines/tsage/sherlock/sherlock_logo.h @@ -0,0 +1,78 @@ +/* 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. + * + */ + +#ifndef TSAGE_SHERLOCK_LOGO_H +#define TSAGE_SHERLOCK_LOGO_H + +#include "common/scummsys.h" +#include "tsage/events.h" +#include "tsage/core.h" +#include "tsage/scenes.h" +#include "tsage/globals.h" +#include "tsage/sound.h" + +namespace TsAGE { + +namespace Sherlock { + +using namespace TsAGE; + +class Object : public SceneObject { +public: + void setVisage(const Common::String &name); +}; + +class SherlockLogo: public Game { +public: + virtual void start(); + virtual Scene *createScene(int sceneNumber); + virtual void quitGame(); + virtual void processEvent(Event &event); + virtual bool canSaveGameStateCurrently(); + virtual bool canLoadGameStateCurrently(); +}; + +class SherlockSceneHandler : public SceneHandler { +public: + virtual void postInit(SceneObjectList *OwnerList); +}; + +class SherlockLogoScene: public Scene { + class Action1 : public Action { + public: + virtual void signal(); + }; +public: + ScenePalette _palette1, _palette2, _palette3; + Object _object1, _object2, _object3, _object4; + Action1 _action1; + GfxManager _gfxManager2; + + virtual void postInit(SceneObjectList *OwnerList = NULL); + void finish(); +}; + +} // End of namespace Sherlock + +} // End of namespace TsAGE + +#endif diff --git a/engines/tsage/tsage.cpp b/engines/tsage/tsage.cpp index 0b882d5cbf..4412d0670f 100644 --- a/engines/tsage/tsage.cpp +++ b/engines/tsage/tsage.cpp @@ -44,11 +44,12 @@ TSageEngine::TSageEngine(OSystem *system, const tSageGameDescription *gameDesc) _debugger = new DemoDebugger(); else _debugger = new RingworldDebugger(); - } - else if (g_vm->getGameID() == GType_BlueForce) + } else if (g_vm->getGameID() == GType_BlueForce) _debugger = new BlueForceDebugger(); else if (g_vm->getGameID() == GType_Ringworld2) _debugger = new Ringworld2Debugger(); + else if (g_vm->getGameID() == GType_Sherlock1) + _debugger = new DemoDebugger(); } Common::Error TSageEngine::init() { @@ -110,6 +111,11 @@ void TSageEngine::initialize() { // Reset all global variables R2_GLOBALS.reset(); + } else if (g_vm->getGameID() == GType_Sherlock1) { + g_resourceManager->addLib("SF3.RLB"); + g_globals = new Globals(); + + return; } g_globals->gfxManager().setDefaults(); diff --git a/engines/tsage/tsage.h b/engines/tsage/tsage.h index ea4f5da6ea..667a8daa59 100644 --- a/engines/tsage/tsage.h +++ b/engines/tsage/tsage.h @@ -42,7 +42,8 @@ namespace TsAGE { enum { GType_Ringworld = 0, GType_BlueForce = 1, - GType_Ringworld2 = 2 + GType_Ringworld2 = 2, + GType_Sherlock1 = 5 }; enum { |