diff options
Diffstat (limited to 'scumm/imuse.cpp')
-rw-r--r-- | scumm/imuse.cpp | 2043 |
1 files changed, 0 insertions, 2043 deletions
diff --git a/scumm/imuse.cpp b/scumm/imuse.cpp deleted file mode 100644 index 5d170e98b8..0000000000 --- a/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->_gameId == 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->_gameId != 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->_gameId != 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->_gameId == 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->_gameId == 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 |