From 81e8a2860ee066d6b863bf80bb99a592dec4cbe0 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Mon, 20 Feb 2006 20:57:26 +0000 Subject: Moved iMUSE code to the new directory engines/scumm/imuse/ svn-id: r20801 --- engines/scumm/akos.cpp | 2 +- engines/scumm/debugger.cpp | 2 +- engines/scumm/dialogs.cpp | 2 +- engines/scumm/he/script_v60he.cpp | 2 +- engines/scumm/he/sound_he.cpp | 2 +- engines/scumm/imuse.cpp | 2043 ---------------------------------- engines/scumm/imuse.h | 85 -- engines/scumm/imuse/imuse.cpp | 2043 ++++++++++++++++++++++++++++++++++ engines/scumm/imuse/imuse.h | 85 ++ engines/scumm/imuse/imuse_internal.h | 472 ++++++++ engines/scumm/imuse/imuse_player.cpp | 1241 +++++++++++++++++++++ engines/scumm/imuse/instrument.cpp | 462 ++++++++ engines/scumm/imuse/instrument.h | 79 ++ engines/scumm/imuse_internal.h | 472 -------- engines/scumm/imuse_player.cpp | 1241 --------------------- engines/scumm/input.cpp | 2 +- engines/scumm/insane/insane.cpp | 2 +- engines/scumm/instrument.cpp | 462 -------- engines/scumm/instrument.h | 79 -- engines/scumm/module.mk | 26 +- engines/scumm/resource.cpp | 2 +- engines/scumm/saveload.cpp | 2 +- engines/scumm/script_v6.cpp | 2 +- engines/scumm/scumm.cpp | 2 +- engines/scumm/smush/smush_mixer.cpp | 2 +- engines/scumm/smush/smush_player.cpp | 2 +- engines/scumm/sound.cpp | 2 +- 27 files changed, 4410 insertions(+), 4408 deletions(-) delete mode 100644 engines/scumm/imuse.cpp delete mode 100644 engines/scumm/imuse.h create mode 100644 engines/scumm/imuse/imuse.cpp create mode 100644 engines/scumm/imuse/imuse.h create mode 100644 engines/scumm/imuse/imuse_internal.h create mode 100644 engines/scumm/imuse/imuse_player.cpp create mode 100644 engines/scumm/imuse/instrument.cpp create mode 100644 engines/scumm/imuse/instrument.h delete mode 100644 engines/scumm/imuse_internal.h delete mode 100644 engines/scumm/imuse_player.cpp delete mode 100644 engines/scumm/instrument.cpp delete mode 100644 engines/scumm/instrument.h (limited to 'engines/scumm') diff --git a/engines/scumm/akos.cpp b/engines/scumm/akos.cpp index 79824a53d0..0870be7b44 100644 --- a/engines/scumm/akos.cpp +++ b/engines/scumm/akos.cpp @@ -26,7 +26,7 @@ #include "scumm/actor.h" #include "scumm/akos.h" #include "scumm/bomp.h" -#include "scumm/imuse.h" +#include "scumm/imuse/imuse.h" #include "scumm/imuse_digi/dimuse.h" #include "scumm/intern.h" #include "scumm/he/intern_he.h" diff --git a/engines/scumm/debugger.cpp b/engines/scumm/debugger.cpp index 7ed345c572..d953b0f24f 100644 --- a/engines/scumm/debugger.cpp +++ b/engines/scumm/debugger.cpp @@ -30,7 +30,7 @@ #include "scumm/actor.h" #include "scumm/boxes.h" #include "scumm/debugger.h" -#include "scumm/imuse.h" +#include "scumm/imuse/imuse.h" #include "scumm/object.h" #include "scumm/player_v2.h" #include "scumm/scumm.h" diff --git a/engines/scumm/dialogs.cpp b/engines/scumm/dialogs.cpp index e4641ae0be..c6c7268ff6 100644 --- a/engines/scumm/dialogs.cpp +++ b/engines/scumm/dialogs.cpp @@ -35,7 +35,7 @@ #include "scumm/dialogs.h" #include "scumm/sound.h" #include "scumm/scumm.h" -#include "scumm/imuse.h" +#include "scumm/imuse/imuse.h" #include "scumm/imuse_digi/dimuse.h" #include "scumm/player_v2.h" #include "scumm/verbs.h" diff --git a/engines/scumm/he/script_v60he.cpp b/engines/scumm/he/script_v60he.cpp index 1868d6b71e..e26dd413a4 100644 --- a/engines/scumm/he/script_v60he.cpp +++ b/engines/scumm/he/script_v60he.cpp @@ -26,7 +26,7 @@ #include "scumm/actor.h" #include "scumm/charset.h" -#include "scumm/imuse.h" +#include "scumm/imuse/imuse.h" #include "scumm/he/intern_he.h" #include "scumm/object.h" #include "scumm/resource.h" diff --git a/engines/scumm/he/sound_he.cpp b/engines/scumm/he/sound_he.cpp index d687844d9d..b6f748dbba 100644 --- a/engines/scumm/he/sound_he.cpp +++ b/engines/scumm/he/sound_he.cpp @@ -23,7 +23,7 @@ #include "common/stdafx.h" #include "scumm/actor.h" -#include "scumm/imuse.h" +#include "scumm/imuse/imuse.h" #include "scumm/scumm.h" #include "scumm/sound.h" #include "scumm/util.h" diff --git a/engines/scumm/imuse.cpp b/engines/scumm/imuse.cpp deleted file mode 100644 index a4eede4935..0000000000 --- a/engines/scumm/imuse.cpp +++ /dev/null @@ -1,2043 +0,0 @@ -/* ScummVM - Scumm Interpreter - * Copyright (C) 2001 Ludvig Strigeus - * Copyright (C) 2001-2006 The ScummVM project - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#include "common/stdafx.h" - -#include "base/version.h" - -#include "common/util.h" -#include "common/system.h" - -#include "scumm/imuse.h" -#include "scumm/imuse_internal.h" -#include "scumm/instrument.h" -#include "scumm/saveload.h" -#include "scumm/scumm.h" -#include "scumm/util.h" - -#include "sound/mididrv.h" - - -namespace Scumm { - -//////////////////////////////////////// -// -// IMuseInternal implementation -// -//////////////////////////////////////// - -IMuseInternal::IMuseInternal() : -_native_mt32(false), -_enable_gs(false), -_sc55(false), -_midi_adlib(0), -_midi_native(0), -_base_sounds(0), -_paused(false), -_initialized(false), -_tempoFactor(0), -_player_limit(ARRAYSIZE(_players)), -_recycle_players(false), -_direct_passthrough(false), -_queue_end(0), -_queue_pos(0), -_queue_sound(0), -_queue_adding(0), -_queue_marker(0), -_queue_cleared(0), -_master_volume(0), -_music_volume(0), -_trigger_count(0), -_snm_trigger_index(0) { - memset(_channel_volume,0,sizeof(_channel_volume)); - memset(_channel_volume_eff,0,sizeof(_channel_volume_eff)); - memset(_volchan_table,0,sizeof(_volchan_table)); -} - -byte *IMuseInternal::findStartOfSound(int sound) { - byte *ptr = NULL; - int32 size, pos; - - if (_base_sounds) - ptr = _base_sounds[sound]; - - if (ptr == NULL) { - debug(1, "IMuseInternal::findStartOfSound(): Sound %d doesn't exist!", sound); - return NULL; - } - - // Check for old-style headers first, like 'RO' - if (ptr[4] == 'R' && ptr[5] == 'O'&& ptr[6] != 'L') - return ptr + 4; - if (ptr[8] == 'S' && ptr[9] == 'O') - return ptr + 8; - - ptr += 8; - size = READ_BE_UINT32(ptr); - ptr += 4; - - // Okay, we're looking for one of those things: either - // an 'MThd' tag (for SMF), or a 'FORM' tag (for XMIDI). - size = 48; // Arbitrary; we should find our tag within the first 48 bytes of the resource - pos = 0; - while (pos < size) { - if (!memcmp(ptr + pos, "MThd", 4) || !memcmp(ptr + pos, "FORM", 4)) - return ptr + pos; - ++pos; // We could probably iterate more intelligently - } - - debug(3, "IMuseInternal::findStartOfSound(): Failed to align on sound %d!", sound); - return 0; -} - -bool IMuseInternal::isMT32(int sound) { - byte *ptr = NULL; - uint32 tag; - - if (_base_sounds) - ptr = _base_sounds[sound]; - - if (ptr == NULL) - return false; - - tag = *(((uint32 *)ptr) + 1); - switch (tag) { - case MKID('ADL '): - case MKID('ASFX'): // Special AD class for old Adlib sound effects - case MKID('SPK '): - return false; - - case MKID('AMI '): - case MKID('ROL '): - return true; - - case MKID('MAC '): // Occurs in the Mac version of FOA and MI2 - return true; - - case MKID('GMD '): - case MKID('MIDI'): // Occurs in Sam & Max - return false; - } - - // Old style 'RO' has equivalent properties to 'ROL' - if (ptr[4] == 'R' && ptr[5] == 'O') - return true; - // Euphony tracks show as 'SO' and have equivalent properties to 'ADL' - if (ptr[8] == 'S' && ptr[9] == 'O') - return false; - - error("Unknown music type: '%s'", tag2str(tag)); - - return false; -} - -bool IMuseInternal::isMIDI(int sound) { - byte *ptr = NULL; - uint32 tag; - - if (_base_sounds) - ptr = _base_sounds[sound]; - - if (ptr == NULL) - return false; - - tag = *(((uint32 *)ptr) + 1); - switch (tag) { - case MKID('ADL '): - case MKID('ASFX'): // Special AD class for old Adlib sound effects - case MKID('SPK '): - return false; - - case MKID('AMI '): - case MKID('ROL '): - return true; - - case MKID('MAC '): // Occurs in the Mac version of FOA and MI2 - return true; - - case MKID('GMD '): - case MKID('MIDI'): // Occurs in Sam & Max - return true; - } - - // Old style 'RO' has equivalent properties to 'ROL' - if (ptr[4] == 'R' && ptr[5] == 'O') - return true; - // Euphony tracks show as 'SO' and have equivalent properties to 'ADL' - // FIXME: Right now we're pretending it's GM. - if (ptr[8] == 'S' && ptr[9] == 'O') - return true; - - error("Unknown music type: '%s'", tag2str(tag)); - - return false; -} - -MidiDriver *IMuseInternal::getBestMidiDriver(int sound) { - MidiDriver *driver = NULL; - - if (isMIDI(sound)) { - if (_midi_native) { - driver = _midi_native; - } else { - // Route it through Adlib anyway. - driver = _midi_adlib; - } - } else { - driver = _midi_adlib; - } - return driver; -} - -bool IMuseInternal::startSound(int sound) { - Player *player; - void *ptr; - - // Do not start a sound if it is already set to start on an ImTrigger - // event. This fixes carnival music problems where a sound has been set - // to trigger at the right time, but then is started up immediately - // anyway, only to be restarted later when the trigger occurs. - // - // However, we have to make sure the sound with the trigger is actually - // playing, otherwise the music may stop when Sam and Max are thrown - // out of Bumpusville, because entering the mansion sets up a trigger - // for a sound that isn't necessarily playing. This is somewhat related - // to bug #780918. - - int i; - ImTrigger *trigger = _snm_triggers; - for (i = ARRAYSIZE(_snm_triggers); i; --i, ++trigger) { - if (trigger->sound && trigger->id && trigger->command[0] == 8 && trigger->command[1] == sound && getSoundStatus(trigger->sound)) - return false; - } - - ptr = findStartOfSound(sound); - if (!ptr) { - debug(2, "IMuseInternal::startSound(): Couldn't find sound %d!", sound); - return false; - } - - // Check which MIDI driver this track should use. - // If it's NULL, it ain't something we can play. - MidiDriver *driver = getBestMidiDriver(sound); - if (!driver) - return false; - - // If the requested sound is already playing, start it over - // from scratch. This was originally a hack to prevent Sam & Max - // iMuse messiness while upgrading the iMuse engine, but it - // is apparently necessary to deal with fade-and-restart - // race conditions that were observed in MI2. Reference - // Bug #590511 and Patch #607175 (which was reversed to fix - // an FOA regression: Bug #622606). - player = findActivePlayer(sound); - if (!player) - player = allocate_player(128); - if (!player) - return false; - - // HACK: This is to work around a problem at the Dino Bungie Memorial. - // There are three pieces of music involved here: - // - // 80 - Main theme (looping) - // 81 - Music when entering Rex's and Wally's room (not looping) - // 82 - Music when listening to Rex or Wally - // - // When entering, tune 81 starts, tune 80 is faded down (not out) and - // a trigger is set in tune 81 to fade tune 80 back up. - // - // When listening to Rex or Wally, tune 82 is started, tune 81 is faded - // out and tune 80 is faded down even further. - // - // However, when tune 81 is faded out its trigger will cause tune 80 to - // fade back up, resulting in two tunes being played simultaneously at - // full blast. It's no use trying to keep tune 81 playing at volume 0. - // It doesn't loop, so eventually it will terminate on its own. - // - // I don't know how the original interpreter handled this - or even if - // it handled it at all - but it looks like sloppy scripting to me. Our - // workaround is to clear the trigger if the player listens to Rex or - // Wally before tune 81 has finished on its own. - - if (g_scumm->_game.id == GID_SAMNMAX && sound == 82 && getSoundStatus(81, false)) - ImClearTrigger(81, 1); - - player->clear(); - return player->startSound(sound, driver, _direct_passthrough); -} - - -Player *IMuseInternal::allocate_player(byte priority) { - Player *player = _players, *best = NULL; - int i; - byte bestpri = 255; - - for (i = _player_limit; i != 0; i--, player++) { - if (!player->isActive()) - return player; - if (player->getPriority() < bestpri) { - best = player; - bestpri = player->getPriority(); - } - } - - if (bestpri < priority || _recycle_players) - return best; - - debug(1, "Denying player request"); - return NULL; -} - -void IMuseInternal::init_players() { - Player *player = _players; - int i; - - for (i = ARRAYSIZE(_players); i != 0; i--, player++) { - player->_se = this; - player->clear(); // Used to just set _active to false - } -} - -void IMuseInternal::init_parts() { - Part *part; - int i; - - for (i = 0, part = _parts; i != ARRAYSIZE(_parts); i++, part++) { - part->init(); - part->_se = this; - part->_slot = i; - } -} - -int IMuseInternal::stopSound(int sound) { - int r = -1; - Player *player = findActivePlayer(sound); - if (player) { - player->clear(); - r = 0; - } - return r; -} - -int IMuseInternal::stopAllSounds() { - Player *player = _players; - int i; - - for (i = ARRAYSIZE(_players); i != 0; i--, player++) { - if (player->isActive()) - player->clear(); - } - return 0; -} - -void IMuseInternal::on_timer(MidiDriver *midi) { - if (_paused || !_initialized) - return; - - if (midi == _midi_native || !_midi_native) - handleDeferredCommands(midi); - sequencer_timers(midi); -} - -int IMuseInternal::getMusicTimer() const { - int best_time = 0; - const Player *player = _players; - int i; - - for (i = ARRAYSIZE(_players); i != 0; i--, player++) { - if (player->isActive()) { - int timer = player->getMusicTimer(); - if (timer > best_time) - best_time = timer; - } - } - return best_time; -} - -void IMuseInternal::sequencer_timers(MidiDriver *midi) { - Player *player = _players; - int i; - for (i = ARRAYSIZE(_players); i != 0; i--, player++) { - if (player->isActive() && player->getMidiDriver() == midi) { - player->onTimer(); - } - } -} - -void IMuseInternal::handle_marker(uint id, byte data) { - uint16 *p = 0; - uint pos; - - if (_queue_adding && _queue_sound == id && data == _queue_marker) - return; - - // Fix for bug #733401, revised for bug #761637: - // It would seem that sometimes a marker is in the queue - // but not at the head position. In the case of our bug, - // this seems to be the result of commands in the queue - // for songs that are no longer playing. So we skip - // ahead to the appropriate marker, effectively chomping - // anything in the queue before it. This fixes the FOA - // end credits music, but needs to be tested for inappopriate - // behavior elsewhere. - pos = _queue_end; - while (pos != _queue_pos) { - p = _cmd_queue[pos].array; - if (p[0] == TRIGGER_ID && p[1] == id && p[2] == data) - break; - pos = (pos + 1) % ARRAYSIZE(_cmd_queue); - } - - if (pos == _queue_pos) - return; - - if (pos != _queue_end) - debug(0, "Skipping entries in iMuse command queue to reach marker"); - - _trigger_count--; - _queue_cleared = false; - do { - pos = (pos + 1) % ARRAYSIZE(_cmd_queue); - if (_queue_pos == pos) - break; - p = _cmd_queue[pos].array; - if (*p++ != COMMAND_ID) - break; - _queue_end = pos; - - doCommand(p[0], p[1], p[2], p[3], p[4], p[5], p[6], 0); - - if (_queue_cleared) - return; - pos = _queue_end; - } while (1); - - _queue_end = pos; -} - -int IMuseInternal::get_channel_volume(uint a) { - if (a < 8) - return _channel_volume_eff[a]; - return (_master_volume * _music_volume / 255) / 2; -} - -Part *IMuseInternal::allocate_part(byte pri, MidiDriver *midi) { - Part *part, *best = NULL; - int i; - - for (i = ARRAYSIZE(_parts), part = _parts; i != 0; i--, part++) { - if (!part->_player) - return part; - if (pri >= part->_pri_eff) { - pri = part->_pri_eff; - best = part; - } - } - - if (best) { - best->uninit(); - reallocateMidiChannels(midi); - } else { - debug(1, "Denying part request"); - } - return best; -} - -int IMuseInternal::getSoundStatus(int sound, bool ignoreFadeouts) const { - int i; - const Player *player = _players; - - for (i = ARRAYSIZE(_players); i != 0; i--, player++) { - if (player->isActive() && (!ignoreFadeouts || !player->isFadingOut())) { - if (sound == -1) - return player->getID(); - else if (player->getID() == (uint16)sound) - return 1; - } - } - return (sound == -1) ? 0 : get_queue_sound_status(sound); -} - -int IMuseInternal::get_queue_sound_status(int sound) const { - const uint16 *a; - int i, j; - - j = _queue_pos; - i = _queue_end; - - while (i != j) { - a = _cmd_queue[i].array; - if (a[0] == COMMAND_ID && a[1] == 8 && a[2] == (uint16)sound) - return 2; - i = (i + 1) % ARRAYSIZE(_cmd_queue); - } - - for (i = 0; i < ARRAYSIZE (_deferredCommands); ++i) { - if (_deferredCommands[i].time_left && _deferredCommands[i].a == 8 && - _deferredCommands[i].b == sound) { - return 2; - } - } - - return 0; -} - -int IMuseInternal::set_volchan(int sound, int volchan) { - int r; - int i; - int num; - Player *player, *best, *sameid; - - r = get_volchan_entry(volchan); - if (r == -1) - return -1; - - if (r >= 8) { - player = findActivePlayer(sound); - if (player && player->_vol_chan != (uint16)volchan) { - player->_vol_chan = volchan; - player->setVolume(player->getVolume()); - return 0; - } - return -1; - } else { - best = NULL; - num = 0; - sameid = NULL; - for (i = ARRAYSIZE(_players), player = _players; i != 0; i--, player++) { - if (player->isActive()) { - if (player->_vol_chan == (uint16)volchan) { - num++; - if (!best || player->getPriority() <= best->getPriority()) - best = player; - } else if (player->getID() == (uint16)sound) { - sameid = player; - } - } - } - if (sameid == NULL) - return -1; - if (num >= r) - best->clear(); - player->_vol_chan = volchan; - player->setVolume(player->getVolume()); - return 0; - } -} - -int IMuseInternal::clear_queue() { - _queue_adding = false; - _queue_cleared = true; - _queue_pos = 0; - _queue_end = 0; - _trigger_count = 0; - return 0; -} - -int IMuseInternal::enqueue_command(int a, int b, int c, int d, int e, int f, int g) { - uint16 *p; - uint i; - - i = _queue_pos; - - if (i == _queue_end) - return -1; - - if (a == -1) { - _queue_adding = false; - _trigger_count++; - return 0; - } - - p = _cmd_queue[_queue_pos].array; - p[0] = COMMAND_ID; - p[1] = a; - p[2] = b; - p[3] = c; - p[4] = d; - p[5] = e; - p[6] = f; - p[7] = g; - - i = (i + 1) % ARRAYSIZE(_cmd_queue); - - if (_queue_end != i) { - _queue_pos = i; - return 0; - } else { - _queue_pos = (i - 1) % ARRAYSIZE(_cmd_queue); - return -1; - } -} - -int IMuseInternal::query_queue(int param) { - switch (param) { - case 0: // Get trigger count - return _trigger_count; - case 1: // Get trigger type - if (_queue_end == _queue_pos) - return -1; - return _cmd_queue[_queue_end].array[1]; - case 2: // Get trigger sound - if (_queue_end == _queue_pos) - return 0xFF; - return _cmd_queue[_queue_end].array[2]; - default: - return -1; - } -} - -int IMuseInternal::setMusicVolume(uint vol) { - if (vol > 255) - vol = 255; - if (_music_volume == vol) - return 0; - _music_volume = vol; - vol = _master_volume * _music_volume / 255; - for (uint i = 0; i < ARRAYSIZE(_channel_volume); i++) { - _channel_volume_eff[i] = _channel_volume[i] * vol / 255; - } - if (!_paused) - update_volumes(); - return 0; -} - -int IMuseInternal::setImuseMasterVolume(uint vol) { - if (vol > 255) - vol = 255; - if (_master_volume == vol) - return 0; - _master_volume = vol; - vol = _master_volume * _music_volume / 255; - for (uint i = 0; i < ARRAYSIZE(_channel_volume); i++) { - _channel_volume_eff[i] = _channel_volume[i] * vol / 255; - } - if (!_paused) - update_volumes(); - return 0; -} - -int IMuseInternal::terminate1() { - _initialized = false; - stopAllSounds(); - return 0; -} - -// This is the stuff that has to be done -// outside the monitor's mutex, otherwise -// a deadlock occurs. -int IMuseInternal::terminate2() { - if (_midi_adlib) { - _midi_adlib->close(); - delete _midi_adlib; - _midi_adlib = 0; - } - - if (_midi_native) { - _midi_native->close(); - delete _midi_native; - _midi_native = 0; - } - - return 0; -} - -int IMuseInternal::enqueue_trigger(int sound, int marker) { - uint16 *p; - uint pos; - - pos = _queue_pos; - - p = _cmd_queue[pos].array; - p[0] = TRIGGER_ID; - p[1] = sound; - p[2] = marker; - - pos = (pos + 1) % ARRAYSIZE(_cmd_queue); - if (_queue_end == pos) { - _queue_pos = (pos - 1) % ARRAYSIZE(_cmd_queue); - return -1; - } - - _queue_pos = pos; - _queue_adding = true; - _queue_sound = sound; - _queue_marker = marker; - return 0; -} - -int32 IMuseInternal::doCommand(int a, int b, int c, int d, int e, int f, int g, int h) { - int args[8]; - args[0] = a; - args[1] = b; - args[2] = c; - args[3] = d; - args[4] = e; - args[5] = f; - args[6] = g; - args[7] = h; - return doCommand(8, args); -} - -int32 IMuseInternal::doCommand(int numargs, int a[]) { - int i; - - if (numargs < 1) - return -1; - byte cmd = a[0] & 0xFF; - byte param = a[0] >> 8; - Player *player = NULL; - - if (!_initialized && (cmd || param)) - return -1; - -#ifdef IMUSE_DEBUG - { - char string[128]; - sprintf(string, "doCommand - %d (%d/%d)", a[0], (int)param, (int)cmd); - for (i = 1; i < numargs; ++i) - sprintf(string + strlen(string), ", %d", a[i]); - debug(0, string); - } -#endif - - if (param == 0) { - switch (cmd) { - case 6: - if (a[1] > 127) - return -1; - else { - debug(0, "IMuse doCommand(6) - setImuseMasterVolume (%d)", a[1]); - return setImuseMasterVolume((a[1] << 1) | (a[1] ? 0 : 1)); // Convert from 0-127 to 0-255 - } - case 7: - debug(0, "IMuse doCommand(7) - getMasterVolume (%d)", a[1]); - return _master_volume / 2; // Convert from 0-255 to 0-127 - case 8: - return startSound(a[1]) ? 0 : -1; - case 9: - return stopSound(a[1]); - case 10: // FIXME: Sam and Max - Not sure if this is correct - return stopAllSounds(); - case 11: - return stopAllSounds(); - case 12: - // Sam & Max: Player-scope commands - player = findActivePlayer(a[1]); - if (!player) - return -1; - - switch (a[3]) { - case 6: - // Set player volume. - return player->setVolume(a[4]); - default: - error("IMuseInternal::doCommand(12) unsupported sub-command %d", a[3]); - } - return -1; - case 13: - return getSoundStatus(a[1]); - case 14: - // Sam and Max: Parameter fade - player = findActivePlayer(a[1]); - if (player) - return player->addParameterFader(a[3], a[4], a[5]); - return -1; - - case 15: - // Sam & Max: Set hook for a "maybe" jump - player = findActivePlayer(a[1]); - if (player) { - player->setHook(0, a[3], 0); - return 0; - } - return -1; - case 16: - debug(0, "IMuse doCommand(16) - set_volchan (%d, %d)", a[1], a[2]); - return set_volchan(a[1], a[2]); - case 17: - if (g_scumm->_game.id != GID_SAMNMAX) { - debug(0, "IMuse doCommand(17) - set_channel_volume (%d, %d)", a[1], a[2]); - return set_channel_volume(a[1], a[2]); - } else { - if (a[4]) { - int b[16]; - memset(b, 0, sizeof(b)); - for (i = 0; i < numargs; ++i) - b[i] = a[i]; - return ImSetTrigger(b[1], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11]); - } else { - return ImClearTrigger(a[1], a[3]); - } - } - case 18: - if (g_scumm->_game.id != GID_SAMNMAX) { - return set_volchan_entry(a[1], a[2]); - } else { - // Sam & Max: ImCheckTrigger. - // According to Mike's notes to Ender, - // this function returns the number of triggers - // associated with a particular player ID and - // trigger ID. - a[0] = 0; - for (i = 0; i < ARRAYSIZE(_snm_triggers); ++i) { - if (_snm_triggers[i].sound == a[1] && _snm_triggers[i].id && - (a[3] == -1 || _snm_triggers[i].id == a[3])) - { - ++a[0]; - } - } - return a[0]; - } - case 19: - // Sam & Max: ImClearTrigger - // This should clear a trigger that's been set up - // with ImSetTrigger(cmd == 17). Seems to work.... - return ImClearTrigger(a[1], a[3]); - case 20: - // Sam & Max: Deferred Command - addDeferredCommand(a[1], a[2], a[3], a[4], a[5], a[6], a[7]); - return 0; - case 2: - case 3: - return 0; - default: - error("doCommand(%d [%d/%d], %d, %d, %d, %d, %d, %d, %d) unsupported", a[0], param, cmd, a[1], a[2], a[3], a[4], a[5], a[6], a[7]); - } - } else if (param == 1) { - if ((1 << cmd) & 0x783FFF) { - player = findActivePlayer(a[1]); - if (!player) - return -1; - if ((1 << cmd) & (1 << 11 | 1 << 22)) { - assert(a[2] >= 0 && a[2] <= 15); - player = (Player *)player->getPart(a[2]); - if (!player) - return -1; - } - } - - switch (cmd) { - case 0: - if (g_scumm->_game.id == GID_SAMNMAX) { - if (a[3] == 1) // Measure number - return ((player->getBeatIndex() - 1) >> 2) + 1; - else if (a[3] == 2) // Beat number - return player->getBeatIndex(); - return -1; - } else { - return player->getParam(a[2], a[3]); - } - case 1: - if (g_scumm->_game.id == GID_SAMNMAX) { - // FIXME: Could someone verify this? - // - // This jump instruction is known to be used in - // the following cases: - // - // 1) Going anywhere on the USA map - // 2) Winning the Wak-A-Rat game - // 3) Losing or quitting the Wak-A-Rat game - // 4) Conroy hitting Max with a golf club - // - // For all these cases the position parameters - // are always the same: 2, 1, 0, 0. - // - // 5) When leaving the bigfoot party. The - // position parameters are: 3, 4, 300, 0 - // 6) At Frog Rock, when the UFO appears. The - // position parameters are: 10, 4, 400, 1 - // - // The last two cases used to be buggy, so I - // have made a change to how the last two - // position parameters are handled. I still do - // not know if it's correct, but it sounds - // good to me at least. - - debug(0, "doCommand(%d [%d/%d], %d, %d, %d, %d, %d, %d, %d)", a[0], param, cmd, a[1], a[2], a[3], a[4], a[5], a[6], a[7]); - player->jump(a[3] - 1, (a[4] - 1) * 4 + a[5], a[6] + ((a[7] * player->getTicksPerBeat()) >> 2)); - } else - player->setPriority(a[2]); - return 0; - case 2: - return player->setVolume(a[2]); - case 3: - player->setPan(a[2]); - return 0; - case 4: - return player->setTranspose(a[2], a[3]); - case 5: - player->setDetune(a[2]); - return 0; - case 6: - player->setSpeed(a[2]); - return 0; - case 7: - return player->jump(a[2], a[3], a[4]) ? 0 : -1; - case 8: - return player->scan(a[2], a[3], a[4]); - case 9: - return player->setLoop(a[2], a[3], a[4], a[5], a[6]) ? 0 : -1; - case 10: - player->clearLoop(); - return 0; - case 11: - ((Part *)player)->set_onoff(a[3] != 0); - return 0; - case 12: - return player->setHook(a[2], a[3], a[4]); - case 13: - return player->addParameterFader(ParameterFader::pfVolume, a[2], a[3]); - case 14: - return enqueue_trigger(a[1], a[2]); - case 15: - return enqueue_command(a[1], a[2], a[3], a[4], a[5], a[6], a[7]); - case 16: - return clear_queue(); - case 19: - return player->getParam(a[2], a[3]); - case 20: - return player->setHook(a[2], a[3], a[4]); - case 21: - return -1; - case 22: - ((Part *)player)->volume(a[3]); - return 0; - case 23: - return query_queue(a[1]); - case 24: - return 0; - default: - error("doCommand(%d [%d/%d], %d, %d, %d, %d, %d, %d, %d) unsupported", a[0], param, cmd, a[1], a[2], a[3], a[4], a[5], a[6], a[7]); - return -1; - } - } - - return -1; -} - -int32 IMuseInternal::ImSetTrigger(int sound, int id, int a, int b, int c, int d, int e, int f, int g, int h) { - // Sam & Max: ImSetTrigger. - // Sets a trigger for a particular player and - // marker ID, along with doCommand parameters - // to invoke at the marker. The marker is - // represented by MIDI SysEx block 00 xx(F7) - // where "xx" is the marker ID. - uint16 oldest_trigger = 0; - ImTrigger *oldest_ptr = NULL; - - int i; - ImTrigger *trig = _snm_triggers; - for (i = ARRAYSIZE(_snm_triggers); i; --i, ++trig) { - if (!trig->id) - break; - // We used to only compare 'id' and 'sound' here, but at least - // at the Dino Bungie Memorial that causes the music to stop - // after getting the T-Rex tooth. See bug #888161. - if (trig->id == id && trig->sound == sound && trig->command[0] == a) - break; - - uint16 diff; - if (trig->expire <= _snm_trigger_index) - diff = _snm_trigger_index - trig->expire; - else - diff = 0x10000 - trig->expire + _snm_trigger_index; - - if (!oldest_ptr || oldest_trigger < diff) { - oldest_ptr = trig; - oldest_trigger = diff; - } - } - - // If we didn't find a trigger, see if we can expire one. - if (!i) { - if (!oldest_ptr) - return -1; - trig = oldest_ptr; - } - - trig->id = id; - trig->sound = sound; - trig->expire = (++_snm_trigger_index & 0xFFFF); - trig->command[0] = a; - trig->command[1] = b; - trig->command[2] = c; - trig->command[3] = d; - trig->command[4] = e; - trig->command[5] = f; - trig->command[6] = g; - trig->command[7] = h; - - // If the command is to start a sound, stop that sound if it's already playing. - // This fixes some carnival music problems. - // NOTE: We ONLY do this if the sound that will trigger the command is actually - // playing. Otherwise, there's a problem when exiting and re-entering the - // Bumpusville mansion. Ref Bug #780918. - if (trig->command[0] == 8 && getSoundStatus(trig->command[1]) && getSoundStatus(sound)) - stopSound(trig->command[1]); - return 0; -} - -int32 IMuseInternal::ImClearTrigger(int sound, int id) { - int count = 0; - int i; - ImTrigger *trig = _snm_triggers; - for (i = ARRAYSIZE(_snm_triggers); i; --i, ++trig) { - if ((sound == -1 || trig->sound == sound) && trig->id && (id == -1 || trig->id == id)) { - trig->sound = trig->id = 0; - ++count; - } - } - return (count > 0) ? 0 : -1; -} - -int32 IMuseInternal::ImFireAllTriggers(int sound) { - if (!sound) - return 0; - int count = 0; - int i; - for (i = 0; i < ARRAYSIZE(_snm_triggers); ++i) { - if (_snm_triggers[i].sound == sound) { - _snm_triggers[i].sound = _snm_triggers[i].id = 0; - doCommand(8, _snm_triggers[i].command); - ++count; - } - } - return (count > 0) ? 0 : -1; -} - -int IMuseInternal::set_channel_volume(uint chan, uint vol) -{ - if (chan >= 8 || vol > 127) - return -1; - - _channel_volume[chan] = vol; - _channel_volume_eff[chan] = _master_volume * _music_volume * vol / 255 / 255; - update_volumes(); - return 0; -} - -void IMuseInternal::update_volumes() { - Player *player; - int i; - - for (i = ARRAYSIZE(_players), player = _players; i != 0; i--, player++) { - if (player->isActive()) - player->setVolume(player->getVolume()); - } -} - -int IMuseInternal::set_volchan_entry(uint a, uint b) { - if (a >= 8) - return -1; - _volchan_table[a] = b; - return 0; -} - -int HookDatas::query_param(int param, byte chan) { - switch (param) { - case 18: - return _jump[0]; - case 19: - return _transpose; - case 20: - return _part_onoff[chan]; - case 21: - return _part_volume[chan]; - case 22: - return _part_program[chan]; - case 23: - return _part_transpose[chan]; - default: - return -1; - } -} - -int HookDatas::set(byte cls, byte value, byte chan) { - switch (cls) { - case 0: - if (value != _jump[0]) { - _jump[1] = _jump[0]; - _jump[0] = value; - } - break; - case 1: - _transpose = value; - break; - case 2: - if (chan < 16) - _part_onoff[chan] = value; - else if (chan == 16) - memset(_part_onoff, value, 16); - break; - case 3: - if (chan < 16) - _part_volume[chan] = value; - else if (chan == 16) - memset(_part_volume, value, 16); - break; - case 4: - if (chan < 16) - _part_program[chan] = value; - else if (chan == 16) - memset(_part_program, value, 16); - break; - case 5: - if (chan < 16) - _part_transpose[chan] = value; - else if (chan == 16) - memset(_part_transpose, value, 16); - break; - default: - return -1; - } - return 0; -} - -Player *IMuseInternal::findActivePlayer(int id) { - int i; - Player *player = _players; - - for (i = ARRAYSIZE(_players); i != 0; i--, player++) { - if (player->isActive() && player->getID() == (uint16)id) - return player; - } - return NULL; -} - -int IMuseInternal::get_volchan_entry(uint a) { - if (a < 8) - return _volchan_table[a]; - return -1; -} - -uint32 IMuseInternal::property(int prop, uint32 value) { - switch (prop) { - case IMuse::PROP_TEMPO_BASE: - // This is a specified as a percentage of normal - // music speed. The number must be an integer - // ranging from 50 to 200(for 50% to 200% normal speed). - if (value >= 50 && value <= 200) - _tempoFactor = value; - break; - - case IMuse::PROP_NATIVE_MT32: - _native_mt32 = (value > 0); - Instrument::nativeMT32(_native_mt32); - if (_midi_native && _native_mt32) - initMT32(_midi_native); - break; - - case IMuse::PROP_GS: - _enable_gs = (value > 0); - - // If True Roland MT-32 is not selected, run in GM or GS mode. - // If it is selected, change the Roland GS synth to MT-32 mode. - if (_midi_native && !_native_mt32) - initGM(_midi_native); - else if (_midi_native && _native_mt32 && _enable_gs) { - _sc55 = true; - initGM(_midi_native); - } - break; - - case IMuse::PROP_LIMIT_PLAYERS: - if (value > 0 && value <= ARRAYSIZE(_players)) - _player_limit = (int)value; - break; - - case IMuse::PROP_RECYCLE_PLAYERS: - _recycle_players = (value != 0); - break; - - case IMuse::PROP_DIRECT_PASSTHROUGH: - _direct_passthrough = (value != 0); - break; - } - - return 0; -} - -void IMuseInternal::setBase(byte **base) { - _base_sounds = base; -} - -IMuseInternal *IMuseInternal::create(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver) { - IMuseInternal *i = new IMuseInternal; - i->initialize(syst, nativeMidiDriver, adlibMidiDriver); - return i; -} - -int IMuseInternal::initialize(OSystem *syst, MidiDriver *native_midi, MidiDriver *adlib_midi) { - int i; - - _midi_native = native_midi; - _midi_adlib = adlib_midi; - if (native_midi != NULL) - initMidiDriver(native_midi); - if (adlib_midi != NULL) - initMidiDriver(adlib_midi); - - if (!_tempoFactor) - _tempoFactor = 100; - _master_volume = 255; - - for (i = 0; i != 8; i++) - _channel_volume[i] = _channel_volume_eff[i] = _volchan_table[i] = 127; - - init_players(); - init_queue(); - init_parts(); - - _initialized = true; - - return 0; -} - -void IMuseInternal::initMidiDriver(MidiDriver *midi) { - // Open MIDI driver - int result = midi->open(); - if (result) - error("IMuse initialization - %s", MidiDriver::getErrorName(result)); - - // Connect to the driver's timer - midi->setTimerCallback(midi, &IMuseInternal::midiTimerCallback); -} - -void IMuseInternal::initMT32(MidiDriver *midi) { - byte buffer[52]; - char info[256] = "ScummVM "; - int len; - - // Reset the MT-32 - memcpy(&buffer[0], "\x41\x10\x16\x12\x7f\x00\x00\x01\x00", 9); - midi->sysEx(buffer, 9); - g_system->delayMillis(100); - - // Compute version string (truncated to 20 chars max.) - strcat(info, gScummVMVersion); - len = strlen(info); - if (len > 20) - len = 20; - - // Display a welcome message on MT-32 displays. - memcpy(&buffer[4], "\x20\x00\x00", 3); - memcpy(&buffer[7], " ", 20); - memcpy(buffer + 7 +(20 - len) / 2, info, len); - byte checksum = 0; - for (int i = 4; i < 27; ++i) - checksum -= buffer[i]; - buffer[27] = checksum & 0x7F; - midi->sysEx(buffer, 28); - g_system->delayMillis(500); - - // Setup master tune, reverb mode, reverb time, reverb level, - // channel mapping, partial reserve and master volume - memcpy(&buffer[4], "\x10\x00\x00\x40\x00\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x64\x77", 27); - midi->sysEx(buffer, 31); - g_system->delayMillis(250); - - // Map percussion to notes 24 - 34 without reverb - memcpy(&buffer[4], "\x03\x01\x10\x40\x64\x07\x00\x4a\x64\x06\x00\x41\x64\x07\x00\x4b\x64\x08\x00\x45\x64\x06\x00\x44\x64\x0b\x00\x51\x64\x05\x00\x43\x64\x08\x00\x50\x64\x07\x00\x42\x64\x03\x00\x4c\x64\x07\x00\x44", 48); - midi->sysEx(buffer, 52); - g_system->delayMillis(250); -} - -void IMuseInternal::initGM(MidiDriver *midi) { - byte buffer[11]; - int i; - - // General MIDI System On message - // Resets all GM devices to default settings - memcpy(&buffer[0], "\xF0\x7E\x7F\x09\x01\xF7", 6); - midi->sysEx(buffer, 6); - debug(2, "GM SysEx: GM System On"); - g_system->delayMillis(200); - - if (_enable_gs) { - - // All GS devices recognize the GS Reset command, - // even with Roland's ID. It is impractical to - // support other manufacturers' devices for - // further GS settings, as there are limitless - // numbers of them out there that would each - // require individual SysEx commands with unique IDs. - - // Roland GS SysEx ID - memcpy(&buffer[0], "\xF0\x41\x10\x42\x12", 5); - - // GS Reset - memcpy(&buffer[5], "\x40\x00\x7F\x00\x41\xF7", 6); - midi->sysEx(buffer, 11); - debug(2, "GS SysEx: GS Reset"); - g_system->delayMillis(200); - - if (_sc55) { - // This mode is for GS devices that support an MT-32-compatible - // Map, such as the Roland Sound Canvas line of modules. It - // will allow them to work with True MT-32 mode, but will - // obviously still ignore MT-32 SysEx (and thus custom - // instruments). - - // Set Channels 1-16 to SC-55 Map, then CM-64/32L Variation - for (i = 0; i < 16; ++i) { - midi->send(( 127 << 16) | (0 << 8) | (0xB0 | i)); - midi->send(( 1 << 16) | (32 << 8) | (0xB0 | i)); - midi->send(( 0 << 16) | (0 << 8) | (0xC0 | i)); - } - debug(2, "GS Program Change: CM-64/32L Map Selected"); - - // Set Percussion Channel to SC-55 Map (CC#32, 01H), then - // Switch Drum Map to CM-64/32L (MT-32 Compatible Drums) - midi->getPercussionChannel()->controlChange(0, 0); - midi->getPercussionChannel()->controlChange(32, 1); - midi->send(127 << 8 | 0xC0 | 9); - debug(2, "GS Program Change: Drum Map is CM-64/32L"); - - } - - // Set Master Chorus to 0. The MT-32 has no chorus capability. - memcpy(&buffer[5], "\x40\x01\x3A\x00\x05\xF7", 6); - midi->sysEx(buffer, 11); - debug(2, "GS SysEx: Master Chorus Level is 0"); - - // Set Channels 1-16 Reverb to 64, which is the - // equivalent of MT-32 default Reverb Level 5 - for (i = 0; i < 16; ++i) - midi->send(( 64 << 16) | (91 << 8) | (0xB0 | i)); - debug(2, "GM Controller 91 Change: Channels 1-16 Reverb Level is 64"); - - // Set Channels 1-16 Pitch Bend Sensitivity to - // 12 semitones; then lock the RPN by setting null. - for (i = 0; i < 16; ++i) { - midi->send(( 0 << 16) | (100 << 8) | (0xB0 | i)); - midi->send(( 0 << 16) | (101 << 8) | (0xB0 | i)); - midi->send(( 12 << 16) | (6 << 8) | (0xB0 | i)); - midi->send(( 0 << 16) | (38 << 8) | (0xB0 | i)); - midi->send(( 127 << 16) | (100 << 8) | (0xB0 | i)); - midi->send(( 127 << 16) | (101 << 8) | (0xB0 | i)); - } - debug(2, "GM Controller 6 Change: Channels 1-16 Pitch Bend Sensitivity is 12 semitones"); - - // Set channels 1-16 Mod. LFO1 Pitch Depth to 4 - memcpy(&buffer[5], "\x40\x20\x04\x04\x18\xF7", 6); - midi->sysEx(buffer, 11); - memcpy(&buffer[5], "\x40\x21\x04\x04\x17\xF7", 6); - midi->sysEx(buffer, 11); - memcpy(&buffer[5], "\x40\x22\x04\x04\x16\xF7", 6); - midi->sysEx(buffer, 11); - memcpy(&buffer[5], "\x40\x23\x04\x04\x15\xF7", 6); - midi->sysEx(buffer, 11); - memcpy(&buffer[5], "\x40\x24\x04\x04\x14\xF7", 6); - midi->sysEx(buffer, 11); - memcpy(&buffer[5], "\x40\x25\x04\x04\x13\xF7", 6); - midi->sysEx(buffer, 11); - memcpy(&buffer[5], "\x40\x26\x04\x04\x12\xF7", 6); - midi->sysEx(buffer, 11); - memcpy(&buffer[5], "\x40\x27\x04\x04\x11\xF7", 6); - midi->sysEx(buffer, 11); - memcpy(&buffer[5], "\x40\x28\x04\x04\x10\xF7", 6); - midi->sysEx(buffer, 11); - memcpy(&buffer[5], "\x40\x29\x04\x04\x0F\xF7", 6); - midi->sysEx(buffer, 11); - memcpy(&buffer[5], "\x40\x2A\x04\x04\x0E\xF7", 6); - midi->sysEx(buffer, 11); - memcpy(&buffer[5], "\x40\x2B\x04\x04\x0D\xF7", 6); - midi->sysEx(buffer, 11); - memcpy(&buffer[5], "\x40\x2C\x04\x04\x0C\xF7", 6); - midi->sysEx(buffer, 11); - memcpy(&buffer[5], "\x40\x2D\x04\x04\x0B\xF7", 6); - midi->sysEx(buffer, 11); - memcpy(&buffer[5], "\x40\x2E\x04\x04\x0A\xF7", 6); - midi->sysEx(buffer, 11); - memcpy(&buffer[5], "\x40\x2F\x04\x04\x09\xF7", 6); - midi->sysEx(buffer, 11); - debug(2, "GS SysEx: Channels 1-16 Mod. LFO1 Pitch Depth Level is 4"); - - // Set Percussion Channel Expression to 80 - midi->getPercussionChannel()->controlChange(11, 80); - debug(2, "GM Controller 11 Change: Percussion Channel Expression Level is 80"); - - // Turn off Percussion Channel Rx. Expression so that - // Expression cannot be modified. I don't know why, but - // Roland does it this way. - memcpy(&buffer[5], "\x40\x10\x0E\x00\x22\xF7", 6); - midi->sysEx(buffer, 11); - debug(2, "GS SysEx: Percussion Channel Rx. Expression is OFF"); - - // Change Reverb Character to 0. I don't think this - // sounds most like MT-32, but apparently Roland does. - memcpy(&buffer[5], "\x40\x01\x31\x00\x0E\xF7", 6); - midi->sysEx(buffer, 11); - debug(2, "GS SysEx: Reverb Character is 0"); - - // Change Reverb Pre-LF to 4, which is similar to - // what MT-32 reverb does. - memcpy(&buffer[5], "\x40\x01\x32\x04\x09\xF7", 6); - midi->sysEx(buffer, 11); - debug(2, "GS SysEx: Reverb Pre-LF is 4"); - - // Change Reverb Time to 106; the decay on Hall 2 - // Reverb is too fast compared to the MT-32's - memcpy(&buffer[5], "\x40\x01\x34\x6A\x21\xF7", 6); - midi->sysEx(buffer, 11); - debug(2, "GS SysEx: Reverb Time is 106"); - } -} - -void IMuseInternal::init_queue() { - _queue_adding = false; - _queue_pos = 0; - _queue_end = 0; - _trigger_count = 0; -} - -void IMuseInternal::pause(bool paused) { - if (_paused == paused) - return; - int vol = _music_volume; - if (paused) - _music_volume = 0; - update_volumes(); - _music_volume = vol; - - // Fix for Bug #817871. The MT-32 apparently fails - // sometimes to respond to a channel volume message - // (or only uses it for subsequent note events). - // The result is hanging notes on pause. Reportedly - // happens in the original distro, too. To fix that, - // just send AllNotesOff to the channels. - if (_midi_native && _native_mt32) { - for (int i = 0; i < 16; ++i) - _midi_native->send(123 << 8 | 0xB0 | i); - } - - _paused = paused; -} - -void IMuseInternal::handleDeferredCommands(MidiDriver *midi) { - uint32 advance = midi->getBaseTempo(); - - DeferredCommand *ptr = &_deferredCommands[0]; - int i; - for (i = ARRAYSIZE(_deferredCommands); i; --i, ++ptr) { - if (!ptr->time_left) - continue; - if (ptr->time_left <= advance) { - doCommand(ptr->a, ptr->b, ptr->c, ptr->d, ptr->e, ptr->f, 0, 0); - ptr->time_left = advance; - } - ptr->time_left -= advance; - } -} - -// "time" is interpreted as hundredths of a second. -// FIXME: Is that correct? -// We convert it to microseconds before prceeding -void IMuseInternal::addDeferredCommand(int time, int a, int b, int c, int d, int e, int f) { - DeferredCommand *ptr = &_deferredCommands[0]; - int i; - for (i = ARRAYSIZE(_deferredCommands); i; --i, ++ptr) { - if (!ptr->time_left) - break; - } - - if (i) { - ptr->time_left = time * 10000; - ptr->a = a; - ptr->b = b; - ptr->c = c; - ptr->d = d; - ptr->e = e; - ptr->f = f; - } -} - -//////////////////////////////////////////////////////////// -// -// IMuseInternal load/save implementation -// -//////////////////////////////////////////////////////////// - -int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm) { - const SaveLoadEntry mainEntries[] = { - MKLINE(IMuseInternal, _queue_end, sleUint8, VER(8)), - MKLINE(IMuseInternal, _queue_pos, sleUint8, VER(8)), - MKLINE(IMuseInternal, _queue_sound, sleUint16, VER(8)), - MKLINE(IMuseInternal, _queue_adding, sleByte, VER(8)), - MKLINE(IMuseInternal, _queue_marker, sleByte, VER(8)), - MKLINE(IMuseInternal, _queue_cleared, sleByte, VER(8)), - MKLINE(IMuseInternal, _master_volume, sleByte, VER(8)), - MKLINE(IMuseInternal, _trigger_count, sleUint16, VER(8)), - MKLINE(IMuseInternal, _snm_trigger_index, sleUint16, VER(54)), - MKARRAY(IMuseInternal, _channel_volume[0], sleUint16, 8, VER(8)), - MKARRAY(IMuseInternal, _volchan_table[0], sleUint16, 8, VER(8)), - MKEND() - }; - - const SaveLoadEntry cmdQueueEntries[] = { - MKARRAY(CommandQueue, array[0], sleUint16, 8, VER(23)), - MKEND() - }; - - // VolumeFader is obsolete. - const SaveLoadEntry volumeFaderEntries[] = { - MK_OBSOLETE(VolumeFader, player, sleUint16, VER(8), VER(16)), - MK_OBSOLETE(VolumeFader, active, sleUint8, VER(8), VER(16)), - MK_OBSOLETE(VolumeFader, curvol, sleUint8, VER(8), VER(16)), - MK_OBSOLETE(VolumeFader, speed_lo_max, sleUint16, VER(8), VER(16)), - MK_OBSOLETE(VolumeFader, num_steps, sleUint16, VER(8), VER(16)), - MK_OBSOLETE(VolumeFader, speed_hi, sleInt8, VER(8), VER(16)), - MK_OBSOLETE(VolumeFader, direction, sleInt8, VER(8), VER(16)), - MK_OBSOLETE(VolumeFader, speed_lo, sleInt8, VER(8), VER(16)), - MK_OBSOLETE(VolumeFader, speed_lo_counter, sleUint16, VER(8), VER(16)), - MKEND() - }; - - const SaveLoadEntry snmTriggerEntries[] = { - MKLINE(ImTrigger, sound, sleInt16, VER(54)), - MKLINE(ImTrigger, id, sleByte, VER(54)), - MKLINE(ImTrigger, expire, sleUint16, VER(54)), - MKARRAY(ImTrigger, command[0], sleUint16, 8, VER(54)), - MKEND() - }; - - int i; - - ser->saveLoadEntries(this, mainEntries); - ser->saveLoadArrayOf(_cmd_queue, ARRAYSIZE(_cmd_queue), sizeof(_cmd_queue[0]), cmdQueueEntries); - ser->saveLoadArrayOf(_snm_triggers, ARRAYSIZE(_snm_triggers), sizeof(_snm_triggers[0]), snmTriggerEntries); - - // The players - for (i = 0; i < ARRAYSIZE(_players); ++i) - _players[i].saveLoadWithSerializer(ser); - - // The parts - for (i = 0; i < ARRAYSIZE(_parts); ++i) - _parts[i].saveLoadWithSerializer(ser); - - { // Load/save the instrument definitions, which were revamped with V11. - Part *part = &_parts[0]; - if (ser->getVersion() >= VER(11)) { - for (i = ARRAYSIZE(_parts); i; --i, ++part) { - part->_instrument.saveOrLoad(ser); - } - } else { - for (i = ARRAYSIZE(_parts); i; --i, ++part) - part->_instrument.clear(); - } - } - - // VolumeFader has been replaced with the more generic ParameterFader. - // FIXME: replace this loop by something like - // if (loading && version <= 16) ser->skip(XXX bytes); - for (i = 0; i < 8; ++i) - ser->saveLoadEntries(0, volumeFaderEntries); - - if (ser->isLoading()) { - // Load all sounds that we need - fix_players_after_load(scumm); - fix_parts_after_load(); - setImuseMasterVolume(_master_volume); - - if (_midi_native) - reallocateMidiChannels(_midi_native); - if (_midi_adlib) - reallocateMidiChannels(_midi_adlib); - } - - return 0; -} - -void IMuseInternal::fix_parts_after_load() { - Part *part; - int i; - - for (i = ARRAYSIZE(_parts), part = _parts; i != 0; i--, part++) { - if (part->_player) - part->fix_after_load(); - } -} - -// Only call this routine from the main thread, -// since it uses getResourceAddress -void IMuseInternal::fix_players_after_load(ScummEngine *scumm) { - Player *player = _players; - int i; - - for (i = ARRAYSIZE(_players); i != 0; i--, player++) { - if (player->isActive()) { - scumm->getResourceAddress(rtSound, player->getID()); - player->fixAfterLoad(); - } - } -} - -Part::Part() { - _slot = 0; - _next = 0; - _prev = 0; - _mc = 0; - _player = 0; - _pitchbend = 0; - _pitchbend_factor = 0; - _transpose = 0; - _transpose_eff = 0; - _vol = 0; - _vol_eff = 0; - _detune = 0; - _detune_eff = 0; - _pan = 0; - _pan_eff = 0; - _on = false; - _modwheel = 0; - _pedal = false; - _pri = 0; - _pri_eff = 0; - _chan = 0; - _effect_level = 0; - _chorus = 0; - _percussion = 0; - _bank = 0; - _unassigned_instrument = false; -} - -void Part::saveLoadWithSerializer(Serializer *ser) { - const SaveLoadEntry partEntries[] = { - MKLINE(Part, _pitchbend, sleInt16, VER(8)), - MKLINE(Part, _pitchbend_factor, sleUint8, VER(8)), - MKLINE(Part, _transpose, sleInt8, VER(8)), - MKLINE(Part, _vol, sleUint8, VER(8)), - MKLINE(Part, _detune, sleInt8, VER(8)), - MKLINE(Part, _pan, sleInt8, VER(8)), - MKLINE(Part, _on, sleUint8, VER(8)), - MKLINE(Part, _modwheel, sleUint8, VER(8)), - MKLINE(Part, _pedal, sleUint8, VER(8)), - MK_OBSOLETE(Part, _program, sleUint8, VER(8), VER(16)), - MKLINE(Part, _pri, sleUint8, VER(8)), - MKLINE(Part, _chan, sleUint8, VER(8)), - MKLINE(Part, _effect_level, sleUint8, VER(8)), - MKLINE(Part, _chorus, sleUint8, VER(8)), - MKLINE(Part, _percussion, sleUint8, VER(8)), - MKLINE(Part, _bank, sleUint8, VER(8)), - MKEND() - }; - - int num; - if (ser->isSaving()) { - num = (_next ? (_next - _se->_parts + 1) : 0); - ser->saveUint16(num); - - num = (_prev ? (_prev - _se->_parts + 1) : 0); - ser->saveUint16(num); - - num = (_player ? (_player - _se->_players + 1) : 0); - ser->saveUint16(num); - } else { - num = ser->loadUint16(); - _next = (num ? &_se->_parts[num - 1] : 0); - - num = ser->loadUint16(); - _prev = (num ? &_se->_parts[num - 1] : 0); - - num = ser->loadUint16(); - _player = (num ? &_se->_players[num - 1] : 0); - } - ser->saveLoadEntries(this, partEntries); -} - -void Part::set_detune(int8 detune) { - _detune_eff = clamp((_detune = detune) + _player->getDetune(), -128, 127); - if (_mc) - sendPitchBend(); -} - -void Part::pitchBend(int16 value) { - _pitchbend = value; - if (_mc) - sendPitchBend(); -} - -void Part::volume(byte value) { - _vol_eff = ((_vol = value) + 1) * _player->getEffectiveVolume() >> 7; - if (_mc) - _mc->volume(_vol_eff); -} - -void Part::set_pri(int8 pri) { - _pri_eff = clamp((_pri = pri) + _player->getPriority(), 0, 255); - if (_mc) - _mc->priority(_pri_eff); -} - -void Part::set_pan(int8 pan) { - _pan_eff = clamp((_pan = pan) + _player->getPan(), -64, 63); - if (_mc) - _mc->panPosition(_pan_eff + 0x40); -} - -void Part::set_transpose(int8 transpose) { - _transpose_eff = transpose_clamp((_transpose = transpose) + _player->getTranspose(), -24, 24); - if (_mc) - sendPitchBend(); -} - -void Part::sustain(bool value) { - _pedal = value; - if (_mc) - _mc->sustain(value); -} - -void Part::modulationWheel(byte value) { - _modwheel = value; - if (_mc) - _mc->modulationWheel(value); -} - -void Part::chorusLevel(byte value) { - _chorus = value; - if (_mc) - _mc->chorusLevel(value); -} - -void Part::effectLevel(byte value) -{ - _effect_level = value; - if (_mc) - _mc->effectLevel(value); -} - -void Part::fix_after_load() { - set_transpose(_transpose); - volume(_vol); - set_detune(_detune); - set_pri(_pri); - set_pan(_pan); - sendAll(); -} - -void Part::pitchBendFactor(byte value) { - if (value > 12) - return; - pitchBend(0); - _pitchbend_factor = value; - if (_mc) - _mc->pitchBendFactor(value); -} - -void Part::set_onoff(bool on) { - if (_on != on) { - _on = on; - if (!on) - off(); - if (!_percussion) - _player->_se->reallocateMidiChannels(_player->getMidiDriver()); - } -} - -void Part::set_instrument(byte * data) { - _instrument.adlib(data); - if (clearToTransmit()) - _instrument.send(_mc); -} - -void Part::load_global_instrument(byte slot) { - _player->_se->copyGlobalAdlibInstrument(slot, &_instrument); - if (clearToTransmit()) - _instrument.send(_mc); -} - -void Part::noteOn(byte note, byte velocity) { - if (!_on) - return; - - MidiChannel *mc = _mc; - - // DEBUG - if (_unassigned_instrument && !_percussion) { - _unassigned_instrument = false; - if (!_instrument.isValid()) { - debug(0, "[%02d] No instrument specified", (int)_chan); - return; - } - } - - if (mc && _instrument.isValid()) { - mc->noteOn(note, velocity); - } else if (_percussion) { - mc = _player->getMidiDriver()->getPercussionChannel(); - if (!mc) - return; - static byte prev_vol_eff = 128; - if (_vol_eff != prev_vol_eff){ - mc->volume(_vol_eff); - prev_vol_eff = _vol_eff; - } - if ((note < 35) && (!_player->_se->isNativeMT32())) - note = Instrument::_gmRhythmMap[note]; - - mc->noteOn(note, velocity); - } -} - -void Part::noteOff(byte note) { - if (!_on) - return; - - MidiChannel *mc = _mc; - if (mc) { - mc->noteOff(note); - } else if (_percussion) { - mc = _player->getMidiDriver()->getPercussionChannel(); - if (mc) - mc->noteOff(note); - } -} - -void Part::init() { - _player = NULL; - _next = NULL; - _prev = NULL; - _mc = NULL; -} - -void Part::setup(Player *player) { - _player = player; - - _percussion = (player->isMIDI() && _chan == 9); // true; - _on = true; - _pri_eff = player->getPriority(); - _pri = 0; - _vol = 127; - _vol_eff = player->getEffectiveVolume(); - _pan = clamp(player->getPan(), -64, 63); - _transpose_eff = player->getTranspose(); - _transpose = 0; - _detune = 0; - _detune_eff = player->getDetune(); - _pitchbend_factor = 2; - _pitchbend = 0; - _effect_level = 64; - _instrument.clear(); - _unassigned_instrument = true; - _chorus = 0; - _modwheel = 0; - _bank = 0; - _pedal = false; - _mc = NULL; -} - -void Part::uninit() { - if (!_player) - return; - off(); - _player->removePart(this); - _player = NULL; -} - -void Part::off() { - if (_mc) { - _mc->allNotesOff(); - _mc->release(); - _mc = NULL; - } -} - -bool Part::clearToTransmit() { - if (_mc) - return true; - if (_instrument.isValid()) - _player->_se->reallocateMidiChannels(_player->getMidiDriver()); - return false; -} - -void Part::sendAll() { - if (!clearToTransmit()) - return; - _mc->pitchBendFactor(_pitchbend_factor); - sendPitchBend(); - _mc->volume(_vol_eff); - _mc->sustain(_pedal); - _mc->modulationWheel(_modwheel); - _mc->panPosition(_pan_eff + 0x40); - _mc->effectLevel(_effect_level); - if (_instrument.isValid()) - _instrument.send(_mc); - _mc->chorusLevel(_chorus); - _mc->priority(_pri_eff); -} - -void Part::sendPitchBend() { - int16 bend = _pitchbend; - // RPN-based pitchbend range doesn't work for the MT32, - // so we'll do the scaling ourselves. - if (_player->_se->isNativeMT32()) - bend = bend * _pitchbend_factor / 12; - _mc->pitchBend(clamp(bend + (_detune_eff * 64 / 12) + (_transpose_eff * 8192 / 12), -8192, 8191)); -} - -void Part::programChange(byte value) { - _bank = 0; - _instrument.program(value, _player->isMT32()); - if (clearToTransmit()) - _instrument.send(_mc); -} - -void Part::set_instrument(uint b) { - _bank = (byte)(b >> 8); - if (_bank) - error("Non-zero instrument bank selection. Please report this"); - _instrument.program((byte)b, _player->isMT32()); - if (clearToTransmit()) - _instrument.send(_mc); -} - -void Part::allNotesOff() { - if (!_mc) - return; - _mc->allNotesOff(); -} - -//////////////////////////////////////// -// -// Some more IMuseInternal stuff -// -//////////////////////////////////////// - -void IMuseInternal::midiTimerCallback(void *data) { - MidiDriver *driver = (MidiDriver *)data; - if (g_scumm->_imuse) - g_scumm->_imuse->on_timer(driver); -} - -void IMuseInternal::reallocateMidiChannels(MidiDriver *midi) { - Part *part, *hipart; - int i; - byte hipri, lopri; - Part *lopart; - - while (true) { - hipri = 0; - hipart = NULL; - for (i = 32, part = _parts; i; i--, part++) { - if (part->_player && part->_player->getMidiDriver() == midi && - !part->_percussion && part->_on && - !part->_mc && part->_pri_eff >= hipri) { - hipri = part->_pri_eff; - hipart = part; - } - } - - if (!hipart) - return; - - if ((hipart->_mc = midi->allocateChannel()) == NULL) { - lopri = 255; - lopart = NULL; - for (i = 32, part = _parts; i; i--, part++) { - if (part->_mc && part->_mc->device() == midi && part->_pri_eff <= lopri) { - lopri = part->_pri_eff; - lopart = part; - } - } - - if (lopart == NULL || lopri >= hipri) - return; - lopart->off(); - - if ((hipart->_mc = midi->allocateChannel()) == NULL) - return; - } - hipart->sendAll(); - } -} - -void IMuseInternal::setGlobalAdlibInstrument(byte slot, byte *data) { - if (slot < 32) { - _global_adlib_instruments[slot].adlib(data); - } -} - -void IMuseInternal::copyGlobalAdlibInstrument(byte slot, Instrument *dest) { - if (slot >= 32) - return; - _global_adlib_instruments[slot].copy_to(dest); -} - -//////////////////////////////////////////////////////////// -// -// IMuse implementation -// -// IMuse actually serves as a concurency monitor front-end -// to IMuseInternal and ensures that only one thread -// accesses the object at a time. This is necessary to -// prevent scripts and the MIDI parser from yanking objects -// out from underneath each other. -// -//////////////////////////////////////////////////////////// - -IMuse::IMuse(OSystem *system, IMuseInternal *target) - : _system(system), _target(target) { - _mutex = system->createMutex(); -} - -IMuse::~IMuse() { - if (_mutex) - _system->deleteMutex(_mutex); - if (_target) - delete _target; -} - -inline void IMuse::in() const { - _system->lockMutex(_mutex); -} -inline void IMuse::out() const { - _system->unlockMutex(_mutex); -} - -void IMuse::on_timer(MidiDriver *midi) { in(); _target->on_timer(midi); out(); } -void IMuse::pause(bool paused) { in(); _target->pause(paused); out(); } -int IMuse::save_or_load(Serializer *ser, ScummEngine *scumm) { in(); int ret = _target->save_or_load(ser, scumm); out(); return ret; } -void IMuse::setMusicVolume(int vol) { in(); _target->setMusicVolume(vol); out(); } -void IMuse::startSound(int sound) { in(); _target->startSound(sound); out(); } -void IMuse::stopSound(int sound) { in(); _target->stopSound(sound); out(); } -void IMuse::stopAllSounds() { in(); _target->stopAllSounds(); out(); } -int IMuse::getSoundStatus(int sound) const { in(); int ret = _target->getSoundStatus(sound, true); out(); return ret; } -bool IMuse::get_sound_active(int sound) const { in(); bool ret = _target->getSoundStatus(sound, false) ? 1 : 0; out(); return ret; } -int IMuse::getMusicTimer() const { in(); int ret = _target->getMusicTimer(); out(); return ret; } -int32 IMuse::doCommand(int a, int b, int c, int d, int e, int f, int g, int h) { in(); int32 ret = _target->doCommand(a,b,c,d,e,f,g,h); out(); return ret; } -int32 IMuse::doCommand(int numargs, int args[]) { in(); int32 ret = _target->doCommand(numargs, args); out(); return ret; } -int IMuse::clear_queue() { in(); int ret = _target->clear_queue(); out(); return ret; } -void IMuse::setBase(byte **base) { in(); _target->setBase(base); out(); } -uint32 IMuse::property(int prop, uint32 value) { in(); uint32 ret = _target->property(prop, value); out(); return ret; } -void IMuse::terminate() { in(); _target->terminate1(); out(); _target->terminate2(); } - -// The IMuse::create method provides a front-end factory -// for creating IMuseInternal without exposing that class -// to the client. -IMuse *IMuse::create(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver) { - IMuseInternal *engine = IMuseInternal::create(syst, nativeMidiDriver, adlibMidiDriver); - return new IMuse(syst, engine); -} - -} // End of namespace Scumm diff --git a/engines/scumm/imuse.h b/engines/scumm/imuse.h deleted file mode 100644 index 0c9a9206ba..0000000000 --- a/engines/scumm/imuse.h +++ /dev/null @@ -1,85 +0,0 @@ -/* ScummVM - Scumm Interpreter - * Copyright (C) 2001 Ludvig Strigeus - * Copyright (C) 2001-2006 The ScummVM project - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#ifndef IMUSE_H -#define IMUSE_H - -#include "common/scummsys.h" -#include "common/mutex.h" -#include "scumm/music.h" - -class MidiDriver; -class OSystem; - -namespace Scumm { - -class IMuseInternal; -class ScummEngine; -class Serializer; - -class IMuse : public MusicEngine { -private: - OSystem *_system; - IMuseInternal *_target; - mutable Common::MutexRef _mutex; - - IMuse(OSystem *system, IMuseInternal *target); - void in() const; - void out() const; - -public: - ~IMuse(); - - enum { - PROP_TEMPO_BASE, - PROP_NATIVE_MT32, - PROP_GS, - PROP_LIMIT_PLAYERS, - PROP_RECYCLE_PLAYERS, - PROP_DIRECT_PASSTHROUGH - }; - - void on_timer(MidiDriver *midi); - void pause(bool paused); - int save_or_load(Serializer *ser, ScummEngine *scumm); - void setMusicVolume(int vol); - void startSound(int sound); - void stopSound(int sound); - void stopAllSounds(); - int getSoundStatus(int sound) const; - bool get_sound_active(int sound) const; - int getMusicTimer() const; - int32 doCommand(int a, int b, int c, int d, int e, int f, int g, int h); - int32 doCommand(int numargs, int args[]); - int clear_queue(); - void setBase(byte **base); - uint32 property(int prop, uint32 value); - void terminate(); - - // Factory methods - static IMuse *create(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver); -}; - -} // End of namespace Scumm - -#endif diff --git a/engines/scumm/imuse/imuse.cpp b/engines/scumm/imuse/imuse.cpp new file mode 100644 index 0000000000..41e4a1aa96 --- /dev/null +++ b/engines/scumm/imuse/imuse.cpp @@ -0,0 +1,2043 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2001 Ludvig Strigeus + * Copyright (C) 2001-2006 The ScummVM project + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/stdafx.h" + +#include "base/version.h" + +#include "common/util.h" +#include "common/system.h" + +#include "scumm/imuse/imuse.h" +#include "scumm/imuse/imuse_internal.h" +#include "scumm/imuse/instrument.h" +#include "scumm/saveload.h" +#include "scumm/scumm.h" +#include "scumm/util.h" + +#include "sound/mididrv.h" + + +namespace Scumm { + +//////////////////////////////////////// +// +// IMuseInternal implementation +// +//////////////////////////////////////// + +IMuseInternal::IMuseInternal() : +_native_mt32(false), +_enable_gs(false), +_sc55(false), +_midi_adlib(0), +_midi_native(0), +_base_sounds(0), +_paused(false), +_initialized(false), +_tempoFactor(0), +_player_limit(ARRAYSIZE(_players)), +_recycle_players(false), +_direct_passthrough(false), +_queue_end(0), +_queue_pos(0), +_queue_sound(0), +_queue_adding(0), +_queue_marker(0), +_queue_cleared(0), +_master_volume(0), +_music_volume(0), +_trigger_count(0), +_snm_trigger_index(0) { + memset(_channel_volume,0,sizeof(_channel_volume)); + memset(_channel_volume_eff,0,sizeof(_channel_volume_eff)); + memset(_volchan_table,0,sizeof(_volchan_table)); +} + +byte *IMuseInternal::findStartOfSound(int sound) { + byte *ptr = NULL; + int32 size, pos; + + if (_base_sounds) + ptr = _base_sounds[sound]; + + if (ptr == NULL) { + debug(1, "IMuseInternal::findStartOfSound(): Sound %d doesn't exist!", sound); + return NULL; + } + + // Check for old-style headers first, like 'RO' + if (ptr[4] == 'R' && ptr[5] == 'O'&& ptr[6] != 'L') + return ptr + 4; + if (ptr[8] == 'S' && ptr[9] == 'O') + return ptr + 8; + + ptr += 8; + size = READ_BE_UINT32(ptr); + ptr += 4; + + // Okay, we're looking for one of those things: either + // an 'MThd' tag (for SMF), or a 'FORM' tag (for XMIDI). + size = 48; // Arbitrary; we should find our tag within the first 48 bytes of the resource + pos = 0; + while (pos < size) { + if (!memcmp(ptr + pos, "MThd", 4) || !memcmp(ptr + pos, "FORM", 4)) + return ptr + pos; + ++pos; // We could probably iterate more intelligently + } + + debug(3, "IMuseInternal::findStartOfSound(): Failed to align on sound %d!", sound); + return 0; +} + +bool IMuseInternal::isMT32(int sound) { + byte *ptr = NULL; + uint32 tag; + + if (_base_sounds) + ptr = _base_sounds[sound]; + + if (ptr == NULL) + return false; + + tag = *(((uint32 *)ptr) + 1); + switch (tag) { + case MKID('ADL '): + case MKID('ASFX'): // Special AD class for old Adlib sound effects + case MKID('SPK '): + return false; + + case MKID('AMI '): + case MKID('ROL '): + return true; + + case MKID('MAC '): // Occurs in the Mac version of FOA and MI2 + return true; + + case MKID('GMD '): + case MKID('MIDI'): // Occurs in Sam & Max + return false; + } + + // Old style 'RO' has equivalent properties to 'ROL' + if (ptr[4] == 'R' && ptr[5] == 'O') + return true; + // Euphony tracks show as 'SO' and have equivalent properties to 'ADL' + if (ptr[8] == 'S' && ptr[9] == 'O') + return false; + + error("Unknown music type: '%s'", tag2str(tag)); + + return false; +} + +bool IMuseInternal::isMIDI(int sound) { + byte *ptr = NULL; + uint32 tag; + + if (_base_sounds) + ptr = _base_sounds[sound]; + + if (ptr == NULL) + return false; + + tag = *(((uint32 *)ptr) + 1); + switch (tag) { + case MKID('ADL '): + case MKID('ASFX'): // Special AD class for old Adlib sound effects + case MKID('SPK '): + return false; + + case MKID('AMI '): + case MKID('ROL '): + return true; + + case MKID('MAC '): // Occurs in the Mac version of FOA and MI2 + return true; + + case MKID('GMD '): + case MKID('MIDI'): // Occurs in Sam & Max + return true; + } + + // Old style 'RO' has equivalent properties to 'ROL' + if (ptr[4] == 'R' && ptr[5] == 'O') + return true; + // Euphony tracks show as 'SO' and have equivalent properties to 'ADL' + // FIXME: Right now we're pretending it's GM. + if (ptr[8] == 'S' && ptr[9] == 'O') + return true; + + error("Unknown music type: '%s'", tag2str(tag)); + + return false; +} + +MidiDriver *IMuseInternal::getBestMidiDriver(int sound) { + MidiDriver *driver = NULL; + + if (isMIDI(sound)) { + if (_midi_native) { + driver = _midi_native; + } else { + // Route it through Adlib anyway. + driver = _midi_adlib; + } + } else { + driver = _midi_adlib; + } + return driver; +} + +bool IMuseInternal::startSound(int sound) { + Player *player; + void *ptr; + + // Do not start a sound if it is already set to start on an ImTrigger + // event. This fixes carnival music problems where a sound has been set + // to trigger at the right time, but then is started up immediately + // anyway, only to be restarted later when the trigger occurs. + // + // However, we have to make sure the sound with the trigger is actually + // playing, otherwise the music may stop when Sam and Max are thrown + // out of Bumpusville, because entering the mansion sets up a trigger + // for a sound that isn't necessarily playing. This is somewhat related + // to bug #780918. + + int i; + ImTrigger *trigger = _snm_triggers; + for (i = ARRAYSIZE(_snm_triggers); i; --i, ++trigger) { + if (trigger->sound && trigger->id && trigger->command[0] == 8 && trigger->command[1] == sound && getSoundStatus(trigger->sound)) + return false; + } + + ptr = findStartOfSound(sound); + if (!ptr) { + debug(2, "IMuseInternal::startSound(): Couldn't find sound %d!", sound); + return false; + } + + // Check which MIDI driver this track should use. + // If it's NULL, it ain't something we can play. + MidiDriver *driver = getBestMidiDriver(sound); + if (!driver) + return false; + + // If the requested sound is already playing, start it over + // from scratch. This was originally a hack to prevent Sam & Max + // iMuse messiness while upgrading the iMuse engine, but it + // is apparently necessary to deal with fade-and-restart + // race conditions that were observed in MI2. Reference + // Bug #590511 and Patch #607175 (which was reversed to fix + // an FOA regression: Bug #622606). + player = findActivePlayer(sound); + if (!player) + player = allocate_player(128); + if (!player) + return false; + + // HACK: This is to work around a problem at the Dino Bungie Memorial. + // There are three pieces of music involved here: + // + // 80 - Main theme (looping) + // 81 - Music when entering Rex's and Wally's room (not looping) + // 82 - Music when listening to Rex or Wally + // + // When entering, tune 81 starts, tune 80 is faded down (not out) and + // a trigger is set in tune 81 to fade tune 80 back up. + // + // When listening to Rex or Wally, tune 82 is started, tune 81 is faded + // out and tune 80 is faded down even further. + // + // However, when tune 81 is faded out its trigger will cause tune 80 to + // fade back up, resulting in two tunes being played simultaneously at + // full blast. It's no use trying to keep tune 81 playing at volume 0. + // It doesn't loop, so eventually it will terminate on its own. + // + // I don't know how the original interpreter handled this - or even if + // it handled it at all - but it looks like sloppy scripting to me. Our + // workaround is to clear the trigger if the player listens to Rex or + // Wally before tune 81 has finished on its own. + + if (g_scumm->_game.id == GID_SAMNMAX && sound == 82 && getSoundStatus(81, false)) + ImClearTrigger(81, 1); + + player->clear(); + return player->startSound(sound, driver, _direct_passthrough); +} + + +Player *IMuseInternal::allocate_player(byte priority) { + Player *player = _players, *best = NULL; + int i; + byte bestpri = 255; + + for (i = _player_limit; i != 0; i--, player++) { + if (!player->isActive()) + return player; + if (player->getPriority() < bestpri) { + best = player; + bestpri = player->getPriority(); + } + } + + if (bestpri < priority || _recycle_players) + return best; + + debug(1, "Denying player request"); + return NULL; +} + +void IMuseInternal::init_players() { + Player *player = _players; + int i; + + for (i = ARRAYSIZE(_players); i != 0; i--, player++) { + player->_se = this; + player->clear(); // Used to just set _active to false + } +} + +void IMuseInternal::init_parts() { + Part *part; + int i; + + for (i = 0, part = _parts; i != ARRAYSIZE(_parts); i++, part++) { + part->init(); + part->_se = this; + part->_slot = i; + } +} + +int IMuseInternal::stopSound(int sound) { + int r = -1; + Player *player = findActivePlayer(sound); + if (player) { + player->clear(); + r = 0; + } + return r; +} + +int IMuseInternal::stopAllSounds() { + Player *player = _players; + int i; + + for (i = ARRAYSIZE(_players); i != 0; i--, player++) { + if (player->isActive()) + player->clear(); + } + return 0; +} + +void IMuseInternal::on_timer(MidiDriver *midi) { + if (_paused || !_initialized) + return; + + if (midi == _midi_native || !_midi_native) + handleDeferredCommands(midi); + sequencer_timers(midi); +} + +int IMuseInternal::getMusicTimer() const { + int best_time = 0; + const Player *player = _players; + int i; + + for (i = ARRAYSIZE(_players); i != 0; i--, player++) { + if (player->isActive()) { + int timer = player->getMusicTimer(); + if (timer > best_time) + best_time = timer; + } + } + return best_time; +} + +void IMuseInternal::sequencer_timers(MidiDriver *midi) { + Player *player = _players; + int i; + for (i = ARRAYSIZE(_players); i != 0; i--, player++) { + if (player->isActive() && player->getMidiDriver() == midi) { + player->onTimer(); + } + } +} + +void IMuseInternal::handle_marker(uint id, byte data) { + uint16 *p = 0; + uint pos; + + if (_queue_adding && _queue_sound == id && data == _queue_marker) + return; + + // Fix for bug #733401, revised for bug #761637: + // It would seem that sometimes a marker is in the queue + // but not at the head position. In the case of our bug, + // this seems to be the result of commands in the queue + // for songs that are no longer playing. So we skip + // ahead to the appropriate marker, effectively chomping + // anything in the queue before it. This fixes the FOA + // end credits music, but needs to be tested for inappopriate + // behavior elsewhere. + pos = _queue_end; + while (pos != _queue_pos) { + p = _cmd_queue[pos].array; + if (p[0] == TRIGGER_ID && p[1] == id && p[2] == data) + break; + pos = (pos + 1) % ARRAYSIZE(_cmd_queue); + } + + if (pos == _queue_pos) + return; + + if (pos != _queue_end) + debug(0, "Skipping entries in iMuse command queue to reach marker"); + + _trigger_count--; + _queue_cleared = false; + do { + pos = (pos + 1) % ARRAYSIZE(_cmd_queue); + if (_queue_pos == pos) + break; + p = _cmd_queue[pos].array; + if (*p++ != COMMAND_ID) + break; + _queue_end = pos; + + doCommand(p[0], p[1], p[2], p[3], p[4], p[5], p[6], 0); + + if (_queue_cleared) + return; + pos = _queue_end; + } while (1); + + _queue_end = pos; +} + +int IMuseInternal::get_channel_volume(uint a) { + if (a < 8) + return _channel_volume_eff[a]; + return (_master_volume * _music_volume / 255) / 2; +} + +Part *IMuseInternal::allocate_part(byte pri, MidiDriver *midi) { + Part *part, *best = NULL; + int i; + + for (i = ARRAYSIZE(_parts), part = _parts; i != 0; i--, part++) { + if (!part->_player) + return part; + if (pri >= part->_pri_eff) { + pri = part->_pri_eff; + best = part; + } + } + + if (best) { + best->uninit(); + reallocateMidiChannels(midi); + } else { + debug(1, "Denying part request"); + } + return best; +} + +int IMuseInternal::getSoundStatus(int sound, bool ignoreFadeouts) const { + int i; + const Player *player = _players; + + for (i = ARRAYSIZE(_players); i != 0; i--, player++) { + if (player->isActive() && (!ignoreFadeouts || !player->isFadingOut())) { + if (sound == -1) + return player->getID(); + else if (player->getID() == (uint16)sound) + return 1; + } + } + return (sound == -1) ? 0 : get_queue_sound_status(sound); +} + +int IMuseInternal::get_queue_sound_status(int sound) const { + const uint16 *a; + int i, j; + + j = _queue_pos; + i = _queue_end; + + while (i != j) { + a = _cmd_queue[i].array; + if (a[0] == COMMAND_ID && a[1] == 8 && a[2] == (uint16)sound) + return 2; + i = (i + 1) % ARRAYSIZE(_cmd_queue); + } + + for (i = 0; i < ARRAYSIZE (_deferredCommands); ++i) { + if (_deferredCommands[i].time_left && _deferredCommands[i].a == 8 && + _deferredCommands[i].b == sound) { + return 2; + } + } + + return 0; +} + +int IMuseInternal::set_volchan(int sound, int volchan) { + int r; + int i; + int num; + Player *player, *best, *sameid; + + r = get_volchan_entry(volchan); + if (r == -1) + return -1; + + if (r >= 8) { + player = findActivePlayer(sound); + if (player && player->_vol_chan != (uint16)volchan) { + player->_vol_chan = volchan; + player->setVolume(player->getVolume()); + return 0; + } + return -1; + } else { + best = NULL; + num = 0; + sameid = NULL; + for (i = ARRAYSIZE(_players), player = _players; i != 0; i--, player++) { + if (player->isActive()) { + if (player->_vol_chan == (uint16)volchan) { + num++; + if (!best || player->getPriority() <= best->getPriority()) + best = player; + } else if (player->getID() == (uint16)sound) { + sameid = player; + } + } + } + if (sameid == NULL) + return -1; + if (num >= r) + best->clear(); + player->_vol_chan = volchan; + player->setVolume(player->getVolume()); + return 0; + } +} + +int IMuseInternal::clear_queue() { + _queue_adding = false; + _queue_cleared = true; + _queue_pos = 0; + _queue_end = 0; + _trigger_count = 0; + return 0; +} + +int IMuseInternal::enqueue_command(int a, int b, int c, int d, int e, int f, int g) { + uint16 *p; + uint i; + + i = _queue_pos; + + if (i == _queue_end) + return -1; + + if (a == -1) { + _queue_adding = false; + _trigger_count++; + return 0; + } + + p = _cmd_queue[_queue_pos].array; + p[0] = COMMAND_ID; + p[1] = a; + p[2] = b; + p[3] = c; + p[4] = d; + p[5] = e; + p[6] = f; + p[7] = g; + + i = (i + 1) % ARRAYSIZE(_cmd_queue); + + if (_queue_end != i) { + _queue_pos = i; + return 0; + } else { + _queue_pos = (i - 1) % ARRAYSIZE(_cmd_queue); + return -1; + } +} + +int IMuseInternal::query_queue(int param) { + switch (param) { + case 0: // Get trigger count + return _trigger_count; + case 1: // Get trigger type + if (_queue_end == _queue_pos) + return -1; + return _cmd_queue[_queue_end].array[1]; + case 2: // Get trigger sound + if (_queue_end == _queue_pos) + return 0xFF; + return _cmd_queue[_queue_end].array[2]; + default: + return -1; + } +} + +int IMuseInternal::setMusicVolume(uint vol) { + if (vol > 255) + vol = 255; + if (_music_volume == vol) + return 0; + _music_volume = vol; + vol = _master_volume * _music_volume / 255; + for (uint i = 0; i < ARRAYSIZE(_channel_volume); i++) { + _channel_volume_eff[i] = _channel_volume[i] * vol / 255; + } + if (!_paused) + update_volumes(); + return 0; +} + +int IMuseInternal::setImuseMasterVolume(uint vol) { + if (vol > 255) + vol = 255; + if (_master_volume == vol) + return 0; + _master_volume = vol; + vol = _master_volume * _music_volume / 255; + for (uint i = 0; i < ARRAYSIZE(_channel_volume); i++) { + _channel_volume_eff[i] = _channel_volume[i] * vol / 255; + } + if (!_paused) + update_volumes(); + return 0; +} + +int IMuseInternal::terminate1() { + _initialized = false; + stopAllSounds(); + return 0; +} + +// This is the stuff that has to be done +// outside the monitor's mutex, otherwise +// a deadlock occurs. +int IMuseInternal::terminate2() { + if (_midi_adlib) { + _midi_adlib->close(); + delete _midi_adlib; + _midi_adlib = 0; + } + + if (_midi_native) { + _midi_native->close(); + delete _midi_native; + _midi_native = 0; + } + + return 0; +} + +int IMuseInternal::enqueue_trigger(int sound, int marker) { + uint16 *p; + uint pos; + + pos = _queue_pos; + + p = _cmd_queue[pos].array; + p[0] = TRIGGER_ID; + p[1] = sound; + p[2] = marker; + + pos = (pos + 1) % ARRAYSIZE(_cmd_queue); + if (_queue_end == pos) { + _queue_pos = (pos - 1) % ARRAYSIZE(_cmd_queue); + return -1; + } + + _queue_pos = pos; + _queue_adding = true; + _queue_sound = sound; + _queue_marker = marker; + return 0; +} + +int32 IMuseInternal::doCommand(int a, int b, int c, int d, int e, int f, int g, int h) { + int args[8]; + args[0] = a; + args[1] = b; + args[2] = c; + args[3] = d; + args[4] = e; + args[5] = f; + args[6] = g; + args[7] = h; + return doCommand(8, args); +} + +int32 IMuseInternal::doCommand(int numargs, int a[]) { + int i; + + if (numargs < 1) + return -1; + byte cmd = a[0] & 0xFF; + byte param = a[0] >> 8; + Player *player = NULL; + + if (!_initialized && (cmd || param)) + return -1; + +#ifdef IMUSE_DEBUG + { + char string[128]; + sprintf(string, "doCommand - %d (%d/%d)", a[0], (int)param, (int)cmd); + for (i = 1; i < numargs; ++i) + sprintf(string + strlen(string), ", %d", a[i]); + debug(0, string); + } +#endif + + if (param == 0) { + switch (cmd) { + case 6: + if (a[1] > 127) + return -1; + else { + debug(0, "IMuse doCommand(6) - setImuseMasterVolume (%d)", a[1]); + return setImuseMasterVolume((a[1] << 1) | (a[1] ? 0 : 1)); // Convert from 0-127 to 0-255 + } + case 7: + debug(0, "IMuse doCommand(7) - getMasterVolume (%d)", a[1]); + return _master_volume / 2; // Convert from 0-255 to 0-127 + case 8: + return startSound(a[1]) ? 0 : -1; + case 9: + return stopSound(a[1]); + case 10: // FIXME: Sam and Max - Not sure if this is correct + return stopAllSounds(); + case 11: + return stopAllSounds(); + case 12: + // Sam & Max: Player-scope commands + player = findActivePlayer(a[1]); + if (!player) + return -1; + + switch (a[3]) { + case 6: + // Set player volume. + return player->setVolume(a[4]); + default: + error("IMuseInternal::doCommand(12) unsupported sub-command %d", a[3]); + } + return -1; + case 13: + return getSoundStatus(a[1]); + case 14: + // Sam and Max: Parameter fade + player = findActivePlayer(a[1]); + if (player) + return player->addParameterFader(a[3], a[4], a[5]); + return -1; + + case 15: + // Sam & Max: Set hook for a "maybe" jump + player = findActivePlayer(a[1]); + if (player) { + player->setHook(0, a[3], 0); + return 0; + } + return -1; + case 16: + debug(0, "IMuse doCommand(16) - set_volchan (%d, %d)", a[1], a[2]); + return set_volchan(a[1], a[2]); + case 17: + if (g_scumm->_game.id != GID_SAMNMAX) { + debug(0, "IMuse doCommand(17) - set_channel_volume (%d, %d)", a[1], a[2]); + return set_channel_volume(a[1], a[2]); + } else { + if (a[4]) { + int b[16]; + memset(b, 0, sizeof(b)); + for (i = 0; i < numargs; ++i) + b[i] = a[i]; + return ImSetTrigger(b[1], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11]); + } else { + return ImClearTrigger(a[1], a[3]); + } + } + case 18: + if (g_scumm->_game.id != GID_SAMNMAX) { + return set_volchan_entry(a[1], a[2]); + } else { + // Sam & Max: ImCheckTrigger. + // According to Mike's notes to Ender, + // this function returns the number of triggers + // associated with a particular player ID and + // trigger ID. + a[0] = 0; + for (i = 0; i < ARRAYSIZE(_snm_triggers); ++i) { + if (_snm_triggers[i].sound == a[1] && _snm_triggers[i].id && + (a[3] == -1 || _snm_triggers[i].id == a[3])) + { + ++a[0]; + } + } + return a[0]; + } + case 19: + // Sam & Max: ImClearTrigger + // This should clear a trigger that's been set up + // with ImSetTrigger(cmd == 17). Seems to work.... + return ImClearTrigger(a[1], a[3]); + case 20: + // Sam & Max: Deferred Command + addDeferredCommand(a[1], a[2], a[3], a[4], a[5], a[6], a[7]); + return 0; + case 2: + case 3: + return 0; + default: + error("doCommand(%d [%d/%d], %d, %d, %d, %d, %d, %d, %d) unsupported", a[0], param, cmd, a[1], a[2], a[3], a[4], a[5], a[6], a[7]); + } + } else if (param == 1) { + if ((1 << cmd) & 0x783FFF) { + player = findActivePlayer(a[1]); + if (!player) + return -1; + if ((1 << cmd) & (1 << 11 | 1 << 22)) { + assert(a[2] >= 0 && a[2] <= 15); + player = (Player *)player->getPart(a[2]); + if (!player) + return -1; + } + } + + switch (cmd) { + case 0: + if (g_scumm->_game.id == GID_SAMNMAX) { + if (a[3] == 1) // Measure number + return ((player->getBeatIndex() - 1) >> 2) + 1; + else if (a[3] == 2) // Beat number + return player->getBeatIndex(); + return -1; + } else { + return player->getParam(a[2], a[3]); + } + case 1: + if (g_scumm->_game.id == GID_SAMNMAX) { + // FIXME: Could someone verify this? + // + // This jump instruction is known to be used in + // the following cases: + // + // 1) Going anywhere on the USA map + // 2) Winning the Wak-A-Rat game + // 3) Losing or quitting the Wak-A-Rat game + // 4) Conroy hitting Max with a golf club + // + // For all these cases the position parameters + // are always the same: 2, 1, 0, 0. + // + // 5) When leaving the bigfoot party. The + // position parameters are: 3, 4, 300, 0 + // 6) At Frog Rock, when the UFO appears. The + // position parameters are: 10, 4, 400, 1 + // + // The last two cases used to be buggy, so I + // have made a change to how the last two + // position parameters are handled. I still do + // not know if it's correct, but it sounds + // good to me at least. + + debug(0, "doCommand(%d [%d/%d], %d, %d, %d, %d, %d, %d, %d)", a[0], param, cmd, a[1], a[2], a[3], a[4], a[5], a[6], a[7]); + player->jump(a[3] - 1, (a[4] - 1) * 4 + a[5], a[6] + ((a[7] * player->getTicksPerBeat()) >> 2)); + } else + player->setPriority(a[2]); + return 0; + case 2: + return player->setVolume(a[2]); + case 3: + player->setPan(a[2]); + return 0; + case 4: + return player->setTranspose(a[2], a[3]); + case 5: + player->setDetune(a[2]); + return 0; + case 6: + player->setSpeed(a[2]); + return 0; + case 7: + return player->jump(a[2], a[3], a[4]) ? 0 : -1; + case 8: + return player->scan(a[2], a[3], a[4]); + case 9: + return player->setLoop(a[2], a[3], a[4], a[5], a[6]) ? 0 : -1; + case 10: + player->clearLoop(); + return 0; + case 11: + ((Part *)player)->set_onoff(a[3] != 0); + return 0; + case 12: + return player->setHook(a[2], a[3], a[4]); + case 13: + return player->addParameterFader(ParameterFader::pfVolume, a[2], a[3]); + case 14: + return enqueue_trigger(a[1], a[2]); + case 15: + return enqueue_command(a[1], a[2], a[3], a[4], a[5], a[6], a[7]); + case 16: + return clear_queue(); + case 19: + return player->getParam(a[2], a[3]); + case 20: + return player->setHook(a[2], a[3], a[4]); + case 21: + return -1; + case 22: + ((Part *)player)->volume(a[3]); + return 0; + case 23: + return query_queue(a[1]); + case 24: + return 0; + default: + error("doCommand(%d [%d/%d], %d, %d, %d, %d, %d, %d, %d) unsupported", a[0], param, cmd, a[1], a[2], a[3], a[4], a[5], a[6], a[7]); + return -1; + } + } + + return -1; +} + +int32 IMuseInternal::ImSetTrigger(int sound, int id, int a, int b, int c, int d, int e, int f, int g, int h) { + // Sam & Max: ImSetTrigger. + // Sets a trigger for a particular player and + // marker ID, along with doCommand parameters + // to invoke at the marker. The marker is + // represented by MIDI SysEx block 00 xx(F7) + // where "xx" is the marker ID. + uint16 oldest_trigger = 0; + ImTrigger *oldest_ptr = NULL; + + int i; + ImTrigger *trig = _snm_triggers; + for (i = ARRAYSIZE(_snm_triggers); i; --i, ++trig) { + if (!trig->id) + break; + // We used to only compare 'id' and 'sound' here, but at least + // at the Dino Bungie Memorial that causes the music to stop + // after getting the T-Rex tooth. See bug #888161. + if (trig->id == id && trig->sound == sound && trig->command[0] == a) + break; + + uint16 diff; + if (trig->expire <= _snm_trigger_index) + diff = _snm_trigger_index - trig->expire; + else + diff = 0x10000 - trig->expire + _snm_trigger_index; + + if (!oldest_ptr || oldest_trigger < diff) { + oldest_ptr = trig; + oldest_trigger = diff; + } + } + + // If we didn't find a trigger, see if we can expire one. + if (!i) { + if (!oldest_ptr) + return -1; + trig = oldest_ptr; + } + + trig->id = id; + trig->sound = sound; + trig->expire = (++_snm_trigger_index & 0xFFFF); + trig->command[0] = a; + trig->command[1] = b; + trig->command[2] = c; + trig->command[3] = d; + trig->command[4] = e; + trig->command[5] = f; + trig->command[6] = g; + trig->command[7] = h; + + // If the command is to start a sound, stop that sound if it's already playing. + // This fixes some carnival music problems. + // NOTE: We ONLY do this if the sound that will trigger the command is actually + // playing. Otherwise, there's a problem when exiting and re-entering the + // Bumpusville mansion. Ref Bug #780918. + if (trig->command[0] == 8 && getSoundStatus(trig->command[1]) && getSoundStatus(sound)) + stopSound(trig->command[1]); + return 0; +} + +int32 IMuseInternal::ImClearTrigger(int sound, int id) { + int count = 0; + int i; + ImTrigger *trig = _snm_triggers; + for (i = ARRAYSIZE(_snm_triggers); i; --i, ++trig) { + if ((sound == -1 || trig->sound == sound) && trig->id && (id == -1 || trig->id == id)) { + trig->sound = trig->id = 0; + ++count; + } + } + return (count > 0) ? 0 : -1; +} + +int32 IMuseInternal::ImFireAllTriggers(int sound) { + if (!sound) + return 0; + int count = 0; + int i; + for (i = 0; i < ARRAYSIZE(_snm_triggers); ++i) { + if (_snm_triggers[i].sound == sound) { + _snm_triggers[i].sound = _snm_triggers[i].id = 0; + doCommand(8, _snm_triggers[i].command); + ++count; + } + } + return (count > 0) ? 0 : -1; +} + +int IMuseInternal::set_channel_volume(uint chan, uint vol) +{ + if (chan >= 8 || vol > 127) + return -1; + + _channel_volume[chan] = vol; + _channel_volume_eff[chan] = _master_volume * _music_volume * vol / 255 / 255; + update_volumes(); + return 0; +} + +void IMuseInternal::update_volumes() { + Player *player; + int i; + + for (i = ARRAYSIZE(_players), player = _players; i != 0; i--, player++) { + if (player->isActive()) + player->setVolume(player->getVolume()); + } +} + +int IMuseInternal::set_volchan_entry(uint a, uint b) { + if (a >= 8) + return -1; + _volchan_table[a] = b; + return 0; +} + +int HookDatas::query_param(int param, byte chan) { + switch (param) { + case 18: + return _jump[0]; + case 19: + return _transpose; + case 20: + return _part_onoff[chan]; + case 21: + return _part_volume[chan]; + case 22: + return _part_program[chan]; + case 23: + return _part_transpose[chan]; + default: + return -1; + } +} + +int HookDatas::set(byte cls, byte value, byte chan) { + switch (cls) { + case 0: + if (value != _jump[0]) { + _jump[1] = _jump[0]; + _jump[0] = value; + } + break; + case 1: + _transpose = value; + break; + case 2: + if (chan < 16) + _part_onoff[chan] = value; + else if (chan == 16) + memset(_part_onoff, value, 16); + break; + case 3: + if (chan < 16) + _part_volume[chan] = value; + else if (chan == 16) + memset(_part_volume, value, 16); + break; + case 4: + if (chan < 16) + _part_program[chan] = value; + else if (chan == 16) + memset(_part_program, value, 16); + break; + case 5: + if (chan < 16) + _part_transpose[chan] = value; + else if (chan == 16) + memset(_part_transpose, value, 16); + break; + default: + return -1; + } + return 0; +} + +Player *IMuseInternal::findActivePlayer(int id) { + int i; + Player *player = _players; + + for (i = ARRAYSIZE(_players); i != 0; i--, player++) { + if (player->isActive() && player->getID() == (uint16)id) + return player; + } + return NULL; +} + +int IMuseInternal::get_volchan_entry(uint a) { + if (a < 8) + return _volchan_table[a]; + return -1; +} + +uint32 IMuseInternal::property(int prop, uint32 value) { + switch (prop) { + case IMuse::PROP_TEMPO_BASE: + // This is a specified as a percentage of normal + // music speed. The number must be an integer + // ranging from 50 to 200(for 50% to 200% normal speed). + if (value >= 50 && value <= 200) + _tempoFactor = value; + break; + + case IMuse::PROP_NATIVE_MT32: + _native_mt32 = (value > 0); + Instrument::nativeMT32(_native_mt32); + if (_midi_native && _native_mt32) + initMT32(_midi_native); + break; + + case IMuse::PROP_GS: + _enable_gs = (value > 0); + + // If True Roland MT-32 is not selected, run in GM or GS mode. + // If it is selected, change the Roland GS synth to MT-32 mode. + if (_midi_native && !_native_mt32) + initGM(_midi_native); + else if (_midi_native && _native_mt32 && _enable_gs) { + _sc55 = true; + initGM(_midi_native); + } + break; + + case IMuse::PROP_LIMIT_PLAYERS: + if (value > 0 && value <= ARRAYSIZE(_players)) + _player_limit = (int)value; + break; + + case IMuse::PROP_RECYCLE_PLAYERS: + _recycle_players = (value != 0); + break; + + case IMuse::PROP_DIRECT_PASSTHROUGH: + _direct_passthrough = (value != 0); + break; + } + + return 0; +} + +void IMuseInternal::setBase(byte **base) { + _base_sounds = base; +} + +IMuseInternal *IMuseInternal::create(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver) { + IMuseInternal *i = new IMuseInternal; + i->initialize(syst, nativeMidiDriver, adlibMidiDriver); + return i; +} + +int IMuseInternal::initialize(OSystem *syst, MidiDriver *native_midi, MidiDriver *adlib_midi) { + int i; + + _midi_native = native_midi; + _midi_adlib = adlib_midi; + if (native_midi != NULL) + initMidiDriver(native_midi); + if (adlib_midi != NULL) + initMidiDriver(adlib_midi); + + if (!_tempoFactor) + _tempoFactor = 100; + _master_volume = 255; + + for (i = 0; i != 8; i++) + _channel_volume[i] = _channel_volume_eff[i] = _volchan_table[i] = 127; + + init_players(); + init_queue(); + init_parts(); + + _initialized = true; + + return 0; +} + +void IMuseInternal::initMidiDriver(MidiDriver *midi) { + // Open MIDI driver + int result = midi->open(); + if (result) + error("IMuse initialization - %s", MidiDriver::getErrorName(result)); + + // Connect to the driver's timer + midi->setTimerCallback(midi, &IMuseInternal::midiTimerCallback); +} + +void IMuseInternal::initMT32(MidiDriver *midi) { + byte buffer[52]; + char info[256] = "ScummVM "; + int len; + + // Reset the MT-32 + memcpy(&buffer[0], "\x41\x10\x16\x12\x7f\x00\x00\x01\x00", 9); + midi->sysEx(buffer, 9); + g_system->delayMillis(100); + + // Compute version string (truncated to 20 chars max.) + strcat(info, gScummVMVersion); + len = strlen(info); + if (len > 20) + len = 20; + + // Display a welcome message on MT-32 displays. + memcpy(&buffer[4], "\x20\x00\x00", 3); + memcpy(&buffer[7], " ", 20); + memcpy(buffer + 7 +(20 - len) / 2, info, len); + byte checksum = 0; + for (int i = 4; i < 27; ++i) + checksum -= buffer[i]; + buffer[27] = checksum & 0x7F; + midi->sysEx(buffer, 28); + g_system->delayMillis(500); + + // Setup master tune, reverb mode, reverb time, reverb level, + // channel mapping, partial reserve and master volume + memcpy(&buffer[4], "\x10\x00\x00\x40\x00\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x64\x77", 27); + midi->sysEx(buffer, 31); + g_system->delayMillis(250); + + // Map percussion to notes 24 - 34 without reverb + memcpy(&buffer[4], "\x03\x01\x10\x40\x64\x07\x00\x4a\x64\x06\x00\x41\x64\x07\x00\x4b\x64\x08\x00\x45\x64\x06\x00\x44\x64\x0b\x00\x51\x64\x05\x00\x43\x64\x08\x00\x50\x64\x07\x00\x42\x64\x03\x00\x4c\x64\x07\x00\x44", 48); + midi->sysEx(buffer, 52); + g_system->delayMillis(250); +} + +void IMuseInternal::initGM(MidiDriver *midi) { + byte buffer[11]; + int i; + + // General MIDI System On message + // Resets all GM devices to default settings + memcpy(&buffer[0], "\xF0\x7E\x7F\x09\x01\xF7", 6); + midi->sysEx(buffer, 6); + debug(2, "GM SysEx: GM System On"); + g_system->delayMillis(200); + + if (_enable_gs) { + + // All GS devices recognize the GS Reset command, + // even with Roland's ID. It is impractical to + // support other manufacturers' devices for + // further GS settings, as there are limitless + // numbers of them out there that would each + // require individual SysEx commands with unique IDs. + + // Roland GS SysEx ID + memcpy(&buffer[0], "\xF0\x41\x10\x42\x12", 5); + + // GS Reset + memcpy(&buffer[5], "\x40\x00\x7F\x00\x41\xF7", 6); + midi->sysEx(buffer, 11); + debug(2, "GS SysEx: GS Reset"); + g_system->delayMillis(200); + + if (_sc55) { + // This mode is for GS devices that support an MT-32-compatible + // Map, such as the Roland Sound Canvas line of modules. It + // will allow them to work with True MT-32 mode, but will + // obviously still ignore MT-32 SysEx (and thus custom + // instruments). + + // Set Channels 1-16 to SC-55 Map, then CM-64/32L Variation + for (i = 0; i < 16; ++i) { + midi->send(( 127 << 16) | (0 << 8) | (0xB0 | i)); + midi->send(( 1 << 16) | (32 << 8) | (0xB0 | i)); + midi->send(( 0 << 16) | (0 << 8) | (0xC0 | i)); + } + debug(2, "GS Program Change: CM-64/32L Map Selected"); + + // Set Percussion Channel to SC-55 Map (CC#32, 01H), then + // Switch Drum Map to CM-64/32L (MT-32 Compatible Drums) + midi->getPercussionChannel()->controlChange(0, 0); + midi->getPercussionChannel()->controlChange(32, 1); + midi->send(127 << 8 | 0xC0 | 9); + debug(2, "GS Program Change: Drum Map is CM-64/32L"); + + } + + // Set Master Chorus to 0. The MT-32 has no chorus capability. + memcpy(&buffer[5], "\x40\x01\x3A\x00\x05\xF7", 6); + midi->sysEx(buffer, 11); + debug(2, "GS SysEx: Master Chorus Level is 0"); + + // Set Channels 1-16 Reverb to 64, which is the + // equivalent of MT-32 default Reverb Level 5 + for (i = 0; i < 16; ++i) + midi->send(( 64 << 16) | (91 << 8) | (0xB0 | i)); + debug(2, "GM Controller 91 Change: Channels 1-16 Reverb Level is 64"); + + // Set Channels 1-16 Pitch Bend Sensitivity to + // 12 semitones; then lock the RPN by setting null. + for (i = 0; i < 16; ++i) { + midi->send(( 0 << 16) | (100 << 8) | (0xB0 | i)); + midi->send(( 0 << 16) | (101 << 8) | (0xB0 | i)); + midi->send(( 12 << 16) | (6 << 8) | (0xB0 | i)); + midi->send(( 0 << 16) | (38 << 8) | (0xB0 | i)); + midi->send(( 127 << 16) | (100 << 8) | (0xB0 | i)); + midi->send(( 127 << 16) | (101 << 8) | (0xB0 | i)); + } + debug(2, "GM Controller 6 Change: Channels 1-16 Pitch Bend Sensitivity is 12 semitones"); + + // Set channels 1-16 Mod. LFO1 Pitch Depth to 4 + memcpy(&buffer[5], "\x40\x20\x04\x04\x18\xF7", 6); + midi->sysEx(buffer, 11); + memcpy(&buffer[5], "\x40\x21\x04\x04\x17\xF7", 6); + midi->sysEx(buffer, 11); + memcpy(&buffer[5], "\x40\x22\x04\x04\x16\xF7", 6); + midi->sysEx(buffer, 11); + memcpy(&buffer[5], "\x40\x23\x04\x04\x15\xF7", 6); + midi->sysEx(buffer, 11); + memcpy(&buffer[5], "\x40\x24\x04\x04\x14\xF7", 6); + midi->sysEx(buffer, 11); + memcpy(&buffer[5], "\x40\x25\x04\x04\x13\xF7", 6); + midi->sysEx(buffer, 11); + memcpy(&buffer[5], "\x40\x26\x04\x04\x12\xF7", 6); + midi->sysEx(buffer, 11); + memcpy(&buffer[5], "\x40\x27\x04\x04\x11\xF7", 6); + midi->sysEx(buffer, 11); + memcpy(&buffer[5], "\x40\x28\x04\x04\x10\xF7", 6); + midi->sysEx(buffer, 11); + memcpy(&buffer[5], "\x40\x29\x04\x04\x0F\xF7", 6); + midi->sysEx(buffer, 11); + memcpy(&buffer[5], "\x40\x2A\x04\x04\x0E\xF7", 6); + midi->sysEx(buffer, 11); + memcpy(&buffer[5], "\x40\x2B\x04\x04\x0D\xF7", 6); + midi->sysEx(buffer, 11); + memcpy(&buffer[5], "\x40\x2C\x04\x04\x0C\xF7", 6); + midi->sysEx(buffer, 11); + memcpy(&buffer[5], "\x40\x2D\x04\x04\x0B\xF7", 6); + midi->sysEx(buffer, 11); + memcpy(&buffer[5], "\x40\x2E\x04\x04\x0A\xF7", 6); + midi->sysEx(buffer, 11); + memcpy(&buffer[5], "\x40\x2F\x04\x04\x09\xF7", 6); + midi->sysEx(buffer, 11); + debug(2, "GS SysEx: Channels 1-16 Mod. LFO1 Pitch Depth Level is 4"); + + // Set Percussion Channel Expression to 80 + midi->getPercussionChannel()->controlChange(11, 80); + debug(2, "GM Controller 11 Change: Percussion Channel Expression Level is 80"); + + // Turn off Percussion Channel Rx. Expression so that + // Expression cannot be modified. I don't know why, but + // Roland does it this way. + memcpy(&buffer[5], "\x40\x10\x0E\x00\x22\xF7", 6); + midi->sysEx(buffer, 11); + debug(2, "GS SysEx: Percussion Channel Rx. Expression is OFF"); + + // Change Reverb Character to 0. I don't think this + // sounds most like MT-32, but apparently Roland does. + memcpy(&buffer[5], "\x40\x01\x31\x00\x0E\xF7", 6); + midi->sysEx(buffer, 11); + debug(2, "GS SysEx: Reverb Character is 0"); + + // Change Reverb Pre-LF to 4, which is similar to + // what MT-32 reverb does. + memcpy(&buffer[5], "\x40\x01\x32\x04\x09\xF7", 6); + midi->sysEx(buffer, 11); + debug(2, "GS SysEx: Reverb Pre-LF is 4"); + + // Change Reverb Time to 106; the decay on Hall 2 + // Reverb is too fast compared to the MT-32's + memcpy(&buffer[5], "\x40\x01\x34\x6A\x21\xF7", 6); + midi->sysEx(buffer, 11); + debug(2, "GS SysEx: Reverb Time is 106"); + } +} + +void IMuseInternal::init_queue() { + _queue_adding = false; + _queue_pos = 0; + _queue_end = 0; + _trigger_count = 0; +} + +void IMuseInternal::pause(bool paused) { + if (_paused == paused) + return; + int vol = _music_volume; + if (paused) + _music_volume = 0; + update_volumes(); + _music_volume = vol; + + // Fix for Bug #817871. The MT-32 apparently fails + // sometimes to respond to a channel volume message + // (or only uses it for subsequent note events). + // The result is hanging notes on pause. Reportedly + // happens in the original distro, too. To fix that, + // just send AllNotesOff to the channels. + if (_midi_native && _native_mt32) { + for (int i = 0; i < 16; ++i) + _midi_native->send(123 << 8 | 0xB0 | i); + } + + _paused = paused; +} + +void IMuseInternal::handleDeferredCommands(MidiDriver *midi) { + uint32 advance = midi->getBaseTempo(); + + DeferredCommand *ptr = &_deferredCommands[0]; + int i; + for (i = ARRAYSIZE(_deferredCommands); i; --i, ++ptr) { + if (!ptr->time_left) + continue; + if (ptr->time_left <= advance) { + doCommand(ptr->a, ptr->b, ptr->c, ptr->d, ptr->e, ptr->f, 0, 0); + ptr->time_left = advance; + } + ptr->time_left -= advance; + } +} + +// "time" is interpreted as hundredths of a second. +// FIXME: Is that correct? +// We convert it to microseconds before prceeding +void IMuseInternal::addDeferredCommand(int time, int a, int b, int c, int d, int e, int f) { + DeferredCommand *ptr = &_deferredCommands[0]; + int i; + for (i = ARRAYSIZE(_deferredCommands); i; --i, ++ptr) { + if (!ptr->time_left) + break; + } + + if (i) { + ptr->time_left = time * 10000; + ptr->a = a; + ptr->b = b; + ptr->c = c; + ptr->d = d; + ptr->e = e; + ptr->f = f; + } +} + +//////////////////////////////////////////////////////////// +// +// IMuseInternal load/save implementation +// +//////////////////////////////////////////////////////////// + +int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm) { + const SaveLoadEntry mainEntries[] = { + MKLINE(IMuseInternal, _queue_end, sleUint8, VER(8)), + MKLINE(IMuseInternal, _queue_pos, sleUint8, VER(8)), + MKLINE(IMuseInternal, _queue_sound, sleUint16, VER(8)), + MKLINE(IMuseInternal, _queue_adding, sleByte, VER(8)), + MKLINE(IMuseInternal, _queue_marker, sleByte, VER(8)), + MKLINE(IMuseInternal, _queue_cleared, sleByte, VER(8)), + MKLINE(IMuseInternal, _master_volume, sleByte, VER(8)), + MKLINE(IMuseInternal, _trigger_count, sleUint16, VER(8)), + MKLINE(IMuseInternal, _snm_trigger_index, sleUint16, VER(54)), + MKARRAY(IMuseInternal, _channel_volume[0], sleUint16, 8, VER(8)), + MKARRAY(IMuseInternal, _volchan_table[0], sleUint16, 8, VER(8)), + MKEND() + }; + + const SaveLoadEntry cmdQueueEntries[] = { + MKARRAY(CommandQueue, array[0], sleUint16, 8, VER(23)), + MKEND() + }; + + // VolumeFader is obsolete. + const SaveLoadEntry volumeFaderEntries[] = { + MK_OBSOLETE(VolumeFader, player, sleUint16, VER(8), VER(16)), + MK_OBSOLETE(VolumeFader, active, sleUint8, VER(8), VER(16)), + MK_OBSOLETE(VolumeFader, curvol, sleUint8, VER(8), VER(16)), + MK_OBSOLETE(VolumeFader, speed_lo_max, sleUint16, VER(8), VER(16)), + MK_OBSOLETE(VolumeFader, num_steps, sleUint16, VER(8), VER(16)), + MK_OBSOLETE(VolumeFader, speed_hi, sleInt8, VER(8), VER(16)), + MK_OBSOLETE(VolumeFader, direction, sleInt8, VER(8), VER(16)), + MK_OBSOLETE(VolumeFader, speed_lo, sleInt8, VER(8), VER(16)), + MK_OBSOLETE(VolumeFader, speed_lo_counter, sleUint16, VER(8), VER(16)), + MKEND() + }; + + const SaveLoadEntry snmTriggerEntries[] = { + MKLINE(ImTrigger, sound, sleInt16, VER(54)), + MKLINE(ImTrigger, id, sleByte, VER(54)), + MKLINE(ImTrigger, expire, sleUint16, VER(54)), + MKARRAY(ImTrigger, command[0], sleUint16, 8, VER(54)), + MKEND() + }; + + int i; + + ser->saveLoadEntries(this, mainEntries); + ser->saveLoadArrayOf(_cmd_queue, ARRAYSIZE(_cmd_queue), sizeof(_cmd_queue[0]), cmdQueueEntries); + ser->saveLoadArrayOf(_snm_triggers, ARRAYSIZE(_snm_triggers), sizeof(_snm_triggers[0]), snmTriggerEntries); + + // The players + for (i = 0; i < ARRAYSIZE(_players); ++i) + _players[i].saveLoadWithSerializer(ser); + + // The parts + for (i = 0; i < ARRAYSIZE(_parts); ++i) + _parts[i].saveLoadWithSerializer(ser); + + { // Load/save the instrument definitions, which were revamped with V11. + Part *part = &_parts[0]; + if (ser->getVersion() >= VER(11)) { + for (i = ARRAYSIZE(_parts); i; --i, ++part) { + part->_instrument.saveOrLoad(ser); + } + } else { + for (i = ARRAYSIZE(_parts); i; --i, ++part) + part->_instrument.clear(); + } + } + + // VolumeFader has been replaced with the more generic ParameterFader. + // FIXME: replace this loop by something like + // if (loading && version <= 16) ser->skip(XXX bytes); + for (i = 0; i < 8; ++i) + ser->saveLoadEntries(0, volumeFaderEntries); + + if (ser->isLoading()) { + // Load all sounds that we need + fix_players_after_load(scumm); + fix_parts_after_load(); + setImuseMasterVolume(_master_volume); + + if (_midi_native) + reallocateMidiChannels(_midi_native); + if (_midi_adlib) + reallocateMidiChannels(_midi_adlib); + } + + return 0; +} + +void IMuseInternal::fix_parts_after_load() { + Part *part; + int i; + + for (i = ARRAYSIZE(_parts), part = _parts; i != 0; i--, part++) { + if (part->_player) + part->fix_after_load(); + } +} + +// Only call this routine from the main thread, +// since it uses getResourceAddress +void IMuseInternal::fix_players_after_load(ScummEngine *scumm) { + Player *player = _players; + int i; + + for (i = ARRAYSIZE(_players); i != 0; i--, player++) { + if (player->isActive()) { + scumm->getResourceAddress(rtSound, player->getID()); + player->fixAfterLoad(); + } + } +} + +Part::Part() { + _slot = 0; + _next = 0; + _prev = 0; + _mc = 0; + _player = 0; + _pitchbend = 0; + _pitchbend_factor = 0; + _transpose = 0; + _transpose_eff = 0; + _vol = 0; + _vol_eff = 0; + _detune = 0; + _detune_eff = 0; + _pan = 0; + _pan_eff = 0; + _on = false; + _modwheel = 0; + _pedal = false; + _pri = 0; + _pri_eff = 0; + _chan = 0; + _effect_level = 0; + _chorus = 0; + _percussion = 0; + _bank = 0; + _unassigned_instrument = false; +} + +void Part::saveLoadWithSerializer(Serializer *ser) { + const SaveLoadEntry partEntries[] = { + MKLINE(Part, _pitchbend, sleInt16, VER(8)), + MKLINE(Part, _pitchbend_factor, sleUint8, VER(8)), + MKLINE(Part, _transpose, sleInt8, VER(8)), + MKLINE(Part, _vol, sleUint8, VER(8)), + MKLINE(Part, _detune, sleInt8, VER(8)), + MKLINE(Part, _pan, sleInt8, VER(8)), + MKLINE(Part, _on, sleUint8, VER(8)), + MKLINE(Part, _modwheel, sleUint8, VER(8)), + MKLINE(Part, _pedal, sleUint8, VER(8)), + MK_OBSOLETE(Part, _program, sleUint8, VER(8), VER(16)), + MKLINE(Part, _pri, sleUint8, VER(8)), + MKLINE(Part, _chan, sleUint8, VER(8)), + MKLINE(Part, _effect_level, sleUint8, VER(8)), + MKLINE(Part, _chorus, sleUint8, VER(8)), + MKLINE(Part, _percussion, sleUint8, VER(8)), + MKLINE(Part, _bank, sleUint8, VER(8)), + MKEND() + }; + + int num; + if (ser->isSaving()) { + num = (_next ? (_next - _se->_parts + 1) : 0); + ser->saveUint16(num); + + num = (_prev ? (_prev - _se->_parts + 1) : 0); + ser->saveUint16(num); + + num = (_player ? (_player - _se->_players + 1) : 0); + ser->saveUint16(num); + } else { + num = ser->loadUint16(); + _next = (num ? &_se->_parts[num - 1] : 0); + + num = ser->loadUint16(); + _prev = (num ? &_se->_parts[num - 1] : 0); + + num = ser->loadUint16(); + _player = (num ? &_se->_players[num - 1] : 0); + } + ser->saveLoadEntries(this, partEntries); +} + +void Part::set_detune(int8 detune) { + _detune_eff = clamp((_detune = detune) + _player->getDetune(), -128, 127); + if (_mc) + sendPitchBend(); +} + +void Part::pitchBend(int16 value) { + _pitchbend = value; + if (_mc) + sendPitchBend(); +} + +void Part::volume(byte value) { + _vol_eff = ((_vol = value) + 1) * _player->getEffectiveVolume() >> 7; + if (_mc) + _mc->volume(_vol_eff); +} + +void Part::set_pri(int8 pri) { + _pri_eff = clamp((_pri = pri) + _player->getPriority(), 0, 255); + if (_mc) + _mc->priority(_pri_eff); +} + +void Part::set_pan(int8 pan) { + _pan_eff = clamp((_pan = pan) + _player->getPan(), -64, 63); + if (_mc) + _mc->panPosition(_pan_eff + 0x40); +} + +void Part::set_transpose(int8 transpose) { + _transpose_eff = transpose_clamp((_transpose = transpose) + _player->getTranspose(), -24, 24); + if (_mc) + sendPitchBend(); +} + +void Part::sustain(bool value) { + _pedal = value; + if (_mc) + _mc->sustain(value); +} + +void Part::modulationWheel(byte value) { + _modwheel = value; + if (_mc) + _mc->modulationWheel(value); +} + +void Part::chorusLevel(byte value) { + _chorus = value; + if (_mc) + _mc->chorusLevel(value); +} + +void Part::effectLevel(byte value) +{ + _effect_level = value; + if (_mc) + _mc->effectLevel(value); +} + +void Part::fix_after_load() { + set_transpose(_transpose); + volume(_vol); + set_detune(_detune); + set_pri(_pri); + set_pan(_pan); + sendAll(); +} + +void Part::pitchBendFactor(byte value) { + if (value > 12) + return; + pitchBend(0); + _pitchbend_factor = value; + if (_mc) + _mc->pitchBendFactor(value); +} + +void Part::set_onoff(bool on) { + if (_on != on) { + _on = on; + if (!on) + off(); + if (!_percussion) + _player->_se->reallocateMidiChannels(_player->getMidiDriver()); + } +} + +void Part::set_instrument(byte * data) { + _instrument.adlib(data); + if (clearToTransmit()) + _instrument.send(_mc); +} + +void Part::load_global_instrument(byte slot) { + _player->_se->copyGlobalAdlibInstrument(slot, &_instrument); + if (clearToTransmit()) + _instrument.send(_mc); +} + +void Part::noteOn(byte note, byte velocity) { + if (!_on) + return; + + MidiChannel *mc = _mc; + + // DEBUG + if (_unassigned_instrument && !_percussion) { + _unassigned_instrument = false; + if (!_instrument.isValid()) { + debug(0, "[%02d] No instrument specified", (int)_chan); + return; + } + } + + if (mc && _instrument.isValid()) { + mc->noteOn(note, velocity); + } else if (_percussion) { + mc = _player->getMidiDriver()->getPercussionChannel(); + if (!mc) + return; + static byte prev_vol_eff = 128; + if (_vol_eff != prev_vol_eff){ + mc->volume(_vol_eff); + prev_vol_eff = _vol_eff; + } + if ((note < 35) && (!_player->_se->isNativeMT32())) + note = Instrument::_gmRhythmMap[note]; + + mc->noteOn(note, velocity); + } +} + +void Part::noteOff(byte note) { + if (!_on) + return; + + MidiChannel *mc = _mc; + if (mc) { + mc->noteOff(note); + } else if (_percussion) { + mc = _player->getMidiDriver()->getPercussionChannel(); + if (mc) + mc->noteOff(note); + } +} + +void Part::init() { + _player = NULL; + _next = NULL; + _prev = NULL; + _mc = NULL; +} + +void Part::setup(Player *player) { + _player = player; + + _percussion = (player->isMIDI() && _chan == 9); // true; + _on = true; + _pri_eff = player->getPriority(); + _pri = 0; + _vol = 127; + _vol_eff = player->getEffectiveVolume(); + _pan = clamp(player->getPan(), -64, 63); + _transpose_eff = player->getTranspose(); + _transpose = 0; + _detune = 0; + _detune_eff = player->getDetune(); + _pitchbend_factor = 2; + _pitchbend = 0; + _effect_level = 64; + _instrument.clear(); + _unassigned_instrument = true; + _chorus = 0; + _modwheel = 0; + _bank = 0; + _pedal = false; + _mc = NULL; +} + +void Part::uninit() { + if (!_player) + return; + off(); + _player->removePart(this); + _player = NULL; +} + +void Part::off() { + if (_mc) { + _mc->allNotesOff(); + _mc->release(); + _mc = NULL; + } +} + +bool Part::clearToTransmit() { + if (_mc) + return true; + if (_instrument.isValid()) + _player->_se->reallocateMidiChannels(_player->getMidiDriver()); + return false; +} + +void Part::sendAll() { + if (!clearToTransmit()) + return; + _mc->pitchBendFactor(_pitchbend_factor); + sendPitchBend(); + _mc->volume(_vol_eff); + _mc->sustain(_pedal); + _mc->modulationWheel(_modwheel); + _mc->panPosition(_pan_eff + 0x40); + _mc->effectLevel(_effect_level); + if (_instrument.isValid()) + _instrument.send(_mc); + _mc->chorusLevel(_chorus); + _mc->priority(_pri_eff); +} + +void Part::sendPitchBend() { + int16 bend = _pitchbend; + // RPN-based pitchbend range doesn't work for the MT32, + // so we'll do the scaling ourselves. + if (_player->_se->isNativeMT32()) + bend = bend * _pitchbend_factor / 12; + _mc->pitchBend(clamp(bend + (_detune_eff * 64 / 12) + (_transpose_eff * 8192 / 12), -8192, 8191)); +} + +void Part::programChange(byte value) { + _bank = 0; + _instrument.program(value, _player->isMT32()); + if (clearToTransmit()) + _instrument.send(_mc); +} + +void Part::set_instrument(uint b) { + _bank = (byte)(b >> 8); + if (_bank) + error("Non-zero instrument bank selection. Please report this"); + _instrument.program((byte)b, _player->isMT32()); + if (clearToTransmit()) + _instrument.send(_mc); +} + +void Part::allNotesOff() { + if (!_mc) + return; + _mc->allNotesOff(); +} + +//////////////////////////////////////// +// +// Some more IMuseInternal stuff +// +//////////////////////////////////////// + +void IMuseInternal::midiTimerCallback(void *data) { + MidiDriver *driver = (MidiDriver *)data; + if (g_scumm->_imuse) + g_scumm->_imuse->on_timer(driver); +} + +void IMuseInternal::reallocateMidiChannels(MidiDriver *midi) { + Part *part, *hipart; + int i; + byte hipri, lopri; + Part *lopart; + + while (true) { + hipri = 0; + hipart = NULL; + for (i = 32, part = _parts; i; i--, part++) { + if (part->_player && part->_player->getMidiDriver() == midi && + !part->_percussion && part->_on && + !part->_mc && part->_pri_eff >= hipri) { + hipri = part->_pri_eff; + hipart = part; + } + } + + if (!hipart) + return; + + if ((hipart->_mc = midi->allocateChannel()) == NULL) { + lopri = 255; + lopart = NULL; + for (i = 32, part = _parts; i; i--, part++) { + if (part->_mc && part->_mc->device() == midi && part->_pri_eff <= lopri) { + lopri = part->_pri_eff; + lopart = part; + } + } + + if (lopart == NULL || lopri >= hipri) + return; + lopart->off(); + + if ((hipart->_mc = midi->allocateChannel()) == NULL) + return; + } + hipart->sendAll(); + } +} + +void IMuseInternal::setGlobalAdlibInstrument(byte slot, byte *data) { + if (slot < 32) { + _global_adlib_instruments[slot].adlib(data); + } +} + +void IMuseInternal::copyGlobalAdlibInstrument(byte slot, Instrument *dest) { + if (slot >= 32) + return; + _global_adlib_instruments[slot].copy_to(dest); +} + +//////////////////////////////////////////////////////////// +// +// IMuse implementation +// +// IMuse actually serves as a concurency monitor front-end +// to IMuseInternal and ensures that only one thread +// accesses the object at a time. This is necessary to +// prevent scripts and the MIDI parser from yanking objects +// out from underneath each other. +// +//////////////////////////////////////////////////////////// + +IMuse::IMuse(OSystem *system, IMuseInternal *target) + : _system(system), _target(target) { + _mutex = system->createMutex(); +} + +IMuse::~IMuse() { + if (_mutex) + _system->deleteMutex(_mutex); + if (_target) + delete _target; +} + +inline void IMuse::in() const { + _system->lockMutex(_mutex); +} +inline void IMuse::out() const { + _system->unlockMutex(_mutex); +} + +void IMuse::on_timer(MidiDriver *midi) { in(); _target->on_timer(midi); out(); } +void IMuse::pause(bool paused) { in(); _target->pause(paused); out(); } +int IMuse::save_or_load(Serializer *ser, ScummEngine *scumm) { in(); int ret = _target->save_or_load(ser, scumm); out(); return ret; } +void IMuse::setMusicVolume(int vol) { in(); _target->setMusicVolume(vol); out(); } +void IMuse::startSound(int sound) { in(); _target->startSound(sound); out(); } +void IMuse::stopSound(int sound) { in(); _target->stopSound(sound); out(); } +void IMuse::stopAllSounds() { in(); _target->stopAllSounds(); out(); } +int IMuse::getSoundStatus(int sound) const { in(); int ret = _target->getSoundStatus(sound, true); out(); return ret; } +bool IMuse::get_sound_active(int sound) const { in(); bool ret = _target->getSoundStatus(sound, false) ? 1 : 0; out(); return ret; } +int IMuse::getMusicTimer() const { in(); int ret = _target->getMusicTimer(); out(); return ret; } +int32 IMuse::doCommand(int a, int b, int c, int d, int e, int f, int g, int h) { in(); int32 ret = _target->doCommand(a,b,c,d,e,f,g,h); out(); return ret; } +int32 IMuse::doCommand(int numargs, int args[]) { in(); int32 ret = _target->doCommand(numargs, args); out(); return ret; } +int IMuse::clear_queue() { in(); int ret = _target->clear_queue(); out(); return ret; } +void IMuse::setBase(byte **base) { in(); _target->setBase(base); out(); } +uint32 IMuse::property(int prop, uint32 value) { in(); uint32 ret = _target->property(prop, value); out(); return ret; } +void IMuse::terminate() { in(); _target->terminate1(); out(); _target->terminate2(); } + +// The IMuse::create method provides a front-end factory +// for creating IMuseInternal without exposing that class +// to the client. +IMuse *IMuse::create(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver) { + IMuseInternal *engine = IMuseInternal::create(syst, nativeMidiDriver, adlibMidiDriver); + return new IMuse(syst, engine); +} + +} // End of namespace Scumm diff --git a/engines/scumm/imuse/imuse.h b/engines/scumm/imuse/imuse.h new file mode 100644 index 0000000000..0c9a9206ba --- /dev/null +++ b/engines/scumm/imuse/imuse.h @@ -0,0 +1,85 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2001 Ludvig Strigeus + * Copyright (C) 2001-2006 The ScummVM project + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef IMUSE_H +#define IMUSE_H + +#include "common/scummsys.h" +#include "common/mutex.h" +#include "scumm/music.h" + +class MidiDriver; +class OSystem; + +namespace Scumm { + +class IMuseInternal; +class ScummEngine; +class Serializer; + +class IMuse : public MusicEngine { +private: + OSystem *_system; + IMuseInternal *_target; + mutable Common::MutexRef _mutex; + + IMuse(OSystem *system, IMuseInternal *target); + void in() const; + void out() const; + +public: + ~IMuse(); + + enum { + PROP_TEMPO_BASE, + PROP_NATIVE_MT32, + PROP_GS, + PROP_LIMIT_PLAYERS, + PROP_RECYCLE_PLAYERS, + PROP_DIRECT_PASSTHROUGH + }; + + void on_timer(MidiDriver *midi); + void pause(bool paused); + int save_or_load(Serializer *ser, ScummEngine *scumm); + void setMusicVolume(int vol); + void startSound(int sound); + void stopSound(int sound); + void stopAllSounds(); + int getSoundStatus(int sound) const; + bool get_sound_active(int sound) const; + int getMusicTimer() const; + int32 doCommand(int a, int b, int c, int d, int e, int f, int g, int h); + int32 doCommand(int numargs, int args[]); + int clear_queue(); + void setBase(byte **base); + uint32 property(int prop, uint32 value); + void terminate(); + + // Factory methods + static IMuse *create(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver); +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/imuse/imuse_internal.h b/engines/scumm/imuse/imuse_internal.h new file mode 100644 index 0000000000..3640cafa64 --- /dev/null +++ b/engines/scumm/imuse/imuse_internal.h @@ -0,0 +1,472 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2001 Ludvig Strigeus + * Copyright (C) 2001-2006 The ScummVM project + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef DEFINED_IMUSE_INTERNAL +#define DEFINED_IMUSE_INTERNAL + +#include "common/scummsys.h" +#include "scumm/imuse/instrument.h" +#include "scumm/saveload.h" +#include "sound/mididrv.h" + +class MidiParser; +class OSystem; + +namespace Scumm { + +// Unremark this statement to activate some of +// the most common iMuse diagnostic messages. +// #define IMUSE_DEBUG + +struct ParameterFader; +struct DeferredCommand; +struct ImTrigger; +struct SustainingNotes; +struct CommandQueue; +struct IsNoteCmdData; +class Player; +struct Part; +class IMuseInternal; + +// Some entities also referenced +class ScummEngine; + + + +////////////////////////////////////////////////// +// +// Some constants +// +////////////////////////////////////////////////// + +#define TICKS_PER_BEAT 480 + +#define TRIGGER_ID 0 +#define COMMAND_ID 1 + +#define MDPG_TAG "MDpg" + + +//////////////////////////////////////// +// +// Helper functions +// +//////////////////////////////////////// + +inline int clamp(int val, int min, int max) { + if (val < min) + return min; + if (val > max) + return max; + return val; +} + +inline int transpose_clamp(int a, int b, int c) { + if (b > a) + a += (b - a + 11) / 12 * 12; + if (c < a) + a -= (a - c + 11) / 12 * 12; + return a; +} + + + +////////////////////////////////////////////////// +// +// Entity declarations +// +////////////////////////////////////////////////// + +struct HookDatas { + byte _jump[2]; + byte _transpose; + byte _part_onoff[16]; + byte _part_volume[16]; + byte _part_program[16]; + byte _part_transpose[16]; + + int query_param(int param, byte chan); + int set(byte cls, byte value, byte chan); + HookDatas() { memset(this, 0, sizeof(HookDatas)); } +}; + +struct ParameterFader { + enum { + pfVolume = 1, + pfTranspose = 3, + pfSpeed = 4 + }; + + int param; + int start; + int end; + uint32 total_time; + uint32 current_time; + + ParameterFader() { param = 0; } + void init() { param = 0; } +}; + +struct DeferredCommand { + uint32 time_left; + int a, b, c, d, e, f; + DeferredCommand() { memset(this, 0, sizeof(DeferredCommand)); } +}; + +struct ImTrigger { + int sound; + byte id; + uint16 expire; + int command [8]; + ImTrigger() { memset(this, 0, sizeof(ImTrigger)); } +}; + +struct CommandQueue { + uint16 array[8]; + CommandQueue() { memset(this, 0, sizeof(CommandQueue)); } +}; + +class Player : public MidiDriver { +protected: + // Moved from IMuseInternal. + // This is only used by one player at a time. + static uint16 _active_notes[128]; + +protected: + MidiDriver *_midi; + MidiParser *_parser; + bool _passThrough; // Only respond to EOT, all else direct to MidiDriver + + Part *_parts; + bool _active; + bool _scanning; + int _id; + byte _priority; + byte _volume; + int8 _pan; + int8 _transpose; + int8 _detune; + byte _vol_eff; + + uint _track_index; + uint _loop_to_beat; + uint _loop_from_beat; + uint _loop_counter; + uint _loop_to_tick; + uint _loop_from_tick; + byte _speed; + bool _abort; + + // This does not get used by us! It is only + // here for save/load purposes, and gets + // passed on to the MidiParser during + // fixAfterLoad(). + uint32 _music_tick; + + HookDatas _hook; + ParameterFader _parameterFaders[4]; + + bool _isMT32; + bool _isMIDI; + +protected: + // Player part + void hook_clear(); + void uninit_parts(); + byte *parse_midi(byte *s); + void part_set_transpose(uint8 chan, byte relative, int8 b); + void parse_sysex(byte *p, uint len); + void maybe_jump(byte cmd, uint track, uint beat, uint tick); + void maybe_set_transpose(byte *data); + void maybe_part_onoff(byte *data); + void maybe_set_volume(byte *data); + void maybe_set_program(byte *data); + void maybe_set_transpose_part(byte *data); + void turn_off_pedals(); + int query_part_param(int param, byte chan); + void turn_off_parts(); + void play_active_notes(); + + void transitionParameters(); + + static void decode_sysex_bytes(const byte *src, byte *dst, int len); + + // Sequencer part + int start_seq_sound(int sound, bool reset_vars = true); + int query_param(int param); + +public: + IMuseInternal *_se; + uint _vol_chan; + +public: + Player(); + virtual ~Player(); + + int addParameterFader(int param, int target, int time); + void clear(); + void clearLoop(); + void fixAfterLoad(); + Part * getActivePart(uint8 part); + uint getBeatIndex(); + int8 getDetune() const { return _detune; } + byte getEffectiveVolume() const { return _vol_eff; } + int getID() const { return _id; } + MidiDriver *getMidiDriver() const { return _midi; } + int getParam(int param, byte chan); + int8 getPan() const { return _pan; } + Part * getPart(uint8 part); + byte getPriority() const { return _priority; } + uint getTicksPerBeat() const { return TICKS_PER_BEAT; } + int8 getTranspose() const { return _transpose; } + byte getVolume() const { return _volume; } + bool isActive() const { return _active; } + bool isFadingOut() const; + bool isMIDI() const { return _isMIDI; } + bool isMT32() const { return _isMT32; } + bool jump(uint track, uint beat, uint tick); + void onTimer(); + void removePart(Part *part); + int scan(uint totrack, uint tobeat, uint totick); + void saveLoadWithSerializer(Serializer *ser); + int setHook(byte cls, byte value, byte chan) { return _hook.set(cls, value, chan); } + void setDetune(int detune); + bool setLoop(uint count, uint tobeat, uint totick, uint frombeat, uint fromtick); + void setPan(int pan); + void setPriority(int pri); + void setSpeed(byte speed); + int setTranspose(byte relative, int b); + int setVolume(byte vol); + bool startSound(int sound, MidiDriver *midi, bool passThrough); + int getMusicTimer() const; + +public: + // MidiDriver interface + int open() { return 0; } + void close() { } + void send(uint32 b); + const char *getErrorName(int error_code) { return "Unknown"; } + void sysEx(byte *msg, uint16 length); + void metaEvent(byte type, byte *data, uint16 length); + void setTimerCallback(void *timer_param, void(*timer_proc)(void *)) { } + uint32 getBaseTempo(); + MidiChannel *allocateChannel() { return 0; } + MidiChannel *getPercussionChannel() { return 0; } +}; + +struct Part : public Serializable { + IMuseInternal *_se; + int _slot; + Part *_next, *_prev; + MidiChannel *_mc; + Player *_player; + int16 _pitchbend; + byte _pitchbend_factor; + int8 _transpose, _transpose_eff; + byte _vol, _vol_eff; + int8 _detune, _detune_eff; + int8 _pan, _pan_eff; + bool _on; + byte _modwheel; + bool _pedal; + int8 _pri; + byte _pri_eff; + byte _chan; + byte _effect_level; + byte _chorus; + byte _percussion; + byte _bank; + + // New abstract instrument definition + Instrument _instrument; + bool _unassigned_instrument; // For diagnostic reporting purposes only + + // MidiChannel interface + // (We don't currently derive from MidiChannel, + // but if we ever do, this will make it easy.) + void noteOff(byte note); + void noteOn(byte note, byte velocity); + void programChange(byte value); + void pitchBend(int16 value); + void modulationWheel(byte value); + void volume(byte value); + void pitchBendFactor(byte value); + void sustain(bool value); + void effectLevel(byte value); + void chorusLevel(byte value); + void allNotesOff(); + + void set_param(byte param, int value) { } + void init(); + void setup(Player *player); + void uninit(); + void off(); + void set_instrument(uint b); + void set_instrument(byte *data); + void load_global_instrument(byte b); + + void set_transpose(int8 transpose); + void set_detune(int8 detune); + void set_pri(int8 pri); + void set_pan(int8 pan); + + void set_onoff(bool on); + void fix_after_load(); + + void sendAll(); + void sendPitchBend(); + bool clearToTransmit(); + + Part(); + + void saveLoadWithSerializer(Serializer *ser); +}; + +// WARNING: This is the internal variant of the IMUSE class. +// imuse.h contains a public version of the same class. +// the public version, only contains a set of methods. +class IMuseInternal { + friend class Player; + friend struct Part; + +protected: + bool _native_mt32; + bool _enable_gs; + bool _sc55; + MidiDriver *_midi_adlib; + MidiDriver *_midi_native; + + byte **_base_sounds; + +protected: + bool _paused; + bool _initialized; + + int _tempoFactor; + + int _player_limit; // Limits how many simultaneous music tracks are played + bool _recycle_players; // Can we stop a player in order to start another one? + bool _direct_passthrough; // Pass data direct to MidiDriver (no interactivity) + + uint _queue_end, _queue_pos, _queue_sound; + byte _queue_adding; + + byte _queue_marker; + byte _queue_cleared; + byte _master_volume; // Master volume. 0-255 + byte _music_volume; // Global music volume. 0-255 + + uint16 _trigger_count; + ImTrigger _snm_triggers[16]; // Sam & Max triggers + uint16 _snm_trigger_index; + + uint16 _channel_volume[8]; + uint16 _channel_volume_eff[8]; // No Save + uint16 _volchan_table[8]; + + Player _players[8]; + Part _parts[32]; + + Instrument _global_adlib_instruments[32]; + CommandQueue _cmd_queue[64]; + DeferredCommand _deferredCommands[4]; + +protected: + byte *findStartOfSound(int sound); + bool isMT32(int sound); + bool isMIDI(int sound); + int get_queue_sound_status(int sound) const; + void handle_marker(uint id, byte data); + int get_channel_volume(uint a); + void initMidiDriver(MidiDriver *midi); + void initGM(MidiDriver *midi); + void initMT32(MidiDriver *midi); + void init_players(); + void init_parts(); + void init_queue(); + + void sequencer_timers(MidiDriver *midi); + + MidiDriver *getBestMidiDriver(int sound); + Player *allocate_player(byte priority); + Part *allocate_part(byte pri, MidiDriver *midi); + + int32 ImSetTrigger(int sound, int id, int a, int b, int c, int d, int e, int f, int g, int h); + int32 ImClearTrigger(int sound, int id); + int32 ImFireAllTriggers(int sound); + + void addDeferredCommand(int time, int a, int b, int c, int d, int e, int f); + void handleDeferredCommands(MidiDriver *midi); + + int enqueue_command(int a, int b, int c, int d, int e, int f, int g); + int enqueue_trigger(int sound, int marker); + int query_queue(int param); + Player *findActivePlayer(int id); + + int get_volchan_entry(uint a); + int set_volchan_entry(uint a, uint b); + int set_channel_volume(uint chan, uint vol); + void update_volumes(); + void reset_tick(); + + int set_volchan(int sound, int volchan); + + void fix_parts_after_load(); + void fix_players_after_load(ScummEngine *scumm); + + static void midiTimerCallback(void *data); + +public: + IMuseInternal(); + + int initialize(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver); + void reallocateMidiChannels(MidiDriver *midi); + void setGlobalAdlibInstrument(byte slot, byte *data); + void copyGlobalAdlibInstrument(byte slot, Instrument *dest); + bool isNativeMT32() { return _native_mt32; } + + // IMuse interface + + void on_timer(MidiDriver *midi); + void pause(bool paused); + int terminate1(); + int terminate2(); + int save_or_load(Serializer *ser, ScummEngine *scumm); + int setMusicVolume(uint vol); + int setImuseMasterVolume(uint vol); + bool startSound(int sound); + int stopSound(int sound); + int stopAllSounds(); + int getSoundStatus(int sound, bool ignoreFadeouts = true) const; + int getMusicTimer() const; + int32 doCommand (int a, int b, int c, int d, int e, int f, int g, int h); + int32 doCommand (int numargs, int args[]); + int clear_queue(); + void setBase(byte **base); + uint32 property(int prop, uint32 value); + + static IMuseInternal *create(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver); +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/imuse/imuse_player.cpp b/engines/scumm/imuse/imuse_player.cpp new file mode 100644 index 0000000000..59cb998021 --- /dev/null +++ b/engines/scumm/imuse/imuse_player.cpp @@ -0,0 +1,1241 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2001 Ludvig Strigeus + * Copyright (C) 2001-2006 The ScummVM project + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "common/stdafx.h" + +#include "common/util.h" +#include "base/engine.h" + +#include "scumm/imuse/imuse_internal.h" +#include "scumm/saveload.h" +#include "scumm/scumm.h" + +#include "sound/midiparser.h" + +namespace Scumm { + +//////////////////////////////////////// +// +// Miscellaneous +// +//////////////////////////////////////// + +#define IMUSE_SYSEX_ID 0x7D +#define YM2612_SYSEX_ID 0x7C +#define ROLAND_SYSEX_ID 0x41 +#define PERCUSSION_CHANNEL 9 + +extern MidiParser *MidiParser_createRO(); +extern MidiParser *MidiParser_createEUP(); + +uint16 Player::_active_notes[128]; + + + +////////////////////////////////////////////////// +// +// IMuse Player implementation +// +////////////////////////////////////////////////// + +Player::Player() : + _midi(0), + _parser(0), + _passThrough(0), + _parts(0), + _active(false), + _scanning(false), + _id(0), + _priority(0), + _volume(0), + _pan(0), + _transpose(0), + _detune(0), + _vol_eff(0), + _track_index(0), + _loop_to_beat(0), + _loop_from_beat(0), + _loop_counter(0), + _loop_to_tick(0), + _loop_from_tick(0), + _speed(128), + _isMT32(false), + _isMIDI(false), + _se(0), + _vol_chan(0){ +} + +Player::~Player() { + if (_parser) { + delete _parser; + _parser = 0; + } +} + +bool Player::startSound(int sound, MidiDriver *midi, bool passThrough) { + void *ptr; + int i; + + // Not sure what the old code was doing, + // but we'll go ahead and do a similar check. + ptr = _se->findStartOfSound(sound); + if (!ptr) { + error("Player::startSound(): Couldn't find start of sound %d!", sound); + return false; + } + + _isMT32 = _se->isMT32(sound); + _isMIDI = _se->isMIDI(sound); + + _parts = NULL; + _active = true; + _midi = midi; + _id = sound; + _priority = 0x80; + _volume = 0x7F; + _vol_chan = 0xFFFF; + _vol_eff = (_se->get_channel_volume(0xFFFF) << 7) >> 7; + _pan = 0; + _transpose = 0; + _detune = 0; + _passThrough = passThrough; + + for (i = 0; i < ARRAYSIZE(_parameterFaders); ++i) + _parameterFaders[i].init(); + hook_clear(); + + if (start_seq_sound(sound) != 0) { + _active = false; + _midi = NULL; + return false; + } + +#ifdef IMUSE_DEBUG + debug(0, "Starting music %d", sound); +#endif + return true; +} + +int Player::getMusicTimer() const { + return _parser ? (_parser->getTick() * 2 / _parser->getPPQN()) : 0; +} + +bool Player::isFadingOut() const { + int i; + for (i = 0; i < ARRAYSIZE(_parameterFaders); ++i) { + if (_parameterFaders[i].param == ParameterFader::pfVolume && + _parameterFaders[i].end == 0) { + return true; + } + } + return false; +} + +void Player::clear() { + if (!_active) + return; + +#ifdef IMUSE_DEBUG + debug(0, "Stopping music %d", _id); +#endif + + if (_parser) { + _parser->unloadMusic(); + delete _parser; + _parser = 0; + } + uninit_parts(); + _se->ImFireAllTriggers(_id); + _active = false; + _midi = NULL; + _id = 0; +} + +void Player::hook_clear() { + memset(&_hook, 0, sizeof(_hook)); +} + +int Player::start_seq_sound(int sound, bool reset_vars) { + byte *ptr; + + if (reset_vars) { + _loop_to_beat = 1; + _loop_from_beat = 1; + _track_index = 0; + _loop_counter = 0; + _loop_to_tick = 0; + _loop_from_tick = 0; + } + + ptr = _se->findStartOfSound(sound); + if (ptr == NULL) + return -1; + if (_parser) + delete _parser; + + if (!memcmp(ptr, "RO", 2)) { + // Old style 'RO' resource + _parser = MidiParser_createRO(); + } else if (!memcmp(ptr, "SO", 2)) { + // Euphony (FM-TOWNS) resource + _parser = MidiParser_createEUP(); + } else if (!memcmp(ptr, "FORM", 4)) { + // Humongous Games XMIDI resource + _parser = MidiParser::createParser_XMIDI(); + } else { + // SCUMM SMF resource + _parser = MidiParser::createParser_SMF(); + } + + _parser->setMidiDriver(this); + _parser->property(MidiParser::mpSmartJump, 1); + _parser->loadMusic(ptr, 0); + _parser->setTrack(_track_index); + setSpeed(reset_vars ? 128 : _speed); + + return 0; +} + +void Player::uninit_parts() { + if (_parts && _parts->_player != this) + error("asd"); + while (_parts) + _parts->uninit(); + + // In case another player is waiting to allocate parts + if (_midi) + _se->reallocateMidiChannels(_midi); +} + +void Player::setSpeed(byte speed) { + _speed = speed; + if (_parser) + _parser->setTimerRate(((_midi->getBaseTempo() * speed) >> 7) * _se->_tempoFactor / 100); +} + +void Player::send(uint32 b) { + if (_passThrough) { + _midi->send(b); + return; + } + + byte cmd = (byte)(b & 0xF0); + byte chan = (byte)(b & 0x0F); + byte param1 = (byte)((b >> 8) & 0xFF); + byte param2 = (byte)((b >> 16) & 0xFF); + Part *part; + + switch (cmd >> 4) { + case 0x8: // Key Off + if (!_scanning) { + if ((part = getPart(chan)) != 0) + part->noteOff(param1); + } else { + _active_notes[param1] &= ~(1 << chan); + } + break; + + case 0x9: // Key On + if (!_scanning) { + if (_isMT32 && !_se->isNativeMT32()) + param2 = (((param2 * 3) >> 2) + 32) & 0x7F; + if ((part = getPart(chan)) != 0) + part->noteOn(param1, param2); + } else { + _active_notes[param1] |= (1 << chan); + } + break; + + case 0xB: // Control Change + part = (param1 == 123 ? getActivePart(chan) : getPart(chan)); + if (!part) + break; + + switch (param1) { + case 0: // Bank select. Not supported + break; + case 1: // Modulation Wheel + part->modulationWheel(param2); + break; + case 7: // Volume + part->volume(param2); + break; + case 10: // Pan Position + part->set_pan(param2 - 0x40); + break; + case 16: // Pitchbend Factor(non-standard) + part->pitchBendFactor(param2); + break; + case 17: // GP Slider 2 + part->set_detune(param2 - 0x40); + break; + case 18: // GP Slider 3 + part->set_pri(param2 - 0x40); + _se->reallocateMidiChannels(_midi); + break; + case 64: // Sustain Pedal + part->sustain(param2 != 0); + break; + case 91: // Effects Level + part->effectLevel(param2); + break; + case 93: // Chorus Level + part->chorusLevel(param2); + break; + case 116: // XMIDI For Loop. Not supported + // Used in the ending sequence of puttputt + break; + case 117: // XMIDI Next/Break. Not supported + // Used in the ending sequence of puttputt + break; + case 123: // All Notes Off + part->allNotesOff(); + break; + default: + error("Player::send(): Invalid control change %d", param1); + } + break; + + case 0xC: // Program Change + part = getPart(chan); + if (part) { + if (_isMIDI) { + if (param1 < 128) + part->programChange(param1); + } else { + if (param1 < 32) + part->load_global_instrument(param1); + } + } + break; + + case 0xE: // Pitch Bend + part = getPart(chan); + if (part) + part->pitchBend(((param2 << 7) | param1) - 0x2000); + break; + + case 0xA: // Aftertouch + case 0xD: // Channel Pressure + case 0xF: // Sequence Controls + break; + + default: + if (!_scanning) { + error("Player::send(): Invalid command %d", cmd); + clear(); + } + } + return; +} + +void Player::sysEx(byte *p, uint16 len) { + byte code; + byte a; + uint b; + byte buf[128]; + Part *part; + + if (_passThrough) { + _midi->sysEx(p, len); + return; + } + + // Check SysEx manufacturer. + a = *p++; + --len; + if (a != IMUSE_SYSEX_ID) { + if (a == ROLAND_SYSEX_ID) { + // Roland custom instrument definition. + part = getPart(p[0] & 0x0F); + if (part) { + part->_instrument.roland(p - 1); + if (part->clearToTransmit()) + part->_instrument.send(part->_mc); + } + } else if (a == YM2612_SYSEX_ID) { + // FM-TOWNS custom instrument definition + _midi->sysEx_customInstrument(p[0], 'EUP ', p + 1); + } else { + error("Unknown SysEx manufacturer 0x%02X", (int)a); + } + return; + } + --len; + + // Too big? + if (len >= sizeof(buf) * 2) + return; + +#ifdef IMUSE_DEBUG + if (!_scanning) { + for (a = 0; a < len + 1 && a < 19; ++a) { + sprintf((char *)&buf[a*3], " %02X", p[a]); + } // next for + if (a < len + 1) { + buf[a*3] = buf[a*3+1] = buf[a*3+2] = '.'; + ++a; + } // end if + buf[a*3] = '\0'; + debug(0, "[%02d] SysEx:%s", _id, buf); + } +#endif + + switch (code = *p++) { + case 0: + if (g_scumm->_game.id != GID_SAMNMAX) { + // There are 17 bytes of useful information beyond + // what we've read so far. All we know about them is + // as follows: + // BYTE 00: Channel # + // BYTE 02: BIT 01(0x01): Part on?(1 = yes) + // BYTE 04: Priority adjustment [guessing] + // BYTE 05: Volume(upper 4 bits) [guessing] + // BYTE 06: Volume(lower 4 bits) [guessing] + // BYTE 09: BIT 04(0x08): Percussion?(1 = yes) + // BYTE 15: Program(upper 4 bits) + // BYTE 16: Program(lower 4 bits) + part = getPart(p[0] & 0x0F); + if (part) { + part->set_onoff(p[2] & 0x01); + part->set_pri(p[4]); + part->volume((p[5] & 0x0F) << 4 |(p[6] & 0x0F)); + part->_percussion = _isMIDI ? ((p[9] & 0x08) > 0) : false; + if (part->_percussion) { + if (part->_mc) { + part->off(); + _se->reallocateMidiChannels(_midi); + } + } else { + // Even in cases where a program does not seem to be specified, + // i.e. bytes 15 and 16 are 0, we send a program change because + // 0 is a valid program number. MI2 tests show that in such + // cases, a regular program change message always seems to follow + // anyway. + if (_isMIDI) + part->_instrument.program((p[15] & 0x0F) << 4 |(p[16] & 0x0F), _isMT32); + part->sendAll(); + } + } + } else { + // Sam & Max: Trigger Event + // Triggers are set by doCommand(ImSetTrigger). + // When a SysEx marker is encountered whose sound + // ID and marker ID match what was set by ImSetTrigger, + // something magical is supposed to happen.... + for (a = 0; a < ARRAYSIZE(_se->_snm_triggers); ++a) { + if (_se->_snm_triggers[a].sound == _id && + _se->_snm_triggers[a].id == *p) + { + _se->_snm_triggers[a].sound = _se->_snm_triggers[a].id = 0; + _se->doCommand(8, _se->_snm_triggers[a].command); + break; + } + } + } // end if + break; + + case 1: + // This SysEx is used in Sam & Max for maybe_jump. + if (_scanning) + break; + maybe_jump(p[0], p[1] - 1, (READ_BE_UINT16(p + 2) - 1) * 4 + p[4], ((p[5] * TICKS_PER_BEAT) >> 2) + p[6]); + break; + + case 2: // Start of song. Ignore for now. + break; + + case 16: // Adlib instrument definition(Part) + a = *p++ & 0x0F; + ++p; // Skip hardware type + part = getPart(a); + if (part) { + if (len == 63) { + decode_sysex_bytes(p, buf, len - 3); + part->set_instrument((byte *)buf); + } else { + // SPK tracks have len == 49 here, and are not supported + part->programChange(254); // Must be invalid, but not 255 (which is reserved) + } + } + break; + + case 17: // Adlib instrument definition(Global) + p += 2; // Skip hardware type and... whatever came right before it + a = *p++; + decode_sysex_bytes(p, buf, len - 4); + _se->setGlobalAdlibInstrument(a, buf); + break; + + case 33: // Parameter adjust + a = *p++ & 0x0F; + ++p; // Skip hardware type + decode_sysex_bytes(p, buf, len - 3); + part = getPart(a); + if (part) + part->set_param(READ_BE_UINT16(buf), READ_BE_UINT16(buf + 2)); + break; + + case 48: // Hook - jump + if (_scanning) + break; + decode_sysex_bytes(p + 1, buf, len - 2); + maybe_jump(buf[0], READ_BE_UINT16(buf + 1), READ_BE_UINT16(buf + 3), READ_BE_UINT16(buf + 5)); + break; + + case 49: // Hook - global transpose + decode_sysex_bytes(p + 1, buf, len - 2); + maybe_set_transpose(buf); + break; + + case 50: // Hook - part on/off + buf[0] = *p++ & 0x0F; + decode_sysex_bytes(p, buf + 1, len - 2); + maybe_part_onoff(buf); + break; + + case 51: // Hook - set volume + buf[0] = *p++ & 0x0F; + decode_sysex_bytes(p, buf + 1, len - 2); + maybe_set_volume(buf); + break; + + case 52: // Hook - set program + buf[0] = *p++ & 0x0F; + decode_sysex_bytes(p, buf + 1, len - 2); + maybe_set_program(buf); + break; + + case 53: // Hook - set transpose + buf[0] = *p++ & 0x0F; + decode_sysex_bytes(p, buf + 1, len - 2); + maybe_set_transpose_part(buf); + break; + + case 64: // Marker + p++; + len -= 2; + while (len--) { + _se->handle_marker(_id, *p++); + } + break; + + case 80: // Loop + decode_sysex_bytes(p + 1, buf, len - 2); + setLoop(READ_BE_UINT16(buf), READ_BE_UINT16(buf + 2), + READ_BE_UINT16(buf + 4), READ_BE_UINT16(buf + 6), + READ_BE_UINT16(buf + 8)); + break; + + case 81: // End loop + clearLoop(); + break; + + case 96: // Set instrument + part = getPart(p[0] & 0x0F); + b = (p[1] & 0x0F) << 12 |(p[2] & 0x0F) << 8 |(p[4] & 0x0F) << 4 |(p[4] & 0x0F); + if (part) + part->set_instrument(b); + break; + + default: + error("Unknown SysEx command %d", (int)code); + } +} + +void Player::decode_sysex_bytes(const byte *src, byte *dst, int len) { + while (len >= 0) { + *dst++ = ((src[0] << 4)&0xFF) | (src[1] & 0xF); + src += 2; + len -= 2; + } +} + +void Player::maybe_jump(byte cmd, uint track, uint beat, uint tick) { + // Is this the hook I'm waiting for? + if (cmd && _hook._jump[0] != cmd) + return; + + // Reset hook? + if (cmd != 0 && cmd < 0x80) { + _hook._jump[0] = _hook._jump[1]; + _hook._jump[1] = 0; + } + + jump(track, beat, tick); +} + +void Player::maybe_set_transpose(byte *data) { + byte cmd; + + cmd = data[0]; + + // Is this the hook I'm waiting for? + if (cmd && _hook._transpose != cmd) + return; + + // Reset hook? + if (cmd != 0 && cmd < 0x80) + _hook._transpose = 0; + + setTranspose(data[1], (int8)data[2]); +} + +void Player::maybe_part_onoff(byte *data) { + byte cmd, *p; + uint chan; + Part *part; + + cmd = data[1]; + chan = data[0]; + + p = &_hook._part_onoff[chan]; + + // Is this the hook I'm waiting for? + if (cmd && *p != cmd) + return; + + if (cmd != 0 && cmd < 0x80) + *p = 0; + + part = getPart(chan); + if (part) + part->set_onoff(data[2] != 0); +} + +void Player::maybe_set_volume(byte *data) { + byte cmd; + byte *p; + uint chan; + Part *part; + + cmd = data[1]; + chan = data[0]; + + p = &_hook._part_volume[chan]; + + // Is this the hook I'm waiting for? + if (cmd && *p != cmd) + return; + + // Reset hook? + if (cmd != 0 && cmd < 0x80) + *p = 0; + + part = getPart(chan); + if (part) + part->volume(data[2]); +} + +void Player::maybe_set_program(byte *data) { + byte cmd; + byte *p; + uint chan; + Part *part; + + cmd = data[1]; + chan = data[0]; + + // Is this the hook I'm waiting for? + p = &_hook._part_program[chan]; + + if (cmd && *p != cmd) + return; + + if (cmd != 0 && cmd < 0x80) + *p = 0; + + part = getPart(chan); + if (part) + part->programChange(data[2]); +} + +void Player::maybe_set_transpose_part(byte *data) { + byte cmd; + byte *p; + uint chan; + + cmd = data[1]; + chan = data[0]; + + // Is this the hook I'm waiting for? + p = &_hook._part_transpose[chan]; + + if (cmd && *p != cmd) + return; + + // Reset hook? + if (cmd != 0 && cmd < 0x80) + *p = 0; + + part_set_transpose(chan, data[2], (int8)data[3]); +} + +int Player::setTranspose(byte relative, int b) { + Part *part; + + if (b > 24 || b < -24 || relative > 1) + return -1; + if (relative) + b = transpose_clamp(_transpose + b, -24, 24); + + _transpose = b; + + for (part = _parts; part; part = part->_next) { + part->set_transpose(part->_transpose); + } + + return 0; +} + +void Player::part_set_transpose(uint8 chan, byte relative, int8 b) { + Part *part; + + if (b > 24 || b < -24) + return; + + part = getPart(chan); + if (!part) + return; + if (relative) + b = transpose_clamp(b + part->_transpose, -7, 7); + part->set_transpose(b); +} + +bool Player::jump(uint track, uint beat, uint tick) { + if (!_parser) + return false; + if (_parser->setTrack(track)) + _track_index = track; + if (!_parser->jumpToTick((beat - 1) * TICKS_PER_BEAT + tick)) + return false; + turn_off_pedals(); + return true; +} + +bool Player::setLoop(uint count, uint tobeat, uint totick, uint frombeat, uint fromtick) { + if (tobeat + 1 >= frombeat) + return false; + + if (tobeat == 0) + tobeat = 1; + + _loop_counter = 0; // Because of possible interrupts + _loop_to_beat = tobeat; + _loop_to_tick = totick; + _loop_from_beat = frombeat; + _loop_from_tick = fromtick; + _loop_counter = count; + + return true; +} + +void Player::clearLoop() { + _loop_counter = 0; +} + +void Player::turn_off_pedals() { + Part *part; + + for (part = _parts; part; part = part->_next) { + if (part->_pedal) + part->sustain(false); + } +} + +Part *Player::getActivePart(uint8 chan) { + Part *part = _parts; + while (part) { + if (part->_chan == chan) + return part; + part = part->_next; + } + return 0; +} + +Part *Player::getPart(uint8 chan) { + Part *part = getActivePart(chan); + if (part) + return part; + + part = _se->allocate_part(_priority, _midi); + if (!part) { + debug(1, "No parts available"); + return NULL; + } + + // Insert part into front of parts list + part->_prev = NULL; + part->_next = _parts; + if (_parts) + _parts->_prev = part; + _parts = part; + + + part->_chan = chan; + part->setup(this); + + return part; +} + +void Player::setPriority(int pri) { + Part *part; + + _priority = pri; + for (part = _parts; part; part = part->_next) { + part->set_pri(part->_pri); + } + _se->reallocateMidiChannels(_midi); +} + +void Player::setPan(int pan) { + Part *part; + + _pan = pan; + for (part = _parts; part; part = part->_next) { + part->set_pan(part->_pan); + } +} + +void Player::setDetune(int detune) { + Part *part; + + _detune = detune; + for (part = _parts; part; part = part->_next) { + part->set_detune(part->_detune); + } +} + +int Player::scan(uint totrack, uint tobeat, uint totick) { + if (!_active || !_parser) + return -1; + + if (tobeat == 0) + tobeat++; + + turn_off_parts(); + memset(_active_notes, 0, sizeof(_active_notes)); + _scanning = true; + + // If the scan involves a track switch, scan to the end of + // the current track so that our state when starting the + // new track is fully up to date. + if (totrack != _track_index) + _parser->jumpToTick((uint32)-1, true); + _parser->setTrack(totrack); + if (!_parser->jumpToTick((tobeat - 1) * TICKS_PER_BEAT + totick, true)) { + _scanning = false; + return -1; + } + + _scanning = false; + _se->reallocateMidiChannels(_midi); + play_active_notes(); + + if (_track_index != totrack) { + _track_index = totrack; + _loop_counter = 0; + } + return 0; +} + +void Player::turn_off_parts() { + Part *part; + + for (part = _parts; part; part = part->_next) + part->off(); + _se->reallocateMidiChannels(_midi); +} + +void Player::play_active_notes() { + int i, j; + uint mask; + Part *part; + + for (i = 0; i < 16; ++i) { + part = getPart(i); + if (part) { + mask = 1 << i; + for (j = 0; j < 128; ++j) { + if (_active_notes[j] & mask) + part->noteOn(j, 80); + } + } + } +} + +int Player::setVolume(byte vol) { + Part *part; + + if (vol > 127) + return -1; + + _volume = vol; + _vol_eff = _se->get_channel_volume(_vol_chan) * (vol + 1) >> 7; + + for (part = _parts; part; part = part->_next) { + part->volume(part->_vol); + } + + return 0; +} + +int Player::getParam(int param, byte chan) { + switch (param) { + case 0: + return (byte)_priority; + case 1: + return (byte)_volume; + case 2: + return (byte)_pan; + case 3: + return (byte)_transpose; + case 4: + return (byte)_detune; + case 5: + return _speed; + case 6: + return _track_index; + case 7: + return getBeatIndex(); + case 8: + return (_parser ? _parser->getTick() % TICKS_PER_BEAT : 0); // _tick_index; + case 9: + return _loop_counter; + case 10: + return _loop_to_beat; + case 11: + return _loop_to_tick; + case 12: + return _loop_from_beat; + case 13: + return _loop_from_tick; + case 14: + case 15: + case 16: + case 17: + return query_part_param(param, chan); + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + return _hook.query_param(param, chan); + default: + return -1; + } +} + +int Player::query_part_param(int param, byte chan) { + Part *part; + + part = _parts; + while (part) { + if (part->_chan == chan) { + switch (param) { + case 14: + return part->_on; + case 15: + return part->_vol; + case 16: +// FIXME: Need to know where this occurs... +error("Trying to cast instrument (%d, %d) -- please tell Fingolfin\n", param, chan); +// In old versions of the code, this used to return part->_program. +// This was changed in revision 2.29 of imuse.cpp (where this code used +// to reside). +// return (int)part->_instrument; + case 17: + return part->_transpose; + default: + return -1; + } + } + part = part->_next; + } + return 129; +} + +void Player::onTimer() { + // First handle any parameter transitions + // that are occuring. + transitionParameters(); + + // Since the volume parameter can cause + // the player to be deactivated, check + // to make sure we're still active. + if (!_active || !_parser) + return; + + uint32 target_tick = _parser->getTick(); + uint beat_index = target_tick / TICKS_PER_BEAT + 1; + uint tick_index = target_tick % TICKS_PER_BEAT; + + if (_loop_counter &&(beat_index > _loop_from_beat || + (beat_index == _loop_from_beat && tick_index >= _loop_from_tick))) + { + _loop_counter--; + jump(_track_index, _loop_to_beat, _loop_to_tick); + } + _parser->onTimer(); +} + +// "time" is referenced as hundredths of a second. +// IS THAT CORRECT?? +// We convert it to microseconds before proceeding +int Player::addParameterFader(int param, int target, int time) { + int start; + + switch (param) { + case ParameterFader::pfVolume: + // HACK: If volume is set to 0 with 0 time, + // set it so immediately but DON'T clear + // the player. This fixes a problem with + // music being cleared inappropriately + // in S&M when playing with the Dinosaur. + if (!target && !time) { + setVolume(0); + return 0; + } + + // Volume fades are handled differently. + start = _volume; + break; + + case ParameterFader::pfTranspose: + // FIXME: Is this transpose? And what's the scale? + // It's set to fade to -2400 in the tunnel of love. +// debug(0, "parameterTransition(3) outside Tunnel of Love?"); + start = _transpose; +// target /= 200; + break; + + case ParameterFader::pfSpeed: // impSpeed + // FIXME: Is the speed from 0-100? + // Right now I convert it to 0-128. + start = _speed; +// target = target * 128 / 100; + break; + + case 127: + { // FIXME? I *think* this clears all parameter faders. + ParameterFader *ptr = &_parameterFaders[0]; + int i; + for (i = ARRAYSIZE(_parameterFaders); i; --i, ++ptr) + ptr->param = 0; + return 0; + } + break; + + default: + debug(0, "Player::addParameterFader (%d, %d, %d): Unknown parameter", param, target, time); + return 0; // Should be -1, but we'll let the script think it worked. + } + + ParameterFader *ptr = &_parameterFaders[0]; + ParameterFader *best = 0; + int i; + for (i = ARRAYSIZE(_parameterFaders); i; --i, ++ptr) { + if (ptr->param == param) { + best = ptr; + start = ptr->end; + break; + } else if (!ptr->param) { + best = ptr; + } + } + + if (best) { + best->param = param; + best->start = start; + best->end = target; + if (!time) + best->total_time = 1; + else + best->total_time = (uint32)time * 10000; + best->current_time = 0; + } else { + debug(0, "IMuse Player %d: Out of parameter faders", _id); + return -1; + } + + return 0; +} + +void Player::transitionParameters() { + uint32 advance = _midi->getBaseTempo(); + int value; + + ParameterFader *ptr = &_parameterFaders[0]; + int i; + for (i = ARRAYSIZE(_parameterFaders); i; --i, ++ptr) { + if (!ptr->param) + continue; + + ptr->current_time += advance; + if (ptr->current_time > ptr->total_time) + ptr->current_time = ptr->total_time; + value = (int32)ptr->start + (int32)(ptr->end - ptr->start) * (int32)ptr->current_time / (int32)ptr->total_time; + + switch (ptr->param) { + case ParameterFader::pfVolume: + // Volume. + if (!value && !ptr->end) { + clear(); + return; + } + setVolume((byte)value); + break; + + case ParameterFader::pfTranspose: + // FIXME: Is this really transpose? + setTranspose(0, value / 100); + setDetune(value % 100); + break; + + case ParameterFader::pfSpeed: // impSpeed: + // Speed. + setSpeed((byte)value); + break; + + default: + ptr->param = 0; + } + + if (ptr->current_time >= ptr->total_time) + ptr->param = 0; + } +} + +uint Player::getBeatIndex() { + return (_parser ? (_parser->getTick() / TICKS_PER_BEAT + 1) : 0); +} + +void Player::removePart(Part *part) { + // Unlink + if (part->_next) + part->_next->_prev = part->_prev; + if (part->_prev) + part->_prev->_next = part->_next; + else + _parts = part->_next; + part->_next = part->_prev = 0; +} + +void Player::fixAfterLoad() { + _midi = _se->getBestMidiDriver(_id); + if (!_midi) { + clear(); + } else { + start_seq_sound(_id, false); + setSpeed(_speed); + if (_parser) + _parser->jumpToTick(_music_tick); // start_seq_sound already switched tracks + _isMT32 = _se->isMT32(_id); + _isMIDI = _se->isMIDI(_id); + } +} + +uint32 Player::getBaseTempo() { + return (_midi ? _midi->getBaseTempo() : 0); +} + +void Player::metaEvent(byte type, byte *msg, uint16 len) { + if (type == 0x2F) + clear(); +} + + + +//////////////////////////////////////// +// +// Player save/load functions +// +//////////////////////////////////////// + +void Player::saveLoadWithSerializer(Serializer *ser) { + static const SaveLoadEntry playerEntries[] = { + MKLINE(Player, _active, sleByte, VER(8)), + MKLINE(Player, _id, sleUint16, VER(8)), + MKLINE(Player, _priority, sleByte, VER(8)), + MKLINE(Player, _volume, sleByte, VER(8)), + MKLINE(Player, _pan, sleInt8, VER(8)), + MKLINE(Player, _transpose, sleByte, VER(8)), + MKLINE(Player, _detune, sleInt8, VER(8)), + MKLINE(Player, _vol_chan, sleUint16, VER(8)), + MKLINE(Player, _vol_eff, sleByte, VER(8)), + MKLINE(Player, _speed, sleByte, VER(8)), + MK_OBSOLETE(Player, _song_index, sleUint16, VER(8), VER(19)), + MKLINE(Player, _track_index, sleUint16, VER(8)), + MK_OBSOLETE(Player, _timer_counter, sleUint16, VER(8), VER(17)), + MKLINE(Player, _loop_to_beat, sleUint16, VER(8)), + MKLINE(Player, _loop_from_beat, sleUint16, VER(8)), + MKLINE(Player, _loop_counter, sleUint16, VER(8)), + MKLINE(Player, _loop_to_tick, sleUint16, VER(8)), + MKLINE(Player, _loop_from_tick, sleUint16, VER(8)), + MK_OBSOLETE(Player, _tempo, sleUint32, VER(8), VER(19)), + MK_OBSOLETE(Player, _cur_pos, sleUint32, VER(8), VER(17)), + MK_OBSOLETE(Player, _next_pos, sleUint32, VER(8), VER(17)), + MK_OBSOLETE(Player, _song_offset, sleUint32, VER(8), VER(17)), + MK_OBSOLETE(Player, _tick_index, sleUint16, VER(8), VER(17)), + MK_OBSOLETE(Player, _beat_index, sleUint16, VER(8), VER(17)), + MK_OBSOLETE(Player, _ticks_per_beat, sleUint16, VER(8), VER(17)), + MKLINE(Player, _music_tick, sleUint32, VER(19)), + MKLINE(Player, _hook._jump[0], sleByte, VER(8)), + MKLINE(Player, _hook._transpose, sleByte, VER(8)), + MKARRAY(Player, _hook._part_onoff[0], sleByte, 16, VER(8)), + MKARRAY(Player, _hook._part_volume[0], sleByte, 16, VER(8)), + MKARRAY(Player, _hook._part_program[0], sleByte, 16, VER(8)), + MKARRAY(Player, _hook._part_transpose[0], sleByte, 16, VER(8)), + MKEND() + }; + + const SaveLoadEntry parameterFaderEntries[] = { + MKLINE(ParameterFader, param, sleInt16, VER(17)), + MKLINE(ParameterFader, start, sleInt16, VER(17)), + MKLINE(ParameterFader, end, sleInt16, VER(17)), + MKLINE(ParameterFader, total_time, sleUint32, VER(17)), + MKLINE(ParameterFader, current_time, sleUint32, VER(17)), + MKEND() + }; + + if (!ser->isSaving() && _parser) { + delete _parser; + _parser = 0; + } + _music_tick = _parser ? _parser->getTick() : 0; + + int num; + if (ser->isSaving()) { + num = (_parts ? (_parts - _se->_parts + 1) : 0); + ser->saveUint16(num); + } else { + num = ser->loadUint16(); + _parts = (num ? &_se->_parts[num - 1] : 0); + } + ser->saveLoadEntries(this, playerEntries); + ser->saveLoadArrayOf(_parameterFaders, ARRAYSIZE(_parameterFaders), + sizeof(ParameterFader), parameterFaderEntries); + return; +} + +} // End of namespace Scumm diff --git a/engines/scumm/imuse/instrument.cpp b/engines/scumm/imuse/instrument.cpp new file mode 100644 index 0000000000..16d89c5ed2 --- /dev/null +++ b/engines/scumm/imuse/instrument.cpp @@ -0,0 +1,462 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2001 Ludvig Strigeus + * Copyright (C) 2001-2006 The ScummVM project + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "common/stdafx.h" +#include "scumm/scumm.h" +#include "scumm/saveload.h" +#include "scumm/imuse/instrument.h" +#include "sound/mididrv.h" + +namespace Scumm { + +static bool _native_mt32 = false; + +static struct { + const char *name; + byte program; +} + +roland_to_gm_map [] = { + // Monkey Island 2 instruments + // TODO: Complete + { "badspit ", 62 }, + { "Big Drum ", 116 }, + { "burp ", 58 }, +// { "dinkfall ", ??? }, +// { "Fire Pit ", ??? }, + { "foghorn ", 60 }, + { "glop ", 39 }, +// { "jacob's la", ??? }, + { "LeshBass ", 33 }, +// { "lowsnort ", ??? }, + { "ML explosn", 127 }, + { "ReggaeBass", 32 }, +// { "rope fall ", ??? }, + { "rumble ", 89 }, + { "SdTrk Bend", 97 }, +// { "snort ", ??? }, + { "spitting ", 62 }, + { "Swell 1 ", 95 }, + { "Swell 2 ", 95 }, + { "thnderclap", 127 } + + // Fate of Atlantis instruments + // TODO: Build +// { "*aah! ", ??? }, +// { "*ooh! ", ??? }, +// { "*ShotFar4 ", ??? }, +// { "*splash3 ", ??? }, +// { "*torpedo5 ", ??? }, +// { "*whip3 ", ??? }, +// { "*woodknock", ??? }, +// { "35 lavabub", ??? }, +// { "49 bzzt! ", ??? }, +// { "applause ", ??? }, +// { "Arabongo ", ??? }, +// { "Big Drum ", ??? }, // DUPLICATE (todo: confirm) +// { "bodythud1 ", ??? }, +// { "boneKLOK2 ", ??? }, +// { "boom10 ", ??? }, +// { "boom11 ", ??? }, +// { "boom15 ", ??? }, +// { "boxclik1a ", ??? }, +// { "brassbonk3", ??? }, +// { "carstart ", ??? }, +// { "cb tpt 2 ", ??? }, +// { "cell door ", ??? }, +// { "chains ", ??? }, +// { "crash ", ??? }, +// { "crsrt/idl3", ??? }, +// { "Fire Pit ", ??? }, // DUPLICATE (todo: confirm) +// { "Fzooom ", ??? }, +// { "Fzooom 2 ", ??? }, +// { "ghostwhosh", ??? }, +// { "glasssmash", ??? }, +// { "gloop2 ", ??? }, +// { "gunShotNea", ??? }, +// { "idoorclse ", ??? }, +// { "knife ", ??? }, +// { "lavacmbl4 ", ??? }, +// { "Mellow Str", ??? }, +// { "mtlheater1", ??? }, +// { "pachinko5 ", ??? }, +// { "Ping1 ", ??? }, +// { "rockcrunch", ??? }, +// { "rumble ", ??? }, // DUPLICATE (todo: confirm) +// { "runngwatr ", ??? }, +// { "scrape2 ", ??? }, +// { "snakeHiss ", ??? }, +// { "snort ", ??? }, // DUPLICATE (todo: confirm) +// { "spindle4 ", ??? }, +// { "splash2 ", ??? }, +// { "squirel ", ??? }, +// { "steam3 ", ??? }, +// { "stonwheel6", ??? }, +// { "street ", ??? }, +// { "trickle4 ", ??? } +}; + +const byte Instrument::_gmRhythmMap[35] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 36, 37, 38, 39, 40, 41, 66, 47, + 65, 48, 56}; + // This emulates the percussion bank setup LEC used with the MT-32, + // where notes 24 - 34 were assigned instruments without reverb. + // It also fixes problems on GS devices that map sounds to these + // notes by default. + +class Instrument_Program : public InstrumentInternal { +private: + byte _program; + bool _mt32; + +public: + Instrument_Program (byte program, bool mt32); + Instrument_Program (Serializer *s); + void saveOrLoad (Serializer *s); + void send (MidiChannel *mc); + void copy_to (Instrument *dest) { dest->program (_program, _mt32); } + bool is_valid() { + return (_program < 128) && + ((_native_mt32 == _mt32) || _native_mt32 + ? (MidiDriver::_gmToMt32[_program] < 128) + : (MidiDriver::_mt32ToGm[_program] < 128)); } +}; + +class Instrument_Adlib : public InstrumentInternal { +private: + struct { + byte flags_1; + byte oplvl_1; + byte atdec_1; + byte sustrel_1; + byte waveform_1; + byte flags_2; + byte oplvl_2; + byte atdec_2; + byte sustrel_2; + byte waveform_2; + byte feedback; + byte flags_a; + struct { byte a,b,c,d,e,f,g,h; } extra_a; + byte flags_b; + struct { byte a,b,c,d,e,f,g,h; } extra_b; + byte duration; + } _instrument; + +public: + Instrument_Adlib (byte *data); + Instrument_Adlib (Serializer *s); + void saveOrLoad (Serializer *s); + void send (MidiChannel *mc); + void copy_to (Instrument *dest) { dest->adlib ((byte *) &_instrument); } + bool is_valid() { return true; } +}; + +class Instrument_Roland : public InstrumentInternal { +private: + struct RolandInstrument { + byte roland_id; + byte device_id; + byte model_id; + byte command; + byte address[3]; + struct { + byte name[10]; + byte partial_struct12; + byte partial_struct34; + byte partial_mute; + byte env_mode; + } common; + struct { + byte wg_pitch_coarse; + byte wg_pitch_fine; + byte wg_pitch_keyfollow; + byte wg_pitch_bender_sw; + byte wg_waveform_pcm_bank; + byte wg_pcm_wave_num; + byte wg_pulse_width; + byte wg_pw_velo_sens; + byte p_env_depth; + byte p_evn_velo_sens; + byte p_env_time_keyf; + byte p_env_time[4]; + byte p_env_level[3]; + byte p_env_sustain_level; + byte end_level; + byte p_lfo_rate; + byte p_lfo_depth; + byte p_lfo_mod_sens; + byte tvf_cutoff_freq; + byte tvf_resonance; + byte tvf_keyfollow; + byte tvf_bias_point_dir; + byte tvf_bias_level; + byte tvf_env_depth; + byte tvf_env_velo_sens; + byte tvf_env_depth_keyf; + byte tvf_env_time_keyf; + byte tvf_env_time[5]; + byte tvf_env_level[3]; + byte tvf_env_sustain_level; + byte tva_level; + byte tva_velo_sens; + byte tva_bias_point_1; + byte tva_bias_level_1; + byte tva_bias_point_2; + byte tva_bias_level_2; + byte tva_env_time_keyf; + byte tva_env_time_v_follow; + byte tva_env_time[5]; + byte tva_env_level[3]; + byte tva_env_sustain_level; + } partial[4]; + byte checksum; + } GNUPACK; + RolandInstrument _instrument; + + char _instrument_name [11]; + + uint8 getEquivalentGM(); + +public: + Instrument_Roland (byte *data); + Instrument_Roland (Serializer *s); + void saveOrLoad (Serializer *s); + void send (MidiChannel *mc); + void copy_to (Instrument *dest) { dest->roland ((byte *) &_instrument); } + bool is_valid() { return (_native_mt32 ? true : (_instrument_name[0] != '\0')); } +}; + +//////////////////////////////////////// +// +// Instrument class members +// +//////////////////////////////////////// + +void Instrument::nativeMT32 (bool native) { + _native_mt32 = native; +} + +void Instrument::clear() { + if (_instrument) + delete _instrument; + _instrument = NULL; + _type = itNone; +} + +void Instrument::program (byte prog, bool mt32) { + clear(); + if (prog > 127) + return; + _type = itProgram; + _instrument = new Instrument_Program (prog, mt32); +} + +void Instrument::adlib (byte *instrument) { + clear(); + if (!instrument) + return; + _type = itAdlib; + _instrument = new Instrument_Adlib (instrument); +} + +void Instrument::roland (byte *instrument) { + clear(); + if (!instrument) + return; + _type = itRoland; + _instrument = new Instrument_Roland (instrument); +} + +void Instrument::saveOrLoad (Serializer *s) { + if (s->isSaving()) { + s->saveByte (_type); + if (_instrument) + _instrument->saveOrLoad (s); + } else { + clear(); + _type = s->loadByte(); + switch (_type) { + case itNone: + break; + case itProgram: + _instrument = new Instrument_Program (s); + break; + case itAdlib: + _instrument = new Instrument_Adlib (s); + break; + case itRoland: + _instrument = new Instrument_Roland (s); + break; + default: + warning ("No known instrument classification #%d", (int) _type); + _type = itNone; + } + } +} + +//////////////////////////////////////// +// +// Instrument_Program class members +// +//////////////////////////////////////// + +Instrument_Program::Instrument_Program (byte program, bool mt32) : +_program (program), +_mt32 (mt32) { + if (program > 127) + _program = 255; +} + +Instrument_Program::Instrument_Program (Serializer *s) { + _program = 255; + if (!s->isSaving()) + saveOrLoad (s); +} + +void Instrument_Program::saveOrLoad (Serializer *s) { + if (s->isSaving()) { + s->saveByte (_program); + s->saveByte (_mt32 ? 1 : 0); + } else { + _program = s->loadByte(); + _mt32 = (s->loadByte() > 0); + } +} + +void Instrument_Program::send (MidiChannel *mc) { + if (_program > 127) + return; + + byte program = _program; + if (_native_mt32 != _mt32) + program = _native_mt32 ? MidiDriver::_gmToMt32 [program] : MidiDriver::_mt32ToGm [program]; + if (program < 128) + mc->programChange (program); +} + +//////////////////////////////////////// +// +// Instrument_Adlib class members +// +//////////////////////////////////////// + +Instrument_Adlib::Instrument_Adlib (byte *data) { + memcpy (&_instrument, data, sizeof (_instrument)); +} + +Instrument_Adlib::Instrument_Adlib (Serializer *s) { + if (!s->isSaving()) + saveOrLoad (s); + else + memset (&_instrument, 0, sizeof (_instrument)); +} + +void Instrument_Adlib::saveOrLoad (Serializer *s) { + if (s->isSaving()) + s->saveBytes (&_instrument, sizeof (_instrument)); + else + s->loadBytes (&_instrument, sizeof (_instrument)); +} + +void Instrument_Adlib::send (MidiChannel *mc) { + mc->sysEx_customInstrument ('ADL ', (byte *) &_instrument); +} + +//////////////////////////////////////// +// +// Instrument_Roland class members +// +//////////////////////////////////////// + +Instrument_Roland::Instrument_Roland (byte *data) { + memcpy (&_instrument, data, sizeof (_instrument)); + memcpy (&_instrument_name, &_instrument.common.name, sizeof (_instrument.common.name)); + _instrument_name[10] = '\0'; + if (!_native_mt32 && getEquivalentGM() >= 128) { + debug (0, "MT-32 instrument \"%s\" not supported yet", _instrument_name); + _instrument_name[0] = '\0'; + } +} + +Instrument_Roland::Instrument_Roland (Serializer *s) { + _instrument_name[0] = '\0'; + if (!s->isSaving()) + saveOrLoad (s); + else + memset (&_instrument, 0, sizeof (_instrument)); +} + +void Instrument_Roland::saveOrLoad (Serializer *s) { + if (s->isSaving()) { + s->saveBytes (&_instrument, sizeof (_instrument)); + } else { + s->loadBytes (&_instrument, sizeof (_instrument)); + memcpy (&_instrument_name, &_instrument.common.name, sizeof (_instrument.common.name)); + _instrument_name[10] = '\0'; + if (!_native_mt32 && getEquivalentGM() >= 128) { + debug (2, "MT-32 custom instrument \"%s\" not supported", _instrument_name); + _instrument_name[0] = '\0'; + } + } // end if +} + +void Instrument_Roland::send (MidiChannel *mc) { + if (_native_mt32) { + if (mc->getNumber() > 8) + return; + _instrument.device_id = mc->getNumber(); + + // Remap instrument to appropriate address space. + int address = 0x008000; + _instrument.address[0] = (address >> 14) & 0x7F; + _instrument.address[1] = (address >> 7) & 0x7F; + _instrument.address[2] = (address ) & 0x7F; + + // Recompute the checksum. + byte checksum = 0; + byte *ptr = (byte *) &_instrument + 4; + int i; + for (i = 4; i < (int)sizeof (_instrument) - 1; ++i) + checksum -= *ptr++; + _instrument.checksum = checksum & 0x7F; + + mc->device()->sysEx ((byte *) &_instrument, sizeof (_instrument)); + } else { + // Convert to a GM program change. + byte program = getEquivalentGM(); + if (program < 128) + mc->programChange (program); + } +} + +uint8 Instrument_Roland::getEquivalentGM() { + byte i; + for (i = 0; i != ARRAYSIZE(roland_to_gm_map); ++i) { + if (!memcmp (roland_to_gm_map[i].name, _instrument.common.name, 10)) + return roland_to_gm_map[i].program; + } + return 255; +} + +} // End of namespace Scumm diff --git a/engines/scumm/imuse/instrument.h b/engines/scumm/imuse/instrument.h new file mode 100644 index 0000000000..eb1a30a1d1 --- /dev/null +++ b/engines/scumm/imuse/instrument.h @@ -0,0 +1,79 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2001 Ludvig Strigeus + * Copyright (C) 2001-2006 The ScummVM project + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef INSTRUMENT_H +#define INSTRUMENT_H + +#include "common/stdafx.h" +#include "common/scummsys.h" + +class MidiChannel; + +namespace Scumm { + +class Serializer; +class Instrument; + +class InstrumentInternal { +public: + virtual ~InstrumentInternal() {} + virtual void saveOrLoad (Serializer *s) = 0; + virtual void send (MidiChannel *mc) = 0; + virtual void copy_to (Instrument *dest) = 0; + virtual bool is_valid() = 0; + virtual operator int() { return 255; } +}; + +class Instrument { +private: + byte _type; + InstrumentInternal *_instrument; + +public: + enum { + itNone = 0, + itProgram = 1, + itAdlib = 2, + itRoland = 3 + }; + + Instrument() : _type (0), _instrument (0) { } + ~Instrument() { delete _instrument; } + static void nativeMT32 (bool native); + static const byte _gmRhythmMap[35]; + + void clear(); + void copy_to (Instrument *dest) { if (_instrument) _instrument->copy_to (dest); else dest->clear(); } + + void program (byte program, bool mt32); + void adlib (byte *instrument); + void roland (byte *instrument); + + byte getType() { return _type; } + bool isValid() { return (_instrument ? _instrument->is_valid() : false); } + void saveOrLoad (Serializer *s); + void send (MidiChannel *mc) { if (_instrument) _instrument->send (mc); } +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/imuse_internal.h b/engines/scumm/imuse_internal.h deleted file mode 100644 index aa9b49cb00..0000000000 --- a/engines/scumm/imuse_internal.h +++ /dev/null @@ -1,472 +0,0 @@ -/* ScummVM - Scumm Interpreter - * Copyright (C) 2001 Ludvig Strigeus - * Copyright (C) 2001-2006 The ScummVM project - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -#ifndef DEFINED_IMUSE_INTERNAL -#define DEFINED_IMUSE_INTERNAL - -#include "common/scummsys.h" -#include "scumm/instrument.h" -#include "scumm/saveload.h" -#include "sound/mididrv.h" - -class MidiParser; -class OSystem; - -namespace Scumm { - -// Unremark this statement to activate some of -// the most common iMuse diagnostic messages. -// #define IMUSE_DEBUG - -struct ParameterFader; -struct DeferredCommand; -struct ImTrigger; -struct SustainingNotes; -struct CommandQueue; -struct IsNoteCmdData; -class Player; -struct Part; -class IMuseInternal; - -// Some entities also referenced -class ScummEngine; - - - -////////////////////////////////////////////////// -// -// Some constants -// -////////////////////////////////////////////////// - -#define TICKS_PER_BEAT 480 - -#define TRIGGER_ID 0 -#define COMMAND_ID 1 - -#define MDPG_TAG "MDpg" - - -//////////////////////////////////////// -// -// Helper functions -// -//////////////////////////////////////// - -inline int clamp(int val, int min, int max) { - if (val < min) - return min; - if (val > max) - return max; - return val; -} - -inline int transpose_clamp(int a, int b, int c) { - if (b > a) - a += (b - a + 11) / 12 * 12; - if (c < a) - a -= (a - c + 11) / 12 * 12; - return a; -} - - - -////////////////////////////////////////////////// -// -// Entity declarations -// -////////////////////////////////////////////////// - -struct HookDatas { - byte _jump[2]; - byte _transpose; - byte _part_onoff[16]; - byte _part_volume[16]; - byte _part_program[16]; - byte _part_transpose[16]; - - int query_param(int param, byte chan); - int set(byte cls, byte value, byte chan); - HookDatas() { memset(this, 0, sizeof(HookDatas)); } -}; - -struct ParameterFader { - enum { - pfVolume = 1, - pfTranspose = 3, - pfSpeed = 4 - }; - - int param; - int start; - int end; - uint32 total_time; - uint32 current_time; - - ParameterFader() { param = 0; } - void init() { param = 0; } -}; - -struct DeferredCommand { - uint32 time_left; - int a, b, c, d, e, f; - DeferredCommand() { memset(this, 0, sizeof(DeferredCommand)); } -}; - -struct ImTrigger { - int sound; - byte id; - uint16 expire; - int command [8]; - ImTrigger() { memset(this, 0, sizeof(ImTrigger)); } -}; - -struct CommandQueue { - uint16 array[8]; - CommandQueue() { memset(this, 0, sizeof(CommandQueue)); } -}; - -class Player : public MidiDriver { -protected: - // Moved from IMuseInternal. - // This is only used by one player at a time. - static uint16 _active_notes[128]; - -protected: - MidiDriver *_midi; - MidiParser *_parser; - bool _passThrough; // Only respond to EOT, all else direct to MidiDriver - - Part *_parts; - bool _active; - bool _scanning; - int _id; - byte _priority; - byte _volume; - int8 _pan; - int8 _transpose; - int8 _detune; - byte _vol_eff; - - uint _track_index; - uint _loop_to_beat; - uint _loop_from_beat; - uint _loop_counter; - uint _loop_to_tick; - uint _loop_from_tick; - byte _speed; - bool _abort; - - // This does not get used by us! It is only - // here for save/load purposes, and gets - // passed on to the MidiParser during - // fixAfterLoad(). - uint32 _music_tick; - - HookDatas _hook; - ParameterFader _parameterFaders[4]; - - bool _isMT32; - bool _isMIDI; - -protected: - // Player part - void hook_clear(); - void uninit_parts(); - byte *parse_midi(byte *s); - void part_set_transpose(uint8 chan, byte relative, int8 b); - void parse_sysex(byte *p, uint len); - void maybe_jump(byte cmd, uint track, uint beat, uint tick); - void maybe_set_transpose(byte *data); - void maybe_part_onoff(byte *data); - void maybe_set_volume(byte *data); - void maybe_set_program(byte *data); - void maybe_set_transpose_part(byte *data); - void turn_off_pedals(); - int query_part_param(int param, byte chan); - void turn_off_parts(); - void play_active_notes(); - - void transitionParameters(); - - static void decode_sysex_bytes(const byte *src, byte *dst, int len); - - // Sequencer part - int start_seq_sound(int sound, bool reset_vars = true); - int query_param(int param); - -public: - IMuseInternal *_se; - uint _vol_chan; - -public: - Player(); - virtual ~Player(); - - int addParameterFader(int param, int target, int time); - void clear(); - void clearLoop(); - void fixAfterLoad(); - Part * getActivePart(uint8 part); - uint getBeatIndex(); - int8 getDetune() const { return _detune; } - byte getEffectiveVolume() const { return _vol_eff; } - int getID() const { return _id; } - MidiDriver *getMidiDriver() const { return _midi; } - int getParam(int param, byte chan); - int8 getPan() const { return _pan; } - Part * getPart(uint8 part); - byte getPriority() const { return _priority; } - uint getTicksPerBeat() const { return TICKS_PER_BEAT; } - int8 getTranspose() const { return _transpose; } - byte getVolume() const { return _volume; } - bool isActive() const { return _active; } - bool isFadingOut() const; - bool isMIDI() const { return _isMIDI; } - bool isMT32() const { return _isMT32; } - bool jump(uint track, uint beat, uint tick); - void onTimer(); - void removePart(Part *part); - int scan(uint totrack, uint tobeat, uint totick); - void saveLoadWithSerializer(Serializer *ser); - int setHook(byte cls, byte value, byte chan) { return _hook.set(cls, value, chan); } - void setDetune(int detune); - bool setLoop(uint count, uint tobeat, uint totick, uint frombeat, uint fromtick); - void setPan(int pan); - void setPriority(int pri); - void setSpeed(byte speed); - int setTranspose(byte relative, int b); - int setVolume(byte vol); - bool startSound(int sound, MidiDriver *midi, bool passThrough); - int getMusicTimer() const; - -public: - // MidiDriver interface - int open() { return 0; } - void close() { } - void send(uint32 b); - const char *getErrorName(int error_code) { return "Unknown"; } - void sysEx(byte *msg, uint16 length); - void metaEvent(byte type, byte *data, uint16 length); - void setTimerCallback(void *timer_param, void(*timer_proc)(void *)) { } - uint32 getBaseTempo(); - MidiChannel *allocateChannel() { return 0; } - MidiChannel *getPercussionChannel() { return 0; } -}; - -struct Part : public Serializable { - IMuseInternal *_se; - int _slot; - Part *_next, *_prev; - MidiChannel *_mc; - Player *_player; - int16 _pitchbend; - byte _pitchbend_factor; - int8 _transpose, _transpose_eff; - byte _vol, _vol_eff; - int8 _detune, _detune_eff; - int8 _pan, _pan_eff; - bool _on; - byte _modwheel; - bool _pedal; - int8 _pri; - byte _pri_eff; - byte _chan; - byte _effect_level; - byte _chorus; - byte _percussion; - byte _bank; - - // New abstract instrument definition - Instrument _instrument; - bool _unassigned_instrument; // For diagnostic reporting purposes only - - // MidiChannel interface - // (We don't currently derive from MidiChannel, - // but if we ever do, this will make it easy.) - void noteOff(byte note); - void noteOn(byte note, byte velocity); - void programChange(byte value); - void pitchBend(int16 value); - void modulationWheel(byte value); - void volume(byte value); - void pitchBendFactor(byte value); - void sustain(bool value); - void effectLevel(byte value); - void chorusLevel(byte value); - void allNotesOff(); - - void set_param(byte param, int value) { } - void init(); - void setup(Player *player); - void uninit(); - void off(); - void set_instrument(uint b); - void set_instrument(byte *data); - void load_global_instrument(byte b); - - void set_transpose(int8 transpose); - void set_detune(int8 detune); - void set_pri(int8 pri); - void set_pan(int8 pan); - - void set_onoff(bool on); - void fix_after_load(); - - void sendAll(); - void sendPitchBend(); - bool clearToTransmit(); - - Part(); - - void saveLoadWithSerializer(Serializer *ser); -}; - -// WARNING: This is the internal variant of the IMUSE class. -// imuse.h contains a public version of the same class. -// the public version, only contains a set of methods. -class IMuseInternal { - friend class Player; - friend struct Part; - -protected: - bool _native_mt32; - bool _enable_gs; - bool _sc55; - MidiDriver *_midi_adlib; - MidiDriver *_midi_native; - - byte **_base_sounds; - -protected: - bool _paused; - bool _initialized; - - int _tempoFactor; - - int _player_limit; // Limits how many simultaneous music tracks are played - bool _recycle_players; // Can we stop a player in order to start another one? - bool _direct_passthrough; // Pass data direct to MidiDriver (no interactivity) - - uint _queue_end, _queue_pos, _queue_sound; - byte _queue_adding; - - byte _queue_marker; - byte _queue_cleared; - byte _master_volume; // Master volume. 0-255 - byte _music_volume; // Global music volume. 0-255 - - uint16 _trigger_count; - ImTrigger _snm_triggers[16]; // Sam & Max triggers - uint16 _snm_trigger_index; - - uint16 _channel_volume[8]; - uint16 _channel_volume_eff[8]; // No Save - uint16 _volchan_table[8]; - - Player _players[8]; - Part _parts[32]; - - Instrument _global_adlib_instruments[32]; - CommandQueue _cmd_queue[64]; - DeferredCommand _deferredCommands[4]; - -protected: - byte *findStartOfSound(int sound); - bool isMT32(int sound); - bool isMIDI(int sound); - int get_queue_sound_status(int sound) const; - void handle_marker(uint id, byte data); - int get_channel_volume(uint a); - void initMidiDriver(MidiDriver *midi); - void initGM(MidiDriver *midi); - void initMT32(MidiDriver *midi); - void init_players(); - void init_parts(); - void init_queue(); - - void sequencer_timers(MidiDriver *midi); - - MidiDriver *getBestMidiDriver(int sound); - Player *allocate_player(byte priority); - Part *allocate_part(byte pri, MidiDriver *midi); - - int32 ImSetTrigger(int sound, int id, int a, int b, int c, int d, int e, int f, int g, int h); - int32 ImClearTrigger(int sound, int id); - int32 ImFireAllTriggers(int sound); - - void addDeferredCommand(int time, int a, int b, int c, int d, int e, int f); - void handleDeferredCommands(MidiDriver *midi); - - int enqueue_command(int a, int b, int c, int d, int e, int f, int g); - int enqueue_trigger(int sound, int marker); - int query_queue(int param); - Player *findActivePlayer(int id); - - int get_volchan_entry(uint a); - int set_volchan_entry(uint a, uint b); - int set_channel_volume(uint chan, uint vol); - void update_volumes(); - void reset_tick(); - - int set_volchan(int sound, int volchan); - - void fix_parts_after_load(); - void fix_players_after_load(ScummEngine *scumm); - - static void midiTimerCallback(void *data); - -public: - IMuseInternal(); - - int initialize(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver); - void reallocateMidiChannels(MidiDriver *midi); - void setGlobalAdlibInstrument(byte slot, byte *data); - void copyGlobalAdlibInstrument(byte slot, Instrument *dest); - bool isNativeMT32() { return _native_mt32; } - - // IMuse interface - - void on_timer(MidiDriver *midi); - void pause(bool paused); - int terminate1(); - int terminate2(); - int save_or_load(Serializer *ser, ScummEngine *scumm); - int setMusicVolume(uint vol); - int setImuseMasterVolume(uint vol); - bool startSound(int sound); - int stopSound(int sound); - int stopAllSounds(); - int getSoundStatus(int sound, bool ignoreFadeouts = true) const; - int getMusicTimer() const; - int32 doCommand (int a, int b, int c, int d, int e, int f, int g, int h); - int32 doCommand (int numargs, int args[]); - int clear_queue(); - void setBase(byte **base); - uint32 property(int prop, uint32 value); - - static IMuseInternal *create(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver); -}; - -} // End of namespace Scumm - -#endif diff --git a/engines/scumm/imuse_player.cpp b/engines/scumm/imuse_player.cpp deleted file mode 100644 index cf40044a9d..0000000000 --- a/engines/scumm/imuse_player.cpp +++ /dev/null @@ -1,1241 +0,0 @@ -/* ScummVM - Scumm Interpreter - * Copyright (C) 2001 Ludvig Strigeus - * Copyright (C) 2001-2006 The ScummVM project - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -#include "common/stdafx.h" - -#include "common/util.h" -#include "base/engine.h" - -#include "scumm/imuse_internal.h" -#include "scumm/saveload.h" -#include "scumm/scumm.h" - -#include "sound/midiparser.h" - -namespace Scumm { - -//////////////////////////////////////// -// -// Miscellaneous -// -//////////////////////////////////////// - -#define IMUSE_SYSEX_ID 0x7D -#define YM2612_SYSEX_ID 0x7C -#define ROLAND_SYSEX_ID 0x41 -#define PERCUSSION_CHANNEL 9 - -extern MidiParser *MidiParser_createRO(); -extern MidiParser *MidiParser_createEUP(); - -uint16 Player::_active_notes[128]; - - - -////////////////////////////////////////////////// -// -// IMuse Player implementation -// -////////////////////////////////////////////////// - -Player::Player() : - _midi(0), - _parser(0), - _passThrough(0), - _parts(0), - _active(false), - _scanning(false), - _id(0), - _priority(0), - _volume(0), - _pan(0), - _transpose(0), - _detune(0), - _vol_eff(0), - _track_index(0), - _loop_to_beat(0), - _loop_from_beat(0), - _loop_counter(0), - _loop_to_tick(0), - _loop_from_tick(0), - _speed(128), - _isMT32(false), - _isMIDI(false), - _se(0), - _vol_chan(0){ -} - -Player::~Player() { - if (_parser) { - delete _parser; - _parser = 0; - } -} - -bool Player::startSound(int sound, MidiDriver *midi, bool passThrough) { - void *ptr; - int i; - - // Not sure what the old code was doing, - // but we'll go ahead and do a similar check. - ptr = _se->findStartOfSound(sound); - if (!ptr) { - error("Player::startSound(): Couldn't find start of sound %d!", sound); - return false; - } - - _isMT32 = _se->isMT32(sound); - _isMIDI = _se->isMIDI(sound); - - _parts = NULL; - _active = true; - _midi = midi; - _id = sound; - _priority = 0x80; - _volume = 0x7F; - _vol_chan = 0xFFFF; - _vol_eff = (_se->get_channel_volume(0xFFFF) << 7) >> 7; - _pan = 0; - _transpose = 0; - _detune = 0; - _passThrough = passThrough; - - for (i = 0; i < ARRAYSIZE(_parameterFaders); ++i) - _parameterFaders[i].init(); - hook_clear(); - - if (start_seq_sound(sound) != 0) { - _active = false; - _midi = NULL; - return false; - } - -#ifdef IMUSE_DEBUG - debug(0, "Starting music %d", sound); -#endif - return true; -} - -int Player::getMusicTimer() const { - return _parser ? (_parser->getTick() * 2 / _parser->getPPQN()) : 0; -} - -bool Player::isFadingOut() const { - int i; - for (i = 0; i < ARRAYSIZE(_parameterFaders); ++i) { - if (_parameterFaders[i].param == ParameterFader::pfVolume && - _parameterFaders[i].end == 0) { - return true; - } - } - return false; -} - -void Player::clear() { - if (!_active) - return; - -#ifdef IMUSE_DEBUG - debug(0, "Stopping music %d", _id); -#endif - - if (_parser) { - _parser->unloadMusic(); - delete _parser; - _parser = 0; - } - uninit_parts(); - _se->ImFireAllTriggers(_id); - _active = false; - _midi = NULL; - _id = 0; -} - -void Player::hook_clear() { - memset(&_hook, 0, sizeof(_hook)); -} - -int Player::start_seq_sound(int sound, bool reset_vars) { - byte *ptr; - - if (reset_vars) { - _loop_to_beat = 1; - _loop_from_beat = 1; - _track_index = 0; - _loop_counter = 0; - _loop_to_tick = 0; - _loop_from_tick = 0; - } - - ptr = _se->findStartOfSound(sound); - if (ptr == NULL) - return -1; - if (_parser) - delete _parser; - - if (!memcmp(ptr, "RO", 2)) { - // Old style 'RO' resource - _parser = MidiParser_createRO(); - } else if (!memcmp(ptr, "SO", 2)) { - // Euphony (FM-TOWNS) resource - _parser = MidiParser_createEUP(); - } else if (!memcmp(ptr, "FORM", 4)) { - // Humongous Games XMIDI resource - _parser = MidiParser::createParser_XMIDI(); - } else { - // SCUMM SMF resource - _parser = MidiParser::createParser_SMF(); - } - - _parser->setMidiDriver(this); - _parser->property(MidiParser::mpSmartJump, 1); - _parser->loadMusic(ptr, 0); - _parser->setTrack(_track_index); - setSpeed(reset_vars ? 128 : _speed); - - return 0; -} - -void Player::uninit_parts() { - if (_parts && _parts->_player != this) - error("asd"); - while (_parts) - _parts->uninit(); - - // In case another player is waiting to allocate parts - if (_midi) - _se->reallocateMidiChannels(_midi); -} - -void Player::setSpeed(byte speed) { - _speed = speed; - if (_parser) - _parser->setTimerRate(((_midi->getBaseTempo() * speed) >> 7) * _se->_tempoFactor / 100); -} - -void Player::send(uint32 b) { - if (_passThrough) { - _midi->send(b); - return; - } - - byte cmd = (byte)(b & 0xF0); - byte chan = (byte)(b & 0x0F); - byte param1 = (byte)((b >> 8) & 0xFF); - byte param2 = (byte)((b >> 16) & 0xFF); - Part *part; - - switch (cmd >> 4) { - case 0x8: // Key Off - if (!_scanning) { - if ((part = getPart(chan)) != 0) - part->noteOff(param1); - } else { - _active_notes[param1] &= ~(1 << chan); - } - break; - - case 0x9: // Key On - if (!_scanning) { - if (_isMT32 && !_se->isNativeMT32()) - param2 = (((param2 * 3) >> 2) + 32) & 0x7F; - if ((part = getPart(chan)) != 0) - part->noteOn(param1, param2); - } else { - _active_notes[param1] |= (1 << chan); - } - break; - - case 0xB: // Control Change - part = (param1 == 123 ? getActivePart(chan) : getPart(chan)); - if (!part) - break; - - switch (param1) { - case 0: // Bank select. Not supported - break; - case 1: // Modulation Wheel - part->modulationWheel(param2); - break; - case 7: // Volume - part->volume(param2); - break; - case 10: // Pan Position - part->set_pan(param2 - 0x40); - break; - case 16: // Pitchbend Factor(non-standard) - part->pitchBendFactor(param2); - break; - case 17: // GP Slider 2 - part->set_detune(param2 - 0x40); - break; - case 18: // GP Slider 3 - part->set_pri(param2 - 0x40); - _se->reallocateMidiChannels(_midi); - break; - case 64: // Sustain Pedal - part->sustain(param2 != 0); - break; - case 91: // Effects Level - part->effectLevel(param2); - break; - case 93: // Chorus Level - part->chorusLevel(param2); - break; - case 116: // XMIDI For Loop. Not supported - // Used in the ending sequence of puttputt - break; - case 117: // XMIDI Next/Break. Not supported - // Used in the ending sequence of puttputt - break; - case 123: // All Notes Off - part->allNotesOff(); - break; - default: - error("Player::send(): Invalid control change %d", param1); - } - break; - - case 0xC: // Program Change - part = getPart(chan); - if (part) { - if (_isMIDI) { - if (param1 < 128) - part->programChange(param1); - } else { - if (param1 < 32) - part->load_global_instrument(param1); - } - } - break; - - case 0xE: // Pitch Bend - part = getPart(chan); - if (part) - part->pitchBend(((param2 << 7) | param1) - 0x2000); - break; - - case 0xA: // Aftertouch - case 0xD: // Channel Pressure - case 0xF: // Sequence Controls - break; - - default: - if (!_scanning) { - error("Player::send(): Invalid command %d", cmd); - clear(); - } - } - return; -} - -void Player::sysEx(byte *p, uint16 len) { - byte code; - byte a; - uint b; - byte buf[128]; - Part *part; - - if (_passThrough) { - _midi->sysEx(p, len); - return; - } - - // Check SysEx manufacturer. - a = *p++; - --len; - if (a != IMUSE_SYSEX_ID) { - if (a == ROLAND_SYSEX_ID) { - // Roland custom instrument definition. - part = getPart(p[0] & 0x0F); - if (part) { - part->_instrument.roland(p - 1); - if (part->clearToTransmit()) - part->_instrument.send(part->_mc); - } - } else if (a == YM2612_SYSEX_ID) { - // FM-TOWNS custom instrument definition - _midi->sysEx_customInstrument(p[0], 'EUP ', p + 1); - } else { - error("Unknown SysEx manufacturer 0x%02X", (int)a); - } - return; - } - --len; - - // Too big? - if (len >= sizeof(buf) * 2) - return; - -#ifdef IMUSE_DEBUG - if (!_scanning) { - for (a = 0; a < len + 1 && a < 19; ++a) { - sprintf((char *)&buf[a*3], " %02X", p[a]); - } // next for - if (a < len + 1) { - buf[a*3] = buf[a*3+1] = buf[a*3+2] = '.'; - ++a; - } // end if - buf[a*3] = '\0'; - debug(0, "[%02d] SysEx:%s", _id, buf); - } -#endif - - switch (code = *p++) { - case 0: - if (g_scumm->_game.id != GID_SAMNMAX) { - // There are 17 bytes of useful information beyond - // what we've read so far. All we know about them is - // as follows: - // BYTE 00: Channel # - // BYTE 02: BIT 01(0x01): Part on?(1 = yes) - // BYTE 04: Priority adjustment [guessing] - // BYTE 05: Volume(upper 4 bits) [guessing] - // BYTE 06: Volume(lower 4 bits) [guessing] - // BYTE 09: BIT 04(0x08): Percussion?(1 = yes) - // BYTE 15: Program(upper 4 bits) - // BYTE 16: Program(lower 4 bits) - part = getPart(p[0] & 0x0F); - if (part) { - part->set_onoff(p[2] & 0x01); - part->set_pri(p[4]); - part->volume((p[5] & 0x0F) << 4 |(p[6] & 0x0F)); - part->_percussion = _isMIDI ? ((p[9] & 0x08) > 0) : false; - if (part->_percussion) { - if (part->_mc) { - part->off(); - _se->reallocateMidiChannels(_midi); - } - } else { - // Even in cases where a program does not seem to be specified, - // i.e. bytes 15 and 16 are 0, we send a program change because - // 0 is a valid program number. MI2 tests show that in such - // cases, a regular program change message always seems to follow - // anyway. - if (_isMIDI) - part->_instrument.program((p[15] & 0x0F) << 4 |(p[16] & 0x0F), _isMT32); - part->sendAll(); - } - } - } else { - // Sam & Max: Trigger Event - // Triggers are set by doCommand(ImSetTrigger). - // When a SysEx marker is encountered whose sound - // ID and marker ID match what was set by ImSetTrigger, - // something magical is supposed to happen.... - for (a = 0; a < ARRAYSIZE(_se->_snm_triggers); ++a) { - if (_se->_snm_triggers[a].sound == _id && - _se->_snm_triggers[a].id == *p) - { - _se->_snm_triggers[a].sound = _se->_snm_triggers[a].id = 0; - _se->doCommand(8, _se->_snm_triggers[a].command); - break; - } - } - } // end if - break; - - case 1: - // This SysEx is used in Sam & Max for maybe_jump. - if (_scanning) - break; - maybe_jump(p[0], p[1] - 1, (READ_BE_UINT16(p + 2) - 1) * 4 + p[4], ((p[5] * TICKS_PER_BEAT) >> 2) + p[6]); - break; - - case 2: // Start of song. Ignore for now. - break; - - case 16: // Adlib instrument definition(Part) - a = *p++ & 0x0F; - ++p; // Skip hardware type - part = getPart(a); - if (part) { - if (len == 63) { - decode_sysex_bytes(p, buf, len - 3); - part->set_instrument((byte *)buf); - } else { - // SPK tracks have len == 49 here, and are not supported - part->programChange(254); // Must be invalid, but not 255 (which is reserved) - } - } - break; - - case 17: // Adlib instrument definition(Global) - p += 2; // Skip hardware type and... whatever came right before it - a = *p++; - decode_sysex_bytes(p, buf, len - 4); - _se->setGlobalAdlibInstrument(a, buf); - break; - - case 33: // Parameter adjust - a = *p++ & 0x0F; - ++p; // Skip hardware type - decode_sysex_bytes(p, buf, len - 3); - part = getPart(a); - if (part) - part->set_param(READ_BE_UINT16(buf), READ_BE_UINT16(buf + 2)); - break; - - case 48: // Hook - jump - if (_scanning) - break; - decode_sysex_bytes(p + 1, buf, len - 2); - maybe_jump(buf[0], READ_BE_UINT16(buf + 1), READ_BE_UINT16(buf + 3), READ_BE_UINT16(buf + 5)); - break; - - case 49: // Hook - global transpose - decode_sysex_bytes(p + 1, buf, len - 2); - maybe_set_transpose(buf); - break; - - case 50: // Hook - part on/off - buf[0] = *p++ & 0x0F; - decode_sysex_bytes(p, buf + 1, len - 2); - maybe_part_onoff(buf); - break; - - case 51: // Hook - set volume - buf[0] = *p++ & 0x0F; - decode_sysex_bytes(p, buf + 1, len - 2); - maybe_set_volume(buf); - break; - - case 52: // Hook - set program - buf[0] = *p++ & 0x0F; - decode_sysex_bytes(p, buf + 1, len - 2); - maybe_set_program(buf); - break; - - case 53: // Hook - set transpose - buf[0] = *p++ & 0x0F; - decode_sysex_bytes(p, buf + 1, len - 2); - maybe_set_transpose_part(buf); - break; - - case 64: // Marker - p++; - len -= 2; - while (len--) { - _se->handle_marker(_id, *p++); - } - break; - - case 80: // Loop - decode_sysex_bytes(p + 1, buf, len - 2); - setLoop(READ_BE_UINT16(buf), READ_BE_UINT16(buf + 2), - READ_BE_UINT16(buf + 4), READ_BE_UINT16(buf + 6), - READ_BE_UINT16(buf + 8)); - break; - - case 81: // End loop - clearLoop(); - break; - - case 96: // Set instrument - part = getPart(p[0] & 0x0F); - b = (p[1] & 0x0F) << 12 |(p[2] & 0x0F) << 8 |(p[4] & 0x0F) << 4 |(p[4] & 0x0F); - if (part) - part->set_instrument(b); - break; - - default: - error("Unknown SysEx command %d", (int)code); - } -} - -void Player::decode_sysex_bytes(const byte *src, byte *dst, int len) { - while (len >= 0) { - *dst++ = ((src[0] << 4)&0xFF) | (src[1] & 0xF); - src += 2; - len -= 2; - } -} - -void Player::maybe_jump(byte cmd, uint track, uint beat, uint tick) { - // Is this the hook I'm waiting for? - if (cmd && _hook._jump[0] != cmd) - return; - - // Reset hook? - if (cmd != 0 && cmd < 0x80) { - _hook._jump[0] = _hook._jump[1]; - _hook._jump[1] = 0; - } - - jump(track, beat, tick); -} - -void Player::maybe_set_transpose(byte *data) { - byte cmd; - - cmd = data[0]; - - // Is this the hook I'm waiting for? - if (cmd && _hook._transpose != cmd) - return; - - // Reset hook? - if (cmd != 0 && cmd < 0x80) - _hook._transpose = 0; - - setTranspose(data[1], (int8)data[2]); -} - -void Player::maybe_part_onoff(byte *data) { - byte cmd, *p; - uint chan; - Part *part; - - cmd = data[1]; - chan = data[0]; - - p = &_hook._part_onoff[chan]; - - // Is this the hook I'm waiting for? - if (cmd && *p != cmd) - return; - - if (cmd != 0 && cmd < 0x80) - *p = 0; - - part = getPart(chan); - if (part) - part->set_onoff(data[2] != 0); -} - -void Player::maybe_set_volume(byte *data) { - byte cmd; - byte *p; - uint chan; - Part *part; - - cmd = data[1]; - chan = data[0]; - - p = &_hook._part_volume[chan]; - - // Is this the hook I'm waiting for? - if (cmd && *p != cmd) - return; - - // Reset hook? - if (cmd != 0 && cmd < 0x80) - *p = 0; - - part = getPart(chan); - if (part) - part->volume(data[2]); -} - -void Player::maybe_set_program(byte *data) { - byte cmd; - byte *p; - uint chan; - Part *part; - - cmd = data[1]; - chan = data[0]; - - // Is this the hook I'm waiting for? - p = &_hook._part_program[chan]; - - if (cmd && *p != cmd) - return; - - if (cmd != 0 && cmd < 0x80) - *p = 0; - - part = getPart(chan); - if (part) - part->programChange(data[2]); -} - -void Player::maybe_set_transpose_part(byte *data) { - byte cmd; - byte *p; - uint chan; - - cmd = data[1]; - chan = data[0]; - - // Is this the hook I'm waiting for? - p = &_hook._part_transpose[chan]; - - if (cmd && *p != cmd) - return; - - // Reset hook? - if (cmd != 0 && cmd < 0x80) - *p = 0; - - part_set_transpose(chan, data[2], (int8)data[3]); -} - -int Player::setTranspose(byte relative, int b) { - Part *part; - - if (b > 24 || b < -24 || relative > 1) - return -1; - if (relative) - b = transpose_clamp(_transpose + b, -24, 24); - - _transpose = b; - - for (part = _parts; part; part = part->_next) { - part->set_transpose(part->_transpose); - } - - return 0; -} - -void Player::part_set_transpose(uint8 chan, byte relative, int8 b) { - Part *part; - - if (b > 24 || b < -24) - return; - - part = getPart(chan); - if (!part) - return; - if (relative) - b = transpose_clamp(b + part->_transpose, -7, 7); - part->set_transpose(b); -} - -bool Player::jump(uint track, uint beat, uint tick) { - if (!_parser) - return false; - if (_parser->setTrack(track)) - _track_index = track; - if (!_parser->jumpToTick((beat - 1) * TICKS_PER_BEAT + tick)) - return false; - turn_off_pedals(); - return true; -} - -bool Player::setLoop(uint count, uint tobeat, uint totick, uint frombeat, uint fromtick) { - if (tobeat + 1 >= frombeat) - return false; - - if (tobeat == 0) - tobeat = 1; - - _loop_counter = 0; // Because of possible interrupts - _loop_to_beat = tobeat; - _loop_to_tick = totick; - _loop_from_beat = frombeat; - _loop_from_tick = fromtick; - _loop_counter = count; - - return true; -} - -void Player::clearLoop() { - _loop_counter = 0; -} - -void Player::turn_off_pedals() { - Part *part; - - for (part = _parts; part; part = part->_next) { - if (part->_pedal) - part->sustain(false); - } -} - -Part *Player::getActivePart(uint8 chan) { - Part *part = _parts; - while (part) { - if (part->_chan == chan) - return part; - part = part->_next; - } - return 0; -} - -Part *Player::getPart(uint8 chan) { - Part *part = getActivePart(chan); - if (part) - return part; - - part = _se->allocate_part(_priority, _midi); - if (!part) { - debug(1, "No parts available"); - return NULL; - } - - // Insert part into front of parts list - part->_prev = NULL; - part->_next = _parts; - if (_parts) - _parts->_prev = part; - _parts = part; - - - part->_chan = chan; - part->setup(this); - - return part; -} - -void Player::setPriority(int pri) { - Part *part; - - _priority = pri; - for (part = _parts; part; part = part->_next) { - part->set_pri(part->_pri); - } - _se->reallocateMidiChannels(_midi); -} - -void Player::setPan(int pan) { - Part *part; - - _pan = pan; - for (part = _parts; part; part = part->_next) { - part->set_pan(part->_pan); - } -} - -void Player::setDetune(int detune) { - Part *part; - - _detune = detune; - for (part = _parts; part; part = part->_next) { - part->set_detune(part->_detune); - } -} - -int Player::scan(uint totrack, uint tobeat, uint totick) { - if (!_active || !_parser) - return -1; - - if (tobeat == 0) - tobeat++; - - turn_off_parts(); - memset(_active_notes, 0, sizeof(_active_notes)); - _scanning = true; - - // If the scan involves a track switch, scan to the end of - // the current track so that our state when starting the - // new track is fully up to date. - if (totrack != _track_index) - _parser->jumpToTick((uint32)-1, true); - _parser->setTrack(totrack); - if (!_parser->jumpToTick((tobeat - 1) * TICKS_PER_BEAT + totick, true)) { - _scanning = false; - return -1; - } - - _scanning = false; - _se->reallocateMidiChannels(_midi); - play_active_notes(); - - if (_track_index != totrack) { - _track_index = totrack; - _loop_counter = 0; - } - return 0; -} - -void Player::turn_off_parts() { - Part *part; - - for (part = _parts; part; part = part->_next) - part->off(); - _se->reallocateMidiChannels(_midi); -} - -void Player::play_active_notes() { - int i, j; - uint mask; - Part *part; - - for (i = 0; i < 16; ++i) { - part = getPart(i); - if (part) { - mask = 1 << i; - for (j = 0; j < 128; ++j) { - if (_active_notes[j] & mask) - part->noteOn(j, 80); - } - } - } -} - -int Player::setVolume(byte vol) { - Part *part; - - if (vol > 127) - return -1; - - _volume = vol; - _vol_eff = _se->get_channel_volume(_vol_chan) * (vol + 1) >> 7; - - for (part = _parts; part; part = part->_next) { - part->volume(part->_vol); - } - - return 0; -} - -int Player::getParam(int param, byte chan) { - switch (param) { - case 0: - return (byte)_priority; - case 1: - return (byte)_volume; - case 2: - return (byte)_pan; - case 3: - return (byte)_transpose; - case 4: - return (byte)_detune; - case 5: - return _speed; - case 6: - return _track_index; - case 7: - return getBeatIndex(); - case 8: - return (_parser ? _parser->getTick() % TICKS_PER_BEAT : 0); // _tick_index; - case 9: - return _loop_counter; - case 10: - return _loop_to_beat; - case 11: - return _loop_to_tick; - case 12: - return _loop_from_beat; - case 13: - return _loop_from_tick; - case 14: - case 15: - case 16: - case 17: - return query_part_param(param, chan); - case 18: - case 19: - case 20: - case 21: - case 22: - case 23: - return _hook.query_param(param, chan); - default: - return -1; - } -} - -int Player::query_part_param(int param, byte chan) { - Part *part; - - part = _parts; - while (part) { - if (part->_chan == chan) { - switch (param) { - case 14: - return part->_on; - case 15: - return part->_vol; - case 16: -// FIXME: Need to know where this occurs... -error("Trying to cast instrument (%d, %d) -- please tell Fingolfin\n", param, chan); -// In old versions of the code, this used to return part->_program. -// This was changed in revision 2.29 of imuse.cpp (where this code used -// to reside). -// return (int)part->_instrument; - case 17: - return part->_transpose; - default: - return -1; - } - } - part = part->_next; - } - return 129; -} - -void Player::onTimer() { - // First handle any parameter transitions - // that are occuring. - transitionParameters(); - - // Since the volume parameter can cause - // the player to be deactivated, check - // to make sure we're still active. - if (!_active || !_parser) - return; - - uint32 target_tick = _parser->getTick(); - uint beat_index = target_tick / TICKS_PER_BEAT + 1; - uint tick_index = target_tick % TICKS_PER_BEAT; - - if (_loop_counter &&(beat_index > _loop_from_beat || - (beat_index == _loop_from_beat && tick_index >= _loop_from_tick))) - { - _loop_counter--; - jump(_track_index, _loop_to_beat, _loop_to_tick); - } - _parser->onTimer(); -} - -// "time" is referenced as hundredths of a second. -// IS THAT CORRECT?? -// We convert it to microseconds before proceeding -int Player::addParameterFader(int param, int target, int time) { - int start; - - switch (param) { - case ParameterFader::pfVolume: - // HACK: If volume is set to 0 with 0 time, - // set it so immediately but DON'T clear - // the player. This fixes a problem with - // music being cleared inappropriately - // in S&M when playing with the Dinosaur. - if (!target && !time) { - setVolume(0); - return 0; - } - - // Volume fades are handled differently. - start = _volume; - break; - - case ParameterFader::pfTranspose: - // FIXME: Is this transpose? And what's the scale? - // It's set to fade to -2400 in the tunnel of love. -// debug(0, "parameterTransition(3) outside Tunnel of Love?"); - start = _transpose; -// target /= 200; - break; - - case ParameterFader::pfSpeed: // impSpeed - // FIXME: Is the speed from 0-100? - // Right now I convert it to 0-128. - start = _speed; -// target = target * 128 / 100; - break; - - case 127: - { // FIXME? I *think* this clears all parameter faders. - ParameterFader *ptr = &_parameterFaders[0]; - int i; - for (i = ARRAYSIZE(_parameterFaders); i; --i, ++ptr) - ptr->param = 0; - return 0; - } - break; - - default: - debug(0, "Player::addParameterFader (%d, %d, %d): Unknown parameter", param, target, time); - return 0; // Should be -1, but we'll let the script think it worked. - } - - ParameterFader *ptr = &_parameterFaders[0]; - ParameterFader *best = 0; - int i; - for (i = ARRAYSIZE(_parameterFaders); i; --i, ++ptr) { - if (ptr->param == param) { - best = ptr; - start = ptr->end; - break; - } else if (!ptr->param) { - best = ptr; - } - } - - if (best) { - best->param = param; - best->start = start; - best->end = target; - if (!time) - best->total_time = 1; - else - best->total_time = (uint32)time * 10000; - best->current_time = 0; - } else { - debug(0, "IMuse Player %d: Out of parameter faders", _id); - return -1; - } - - return 0; -} - -void Player::transitionParameters() { - uint32 advance = _midi->getBaseTempo(); - int value; - - ParameterFader *ptr = &_parameterFaders[0]; - int i; - for (i = ARRAYSIZE(_parameterFaders); i; --i, ++ptr) { - if (!ptr->param) - continue; - - ptr->current_time += advance; - if (ptr->current_time > ptr->total_time) - ptr->current_time = ptr->total_time; - value = (int32)ptr->start + (int32)(ptr->end - ptr->start) * (int32)ptr->current_time / (int32)ptr->total_time; - - switch (ptr->param) { - case ParameterFader::pfVolume: - // Volume. - if (!value && !ptr->end) { - clear(); - return; - } - setVolume((byte)value); - break; - - case ParameterFader::pfTranspose: - // FIXME: Is this really transpose? - setTranspose(0, value / 100); - setDetune(value % 100); - break; - - case ParameterFader::pfSpeed: // impSpeed: - // Speed. - setSpeed((byte)value); - break; - - default: - ptr->param = 0; - } - - if (ptr->current_time >= ptr->total_time) - ptr->param = 0; - } -} - -uint Player::getBeatIndex() { - return (_parser ? (_parser->getTick() / TICKS_PER_BEAT + 1) : 0); -} - -void Player::removePart(Part *part) { - // Unlink - if (part->_next) - part->_next->_prev = part->_prev; - if (part->_prev) - part->_prev->_next = part->_next; - else - _parts = part->_next; - part->_next = part->_prev = 0; -} - -void Player::fixAfterLoad() { - _midi = _se->getBestMidiDriver(_id); - if (!_midi) { - clear(); - } else { - start_seq_sound(_id, false); - setSpeed(_speed); - if (_parser) - _parser->jumpToTick(_music_tick); // start_seq_sound already switched tracks - _isMT32 = _se->isMT32(_id); - _isMIDI = _se->isMIDI(_id); - } -} - -uint32 Player::getBaseTempo() { - return (_midi ? _midi->getBaseTempo() : 0); -} - -void Player::metaEvent(byte type, byte *msg, uint16 len) { - if (type == 0x2F) - clear(); -} - - - -//////////////////////////////////////// -// -// Player save/load functions -// -//////////////////////////////////////// - -void Player::saveLoadWithSerializer(Serializer *ser) { - static const SaveLoadEntry playerEntries[] = { - MKLINE(Player, _active, sleByte, VER(8)), - MKLINE(Player, _id, sleUint16, VER(8)), - MKLINE(Player, _priority, sleByte, VER(8)), - MKLINE(Player, _volume, sleByte, VER(8)), - MKLINE(Player, _pan, sleInt8, VER(8)), - MKLINE(Player, _transpose, sleByte, VER(8)), - MKLINE(Player, _detune, sleInt8, VER(8)), - MKLINE(Player, _vol_chan, sleUint16, VER(8)), - MKLINE(Player, _vol_eff, sleByte, VER(8)), - MKLINE(Player, _speed, sleByte, VER(8)), - MK_OBSOLETE(Player, _song_index, sleUint16, VER(8), VER(19)), - MKLINE(Player, _track_index, sleUint16, VER(8)), - MK_OBSOLETE(Player, _timer_counter, sleUint16, VER(8), VER(17)), - MKLINE(Player, _loop_to_beat, sleUint16, VER(8)), - MKLINE(Player, _loop_from_beat, sleUint16, VER(8)), - MKLINE(Player, _loop_counter, sleUint16, VER(8)), - MKLINE(Player, _loop_to_tick, sleUint16, VER(8)), - MKLINE(Player, _loop_from_tick, sleUint16, VER(8)), - MK_OBSOLETE(Player, _tempo, sleUint32, VER(8), VER(19)), - MK_OBSOLETE(Player, _cur_pos, sleUint32, VER(8), VER(17)), - MK_OBSOLETE(Player, _next_pos, sleUint32, VER(8), VER(17)), - MK_OBSOLETE(Player, _song_offset, sleUint32, VER(8), VER(17)), - MK_OBSOLETE(Player, _tick_index, sleUint16, VER(8), VER(17)), - MK_OBSOLETE(Player, _beat_index, sleUint16, VER(8), VER(17)), - MK_OBSOLETE(Player, _ticks_per_beat, sleUint16, VER(8), VER(17)), - MKLINE(Player, _music_tick, sleUint32, VER(19)), - MKLINE(Player, _hook._jump[0], sleByte, VER(8)), - MKLINE(Player, _hook._transpose, sleByte, VER(8)), - MKARRAY(Player, _hook._part_onoff[0], sleByte, 16, VER(8)), - MKARRAY(Player, _hook._part_volume[0], sleByte, 16, VER(8)), - MKARRAY(Player, _hook._part_program[0], sleByte, 16, VER(8)), - MKARRAY(Player, _hook._part_transpose[0], sleByte, 16, VER(8)), - MKEND() - }; - - const SaveLoadEntry parameterFaderEntries[] = { - MKLINE(ParameterFader, param, sleInt16, VER(17)), - MKLINE(ParameterFader, start, sleInt16, VER(17)), - MKLINE(ParameterFader, end, sleInt16, VER(17)), - MKLINE(ParameterFader, total_time, sleUint32, VER(17)), - MKLINE(ParameterFader, current_time, sleUint32, VER(17)), - MKEND() - }; - - if (!ser->isSaving() && _parser) { - delete _parser; - _parser = 0; - } - _music_tick = _parser ? _parser->getTick() : 0; - - int num; - if (ser->isSaving()) { - num = (_parts ? (_parts - _se->_parts + 1) : 0); - ser->saveUint16(num); - } else { - num = ser->loadUint16(); - _parts = (num ? &_se->_parts[num - 1] : 0); - } - ser->saveLoadEntries(this, playerEntries); - ser->saveLoadArrayOf(_parameterFaders, ARRAYSIZE(_parameterFaders), - sizeof(ParameterFader), parameterFaderEntries); - return; -} - -} // End of namespace Scumm diff --git a/engines/scumm/input.cpp b/engines/scumm/input.cpp index 8b8dc75df1..b2e27d15fd 100644 --- a/engines/scumm/input.cpp +++ b/engines/scumm/input.cpp @@ -32,7 +32,7 @@ #include "scumm/debugger.h" #include "scumm/dialogs.h" #include "scumm/insane/insane.h" -#include "scumm/imuse.h" +#include "scumm/imuse/imuse.h" #ifndef DISABLE_HE #include "scumm/he/intern_he.h" #include "scumm/he/logic_he.h" diff --git a/engines/scumm/insane/insane.cpp b/engines/scumm/insane/insane.cpp index 6e769153c6..87b61e4367 100644 --- a/engines/scumm/insane/insane.cpp +++ b/engines/scumm/insane/insane.cpp @@ -31,7 +31,7 @@ #include "scumm/actor.h" #include "scumm/sound.h" -#include "scumm/imuse.h" +#include "scumm/imuse/imuse.h" #include "scumm/imuse_digi/dimuse.h" #include "scumm/smush/smush_player.h" diff --git a/engines/scumm/instrument.cpp b/engines/scumm/instrument.cpp deleted file mode 100644 index 6e688d89e5..0000000000 --- a/engines/scumm/instrument.cpp +++ /dev/null @@ -1,462 +0,0 @@ -/* ScummVM - Scumm Interpreter - * Copyright (C) 2001 Ludvig Strigeus - * Copyright (C) 2001-2006 The ScummVM project - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -#include "common/stdafx.h" -#include "scumm/scumm.h" -#include "scumm/saveload.h" -#include "scumm/instrument.h" -#include "sound/mididrv.h" - -namespace Scumm { - -static bool _native_mt32 = false; - -static struct { - const char *name; - byte program; -} - -roland_to_gm_map [] = { - // Monkey Island 2 instruments - // TODO: Complete - { "badspit ", 62 }, - { "Big Drum ", 116 }, - { "burp ", 58 }, -// { "dinkfall ", ??? }, -// { "Fire Pit ", ??? }, - { "foghorn ", 60 }, - { "glop ", 39 }, -// { "jacob's la", ??? }, - { "LeshBass ", 33 }, -// { "lowsnort ", ??? }, - { "ML explosn", 127 }, - { "ReggaeBass", 32 }, -// { "rope fall ", ??? }, - { "rumble ", 89 }, - { "SdTrk Bend", 97 }, -// { "snort ", ??? }, - { "spitting ", 62 }, - { "Swell 1 ", 95 }, - { "Swell 2 ", 95 }, - { "thnderclap", 127 } - - // Fate of Atlantis instruments - // TODO: Build -// { "*aah! ", ??? }, -// { "*ooh! ", ??? }, -// { "*ShotFar4 ", ??? }, -// { "*splash3 ", ??? }, -// { "*torpedo5 ", ??? }, -// { "*whip3 ", ??? }, -// { "*woodknock", ??? }, -// { "35 lavabub", ??? }, -// { "49 bzzt! ", ??? }, -// { "applause ", ??? }, -// { "Arabongo ", ??? }, -// { "Big Drum ", ??? }, // DUPLICATE (todo: confirm) -// { "bodythud1 ", ??? }, -// { "boneKLOK2 ", ??? }, -// { "boom10 ", ??? }, -// { "boom11 ", ??? }, -// { "boom15 ", ??? }, -// { "boxclik1a ", ??? }, -// { "brassbonk3", ??? }, -// { "carstart ", ??? }, -// { "cb tpt 2 ", ??? }, -// { "cell door ", ??? }, -// { "chains ", ??? }, -// { "crash ", ??? }, -// { "crsrt/idl3", ??? }, -// { "Fire Pit ", ??? }, // DUPLICATE (todo: confirm) -// { "Fzooom ", ??? }, -// { "Fzooom 2 ", ??? }, -// { "ghostwhosh", ??? }, -// { "glasssmash", ??? }, -// { "gloop2 ", ??? }, -// { "gunShotNea", ??? }, -// { "idoorclse ", ??? }, -// { "knife ", ??? }, -// { "lavacmbl4 ", ??? }, -// { "Mellow Str", ??? }, -// { "mtlheater1", ??? }, -// { "pachinko5 ", ??? }, -// { "Ping1 ", ??? }, -// { "rockcrunch", ??? }, -// { "rumble ", ??? }, // DUPLICATE (todo: confirm) -// { "runngwatr ", ??? }, -// { "scrape2 ", ??? }, -// { "snakeHiss ", ??? }, -// { "snort ", ??? }, // DUPLICATE (todo: confirm) -// { "spindle4 ", ??? }, -// { "splash2 ", ??? }, -// { "squirel ", ??? }, -// { "steam3 ", ??? }, -// { "stonwheel6", ??? }, -// { "street ", ??? }, -// { "trickle4 ", ??? } -}; - -const byte Instrument::_gmRhythmMap[35] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 36, 37, 38, 39, 40, 41, 66, 47, - 65, 48, 56}; - // This emulates the percussion bank setup LEC used with the MT-32, - // where notes 24 - 34 were assigned instruments without reverb. - // It also fixes problems on GS devices that map sounds to these - // notes by default. - -class Instrument_Program : public InstrumentInternal { -private: - byte _program; - bool _mt32; - -public: - Instrument_Program (byte program, bool mt32); - Instrument_Program (Serializer *s); - void saveOrLoad (Serializer *s); - void send (MidiChannel *mc); - void copy_to (Instrument *dest) { dest->program (_program, _mt32); } - bool is_valid() { - return (_program < 128) && - ((_native_mt32 == _mt32) || _native_mt32 - ? (MidiDriver::_gmToMt32[_program] < 128) - : (MidiDriver::_mt32ToGm[_program] < 128)); } -}; - -class Instrument_Adlib : public InstrumentInternal { -private: - struct { - byte flags_1; - byte oplvl_1; - byte atdec_1; - byte sustrel_1; - byte waveform_1; - byte flags_2; - byte oplvl_2; - byte atdec_2; - byte sustrel_2; - byte waveform_2; - byte feedback; - byte flags_a; - struct { byte a,b,c,d,e,f,g,h; } extra_a; - byte flags_b; - struct { byte a,b,c,d,e,f,g,h; } extra_b; - byte duration; - } _instrument; - -public: - Instrument_Adlib (byte *data); - Instrument_Adlib (Serializer *s); - void saveOrLoad (Serializer *s); - void send (MidiChannel *mc); - void copy_to (Instrument *dest) { dest->adlib ((byte *) &_instrument); } - bool is_valid() { return true; } -}; - -class Instrument_Roland : public InstrumentInternal { -private: - struct RolandInstrument { - byte roland_id; - byte device_id; - byte model_id; - byte command; - byte address[3]; - struct { - byte name[10]; - byte partial_struct12; - byte partial_struct34; - byte partial_mute; - byte env_mode; - } common; - struct { - byte wg_pitch_coarse; - byte wg_pitch_fine; - byte wg_pitch_keyfollow; - byte wg_pitch_bender_sw; - byte wg_waveform_pcm_bank; - byte wg_pcm_wave_num; - byte wg_pulse_width; - byte wg_pw_velo_sens; - byte p_env_depth; - byte p_evn_velo_sens; - byte p_env_time_keyf; - byte p_env_time[4]; - byte p_env_level[3]; - byte p_env_sustain_level; - byte end_level; - byte p_lfo_rate; - byte p_lfo_depth; - byte p_lfo_mod_sens; - byte tvf_cutoff_freq; - byte tvf_resonance; - byte tvf_keyfollow; - byte tvf_bias_point_dir; - byte tvf_bias_level; - byte tvf_env_depth; - byte tvf_env_velo_sens; - byte tvf_env_depth_keyf; - byte tvf_env_time_keyf; - byte tvf_env_time[5]; - byte tvf_env_level[3]; - byte tvf_env_sustain_level; - byte tva_level; - byte tva_velo_sens; - byte tva_bias_point_1; - byte tva_bias_level_1; - byte tva_bias_point_2; - byte tva_bias_level_2; - byte tva_env_time_keyf; - byte tva_env_time_v_follow; - byte tva_env_time[5]; - byte tva_env_level[3]; - byte tva_env_sustain_level; - } partial[4]; - byte checksum; - } GNUPACK; - RolandInstrument _instrument; - - char _instrument_name [11]; - - uint8 getEquivalentGM(); - -public: - Instrument_Roland (byte *data); - Instrument_Roland (Serializer *s); - void saveOrLoad (Serializer *s); - void send (MidiChannel *mc); - void copy_to (Instrument *dest) { dest->roland ((byte *) &_instrument); } - bool is_valid() { return (_native_mt32 ? true : (_instrument_name[0] != '\0')); } -}; - -//////////////////////////////////////// -// -// Instrument class members -// -//////////////////////////////////////// - -void Instrument::nativeMT32 (bool native) { - _native_mt32 = native; -} - -void Instrument::clear() { - if (_instrument) - delete _instrument; - _instrument = NULL; - _type = itNone; -} - -void Instrument::program (byte prog, bool mt32) { - clear(); - if (prog > 127) - return; - _type = itProgram; - _instrument = new Instrument_Program (prog, mt32); -} - -void Instrument::adlib (byte *instrument) { - clear(); - if (!instrument) - return; - _type = itAdlib; - _instrument = new Instrument_Adlib (instrument); -} - -void Instrument::roland (byte *instrument) { - clear(); - if (!instrument) - return; - _type = itRoland; - _instrument = new Instrument_Roland (instrument); -} - -void Instrument::saveOrLoad (Serializer *s) { - if (s->isSaving()) { - s->saveByte (_type); - if (_instrument) - _instrument->saveOrLoad (s); - } else { - clear(); - _type = s->loadByte(); - switch (_type) { - case itNone: - break; - case itProgram: - _instrument = new Instrument_Program (s); - break; - case itAdlib: - _instrument = new Instrument_Adlib (s); - break; - case itRoland: - _instrument = new Instrument_Roland (s); - break; - default: - warning ("No known instrument classification #%d", (int) _type); - _type = itNone; - } - } -} - -//////////////////////////////////////// -// -// Instrument_Program class members -// -//////////////////////////////////////// - -Instrument_Program::Instrument_Program (byte program, bool mt32) : -_program (program), -_mt32 (mt32) { - if (program > 127) - _program = 255; -} - -Instrument_Program::Instrument_Program (Serializer *s) { - _program = 255; - if (!s->isSaving()) - saveOrLoad (s); -} - -void Instrument_Program::saveOrLoad (Serializer *s) { - if (s->isSaving()) { - s->saveByte (_program); - s->saveByte (_mt32 ? 1 : 0); - } else { - _program = s->loadByte(); - _mt32 = (s->loadByte() > 0); - } -} - -void Instrument_Program::send (MidiChannel *mc) { - if (_program > 127) - return; - - byte program = _program; - if (_native_mt32 != _mt32) - program = _native_mt32 ? MidiDriver::_gmToMt32 [program] : MidiDriver::_mt32ToGm [program]; - if (program < 128) - mc->programChange (program); -} - -//////////////////////////////////////// -// -// Instrument_Adlib class members -// -//////////////////////////////////////// - -Instrument_Adlib::Instrument_Adlib (byte *data) { - memcpy (&_instrument, data, sizeof (_instrument)); -} - -Instrument_Adlib::Instrument_Adlib (Serializer *s) { - if (!s->isSaving()) - saveOrLoad (s); - else - memset (&_instrument, 0, sizeof (_instrument)); -} - -void Instrument_Adlib::saveOrLoad (Serializer *s) { - if (s->isSaving()) - s->saveBytes (&_instrument, sizeof (_instrument)); - else - s->loadBytes (&_instrument, sizeof (_instrument)); -} - -void Instrument_Adlib::send (MidiChannel *mc) { - mc->sysEx_customInstrument ('ADL ', (byte *) &_instrument); -} - -//////////////////////////////////////// -// -// Instrument_Roland class members -// -//////////////////////////////////////// - -Instrument_Roland::Instrument_Roland (byte *data) { - memcpy (&_instrument, data, sizeof (_instrument)); - memcpy (&_instrument_name, &_instrument.common.name, sizeof (_instrument.common.name)); - _instrument_name[10] = '\0'; - if (!_native_mt32 && getEquivalentGM() >= 128) { - debug (0, "MT-32 instrument \"%s\" not supported yet", _instrument_name); - _instrument_name[0] = '\0'; - } -} - -Instrument_Roland::Instrument_Roland (Serializer *s) { - _instrument_name[0] = '\0'; - if (!s->isSaving()) - saveOrLoad (s); - else - memset (&_instrument, 0, sizeof (_instrument)); -} - -void Instrument_Roland::saveOrLoad (Serializer *s) { - if (s->isSaving()) { - s->saveBytes (&_instrument, sizeof (_instrument)); - } else { - s->loadBytes (&_instrument, sizeof (_instrument)); - memcpy (&_instrument_name, &_instrument.common.name, sizeof (_instrument.common.name)); - _instrument_name[10] = '\0'; - if (!_native_mt32 && getEquivalentGM() >= 128) { - debug (2, "MT-32 custom instrument \"%s\" not supported", _instrument_name); - _instrument_name[0] = '\0'; - } - } // end if -} - -void Instrument_Roland::send (MidiChannel *mc) { - if (_native_mt32) { - if (mc->getNumber() > 8) - return; - _instrument.device_id = mc->getNumber(); - - // Remap instrument to appropriate address space. - int address = 0x008000; - _instrument.address[0] = (address >> 14) & 0x7F; - _instrument.address[1] = (address >> 7) & 0x7F; - _instrument.address[2] = (address ) & 0x7F; - - // Recompute the checksum. - byte checksum = 0; - byte *ptr = (byte *) &_instrument + 4; - int i; - for (i = 4; i < (int)sizeof (_instrument) - 1; ++i) - checksum -= *ptr++; - _instrument.checksum = checksum & 0x7F; - - mc->device()->sysEx ((byte *) &_instrument, sizeof (_instrument)); - } else { - // Convert to a GM program change. - byte program = getEquivalentGM(); - if (program < 128) - mc->programChange (program); - } -} - -uint8 Instrument_Roland::getEquivalentGM() { - byte i; - for (i = 0; i != ARRAYSIZE(roland_to_gm_map); ++i) { - if (!memcmp (roland_to_gm_map[i].name, _instrument.common.name, 10)) - return roland_to_gm_map[i].program; - } - return 255; -} - -} // End of namespace Scumm diff --git a/engines/scumm/instrument.h b/engines/scumm/instrument.h deleted file mode 100644 index eb1a30a1d1..0000000000 --- a/engines/scumm/instrument.h +++ /dev/null @@ -1,79 +0,0 @@ -/* ScummVM - Scumm Interpreter - * Copyright (C) 2001 Ludvig Strigeus - * Copyright (C) 2001-2006 The ScummVM project - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - */ - -#ifndef INSTRUMENT_H -#define INSTRUMENT_H - -#include "common/stdafx.h" -#include "common/scummsys.h" - -class MidiChannel; - -namespace Scumm { - -class Serializer; -class Instrument; - -class InstrumentInternal { -public: - virtual ~InstrumentInternal() {} - virtual void saveOrLoad (Serializer *s) = 0; - virtual void send (MidiChannel *mc) = 0; - virtual void copy_to (Instrument *dest) = 0; - virtual bool is_valid() = 0; - virtual operator int() { return 255; } -}; - -class Instrument { -private: - byte _type; - InstrumentInternal *_instrument; - -public: - enum { - itNone = 0, - itProgram = 1, - itAdlib = 2, - itRoland = 3 - }; - - Instrument() : _type (0), _instrument (0) { } - ~Instrument() { delete _instrument; } - static void nativeMT32 (bool native); - static const byte _gmRhythmMap[35]; - - void clear(); - void copy_to (Instrument *dest) { if (_instrument) _instrument->copy_to (dest); else dest->clear(); } - - void program (byte program, bool mt32); - void adlib (byte *instrument); - void roland (byte *instrument); - - byte getType() { return _type; } - bool isValid() { return (_instrument ? _instrument->is_valid() : false); } - void saveOrLoad (Serializer *s); - void send (MidiChannel *mc) { if (_instrument) _instrument->send (mc); } -}; - -} // End of namespace Scumm - -#endif diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk index 97b4323af6..48421458e0 100644 --- a/engines/scumm/module.mk +++ b/engines/scumm/module.mk @@ -13,42 +13,42 @@ MODULE_OBJS := \ debugger.o \ dialogs.o \ gfx.o \ - imuse.o \ - imuse_player.o \ - input.o \ - instrument.o \ + he/script_v60he.o \ + he/sound_he.o \ help.o \ - midiparser_ro.o \ + imuse/imuse_player.o \ + imuse/imuse.o \ + imuse/instrument.o \ + input.o \ midiparser_eup.o \ + midiparser_ro.o \ object.o \ palette.o \ player_mod.o \ - player_v1.o \ player_nes.o \ + player_v1.o \ player_v2.o \ player_v2a.o \ player_v3a.o \ - resource.o \ resource_v2.o \ resource_v3.o \ resource_v4.o \ + resource.o \ room.o \ saveload.o \ - script.o \ script_c64.o \ script_v2.o \ script_v5.o \ script_v6.o \ - he/script_v60he.o \ + script.o \ scumm.o \ sound.o \ - he/sound_he.o \ string.o \ + thumbnail.o \ usage_bits.o \ util.o \ vars.o \ - verbs.o \ - thumbnail.o + verbs.o ifndef DISABLE_SCUMM_7_8 MODULE_OBJS += \ @@ -95,6 +95,8 @@ endif MODULE_DIRS += \ engines/scumm \ + engines/scumm/he \ + engines/scumm/imuse \ engines/scumm/imuse_digi \ engines/scumm/insane \ engines/scumm/smush diff --git a/engines/scumm/resource.cpp b/engines/scumm/resource.cpp index c42e451522..c4e606a9c4 100644 --- a/engines/scumm/resource.cpp +++ b/engines/scumm/resource.cpp @@ -26,7 +26,7 @@ #include "scumm/charset.h" #include "scumm/dialogs.h" -#include "scumm/imuse.h" +#include "scumm/imuse/imuse.h" #include "scumm/imuse_digi/dimuse.h" #include "scumm/intern.h" #ifndef DISABLE_HE diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp index 2fe48e585e..23307d64ec 100644 --- a/engines/scumm/saveload.cpp +++ b/engines/scumm/saveload.cpp @@ -30,7 +30,7 @@ #include "scumm/actor.h" #include "scumm/charset.h" #include "scumm/imuse_digi/dimuse.h" -#include "scumm/imuse.h" +#include "scumm/imuse/imuse.h" #include "scumm/intern.h" #include "scumm/he/intern_he.h" #include "scumm/object.h" diff --git a/engines/scumm/script_v6.cpp b/engines/scumm/script_v6.cpp index 21dfefd76f..6cf6e008f4 100644 --- a/engines/scumm/script_v6.cpp +++ b/engines/scumm/script_v6.cpp @@ -27,7 +27,7 @@ #include "scumm/actor.h" #include "scumm/charset.h" -#include "scumm/imuse.h" +#include "scumm/imuse/imuse.h" #include "scumm/imuse_digi/dimuse.h" #include "scumm/insane/insane.h" #include "scumm/intern.h" diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index a67b6e76f4..5c393733cb 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -40,7 +40,7 @@ #include "scumm/costume.h" #include "scumm/debugger.h" #include "scumm/dialogs.h" -#include "scumm/imuse.h" +#include "scumm/imuse/imuse.h" #include "scumm/imuse_digi/dimuse.h" #include "scumm/insane/insane.h" #include "scumm/intern.h" diff --git a/engines/scumm/smush/smush_mixer.cpp b/engines/scumm/smush/smush_mixer.cpp index 00a50321da..63ac6e445c 100644 --- a/engines/scumm/smush/smush_mixer.cpp +++ b/engines/scumm/smush/smush_mixer.cpp @@ -27,7 +27,7 @@ #include "scumm/smush/channel.h" #include "scumm/scumm.h" #include "scumm/sound.h" -#include "scumm/imuse.h" +#include "scumm/imuse/imuse.h" #include "sound/mixer.h" diff --git a/engines/scumm/smush/smush_player.cpp b/engines/scumm/smush/smush_player.cpp index 89fe8998d7..740aeb0239 100644 --- a/engines/scumm/smush/smush_player.cpp +++ b/engines/scumm/smush/smush_player.cpp @@ -32,7 +32,7 @@ #include "scumm/bomp.h" #include "scumm/imuse_digi/dimuse.h" -#include "scumm/imuse.h" +#include "scumm/imuse/imuse.h" #include "scumm/scumm.h" #include "scumm/sound.h" #include "scumm/smush/channel.h" diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index 78ed585f72..b46b2c94f9 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -23,7 +23,7 @@ #include "common/stdafx.h" #include "scumm/actor.h" -#include "scumm/imuse.h" +#include "scumm/imuse/imuse.h" #include "scumm/imuse_digi/dimuse.h" #include "scumm/scumm.h" #include "scumm/sound.h" -- cgit v1.2.3