aboutsummaryrefslogtreecommitdiff
path: root/scumm/imuse.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'scumm/imuse.cpp')
-rw-r--r--scumm/imuse.cpp2043
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