diff options
-rw-r--r-- | engines/scumm/imuse/imuse.cpp | 1232 | ||||
-rw-r--r-- | engines/scumm/imuse/imuse.h | 52 | ||||
-rw-r--r-- | engines/scumm/imuse/imuse_internal.h | 95 | ||||
-rw-r--r-- | engines/scumm/imuse/imuse_player.cpp | 2 | ||||
-rw-r--r-- | engines/scumm/scumm.cpp | 1 | ||||
-rw-r--r-- | engines/scumm/sound.cpp | 5 |
6 files changed, 716 insertions, 671 deletions
diff --git a/engines/scumm/imuse/imuse.cpp b/engines/scumm/imuse/imuse.cpp index bed0a7a45d..89871cc7c0 100644 --- a/engines/scumm/imuse/imuse.cpp +++ b/engines/scumm/imuse/imuse.cpp @@ -215,84 +215,6 @@ MidiDriver *IMuseInternal::getBestMidiDriver(int sound) { 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,true)) - return false; - } - - ptr = findStartOfSound(sound); - if (!ptr) { - debug(2, "IMuseInternal::startSound(): Couldn't find sound %d!", sound); - return false; - } - - // Check which MIDI driver this track should use. - // If it's NULL, it ain't something we can play. - MidiDriver *driver = getBestMidiDriver(sound); - if (!driver) - return false; - - // If the requested sound is already playing, start it over - // from scratch. This was originally a hack to prevent Sam & Max - // iMuse messiness while upgrading the iMuse engine, but it - // is apparently necessary to deal with fade-and-restart - // race conditions that were observed in MI2. Reference - // Bug #590511 and Patch #607175 (which was reversed to fix - // an FOA regression: Bug #622606). - player = findActivePlayer(sound); - if (!player) - player = allocate_player(128); - if (!player) - return false; - - // HACK: This is to work around a problem at the Dino Bungie Memorial. - // There are three pieces of music involved here: - // - // 80 - Main theme (looping) - // 81 - Music when entering Rex's and Wally's room (not looping) - // 82 - Music when listening to Rex or Wally - // - // When entering, tune 81 starts, tune 80 is faded down (not out) and - // a trigger is set in tune 81 to fade tune 80 back up. - // - // When listening to Rex or Wally, tune 82 is started, tune 81 is faded - // out and tune 80 is faded down even further. - // - // However, when tune 81 is faded out its trigger will cause tune 80 to - // fade back up, resulting in two tunes being played simultaneously at - // full blast. It's no use trying to keep tune 81 playing at volume 0. - // It doesn't loop, so eventually it will terminate on its own. - // - // I don't know how the original interpreter handled this - or even if - // it handled it at all - but it looks like sloppy scripting to me. Our - // workaround is to clear the trigger if the player listens to Rex or - // Wally before tune 81 has finished on its own. - - if (g_scumm->_game.id == GID_SAMNMAX && sound == 82 && getSoundStatus(81, false)) - ImClearTrigger(81, 1); - - player->clear(); - return player->startSound(sound, driver, _direct_passthrough); -} - - Player *IMuseInternal::allocate_player(byte priority) { Player *player = _players, *best = NULL; int i; @@ -335,28 +257,14 @@ void IMuseInternal::init_parts() { } } -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; -} +//////////////////////////////////////// +// +// IMuse mixin interface methods +// +//////////////////////////////////////// void IMuseInternal::on_timer(MidiDriver *midi) { + Common::StackLock lock(_mutex, "IMuseInternal::on_timer()"); if (_paused || !_initialized) return; @@ -365,259 +273,200 @@ void IMuseInternal::on_timer(MidiDriver *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) +void IMuseInternal::pause(bool paused) { + Common::StackLock lock(_mutex, "IMuseInternal::pause()"); + if (_paused == paused) return; + int vol = _music_volume; + if (paused) + _music_volume = 0; + update_volumes(); + _music_volume = vol; - // 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); + // 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); } - 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; + _paused = paused; +} - doCommand(p[0], p[1], p[2], p[3], p[4], p[5], p[6], 0); +int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm) { + Common::StackLock lock(_mutex, "IMuseInternal::save_or_load()"); + 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() + }; - if (_queue_cleared) - return; - pos = _queue_end; - } while (1); + const SaveLoadEntry cmdQueueEntries[] = { + MKARRAY(CommandQueue, array[0], sleUint16, 8, VER(23)), + MKEND() + }; - _queue_end = pos; -} + // 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() + }; -int IMuseInternal::get_channel_volume(uint a) { - if (a < 8) - return _channel_volume_eff[a]; - return (_master_volume * _music_volume / 255) / 2; -} + 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() + }; -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; - } - } + 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); - if (best) { - best->uninit(); - reallocateMidiChannels(midi); - } else { - debug(1, "Denying part request"); - } - return best; -} + // The players + for (i = 0; i < ARRAYSIZE(_players); ++i) + _players[i].saveLoadWithSerializer(ser); -int IMuseInternal::getSoundStatus(int sound, bool ignoreFadeouts) const { - int i; - const Player *player = _players; + // The parts + for (i = 0; i < ARRAYSIZE(_parts); ++i) + _parts[i].saveLoadWithSerializer(ser); - 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; + { // 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(); } } - 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; + // 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); - 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); - } + if (ser->isLoading()) { + // Load all sounds that we need + fix_players_after_load(scumm); + fix_parts_after_load(); + setImuseMasterVolume(_master_volume); - for (i = 0; i < ARRAYSIZE (_deferredCommands); ++i) { - if (_deferredCommands[i].time_left && _deferredCommands[i].a == 8 && - _deferredCommands[i].b == sound) { - return 2; - } + if (_midi_native) + reallocateMidiChannels(_midi_native); + if (_midi_adlib) + reallocateMidiChannels(_midi_adlib); } 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; +bool IMuseInternal::get_sound_active(int sound) const { + Common::StackLock lock(_mutex, "IMuseInternal::get_sound_active()"); + return getSoundStatus_internal (sound, false); +} - 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; - } +int32 IMuseInternal::doCommand(int numargs, int a[]) { + Common::StackLock lock(_mutex, "IMuseInternal::doCommand()"); + return doCommand_internal (numargs, a); } -int IMuseInternal::clear_queue() { - _queue_adding = false; - _queue_cleared = true; - _queue_pos = 0; - _queue_end = 0; - _trigger_count = 0; - return 0; +void IMuseInternal::setBase(byte **base) { + Common::StackLock lock(_mutex, "IMuseInternal::setBase()"); + _base_sounds = base; } -int IMuseInternal::enqueue_command(int a, int b, int c, int d, int e, int f, int g) { - uint16 *p; - uint i; +uint32 IMuseInternal::property(int prop, uint32 value) { + Common::StackLock lock(_mutex, "IMuseInternal::property()"); + 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; - i = _queue_pos; + case IMuse::PROP_NATIVE_MT32: + _native_mt32 = (value > 0); + Instrument::nativeMT32(_native_mt32); + if (_midi_native && _native_mt32) + initMT32(_midi_native); + break; - if (i == _queue_end) - return -1; + case IMuse::PROP_GS: + _enable_gs = (value > 0); - if (a == -1) { - _queue_adding = false; - _trigger_count++; - return 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; - 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; + case IMuse::PROP_LIMIT_PLAYERS: + if (value > 0 && value <= ARRAYSIZE(_players)) + _player_limit = (int)value; + break; - i = (i + 1) % ARRAYSIZE(_cmd_queue); + case IMuse::PROP_RECYCLE_PLAYERS: + _recycle_players = (value != 0); + break; - if (_queue_end != i) { - _queue_pos = i; - return 0; - } else { - _queue_pos = (i - 1) % ARRAYSIZE(_cmd_queue); - return -1; - } -} + case IMuse::PROP_DIRECT_PASSTHROUGH: + _direct_passthrough = (value != 0); + break; -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; + case IMuse::PROP_GAME_ID: + _game_id = value; + break; } + + return 0; } -int IMuseInternal::setMusicVolume(uint vol) { +//////////////////////////////////////// +// +// MusicEngine interface methods +// +//////////////////////////////////////// + +void IMuseInternal::setMusicVolume (int vol) { + Common::StackLock lock(_mutex, "IMuseInternal::setMusicVolume()"); if (vol > 255) vol = 255; if (_music_volume == vol) - return 0; + return; _music_volume = vol; vol = _master_volume * _music_volume / 255; for (uint i = 0; i < ARRAYSIZE(_channel_volume); i++) { @@ -625,34 +474,53 @@ int IMuseInternal::setMusicVolume(uint vol) { } 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; +void IMuseInternal::startSound(int sound) { + Common::StackLock lock(_mutex, "IMuseInternal::startSound()"); + startSound_internal (sound); } -int IMuseInternal::terminate1() { - _initialized = false; - stopAllSounds(); - return 0; +void IMuseInternal::stopSound(int sound) { + Common::StackLock lock(_mutex, "IMuseInternal::stopSound()"); + stopSound_internal (sound); +} + +void IMuseInternal::stopAllSounds() { + Common::StackLock lock(_mutex, "IMuseInternal::stopAllSounds()"); + stopAllSounds_internal(); +} + +int IMuseInternal::getSoundStatus (int sound) const { + Common::StackLock lock(_mutex, "IMuseInternal::getSoundStatus()"); + return getSoundStatus_internal (sound, true); +} + +int IMuseInternal::getMusicTimer() const { + Common::StackLock lock(_mutex, "IMuseInternal::getMusicTimer()"); + int best_time = 0; + const Player *player = _players; + for (int i = ARRAYSIZE(_players); i; i--, player++) { + if (player->isActive()) { + int timer = player->getMusicTimer(); + if (timer > best_time) + best_time = timer; + } + } + return best_time; } -// This is the stuff that has to be done -// outside the monitor's mutex, otherwise -// a deadlock occurs. -int IMuseInternal::terminate2() { +void IMuseInternal::terminate() { + // Do just enough stuff inside the mutex to + // make sure any MIDI timing threads won't + // interrupt us, and then do the rest outside + // the mutex. + { + Common::StackLock lock(_mutex, "IMuseInternal::terminate()"); + _initialized = false; + stopAllSounds_internal(); + } + if (_midi_adlib) { _midi_adlib->close(); delete _midi_adlib; @@ -663,42 +531,135 @@ int IMuseInternal::terminate2() { if (_native_mt32) { // Reset the MT-32 _midi_native->sysEx((const byte *) "\x41\x10\x16\x12\x7f\x00\x00\x01\x00", 9); - g_system->delayMillis(250); + _system->delayMillis(250); } _midi_native->close(); delete _midi_native; _midi_native = 0; } - - return 0; } -int IMuseInternal::enqueue_trigger(int sound, int marker) { - uint16 *p; - uint pos; +//////////////////////////////////////// +// +// Internal versions of the IMuse and +// MusicEngine base class methods. +// These methods assume the appropriate +// mutex locks have already been set, +// and may also have slightly different +// semantics than the interface methods. +// +//////////////////////////////////////// - pos = _queue_pos; +bool IMuseInternal::startSound_internal (int sound) { + // 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. - p = _cmd_queue[pos].array; - p[0] = TRIGGER_ID; - p[1] = sound; - p[2] = marker; + 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_internal (trigger->sound,true)) + return false; + } - pos = (pos + 1) % ARRAYSIZE(_cmd_queue); - if (_queue_end == pos) { - _queue_pos = (pos - 1) % ARRAYSIZE(_cmd_queue); - return -1; + void *ptr = findStartOfSound(sound); + if (!ptr) { + debug(2, "IMuseInternal::startSound(): Couldn't find sound %d!", sound); + return false; } - _queue_pos = pos; - _queue_adding = true; - _queue_sound = sound; - _queue_marker = marker; + // 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 *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 (_game_id == GID_SAMNMAX && sound == 82 && getSoundStatus_internal (81, false)) + ImClearTrigger(81, 1); + + player->clear(); + return player->startSound(sound, driver, _direct_passthrough); +} + +int IMuseInternal::stopSound_internal (int sound) { + int r = -1; + Player *player = findActivePlayer(sound); + if (player) { + player->clear(); + r = 0; + } + return r; +} + +int IMuseInternal::stopAllSounds_internal() { + Player *player = _players; + for (int i = ARRAYSIZE(_players); i; i--, player++) { + if (player->isActive()) + player->clear(); + } return 0; } -int32 IMuseInternal::doCommand(int a, int b, int c, int d, int e, int f, int g, int h) { +int IMuseInternal::getSoundStatus_internal (int sound, bool ignoreFadeouts) const { + const Player *player = _players; + for (int i = ARRAYSIZE(_players); i; 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); +} + +int32 IMuseInternal::doCommand_internal + (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; @@ -708,14 +669,14 @@ int32 IMuseInternal::doCommand(int a, int b, int c, int d, int e, int f, int g, args[5] = f; args[6] = g; args[7] = h; - return doCommand(8, args); + return doCommand_internal (8, args); } -int32 IMuseInternal::doCommand(int numargs, int a[]) { - int i; - +int32 IMuseInternal::doCommand_internal (int numargs, int a[]) { if (numargs < 1) return -1; + + int i; byte cmd = a[0] & 0xFF; byte param = a[0] >> 8; Player *player = NULL; @@ -744,17 +705,17 @@ int32 IMuseInternal::doCommand(int numargs, int a[]) { 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; + return startSound_internal (a[1]) ? 0 : -1; case 9: - return stopSound(a[1]); + return stopSound_internal (a[1]); case 10: // FIXME: Sam and Max - Not sure if this is correct - return stopAllSounds(); + return stopAllSounds_internal(); case 11: - return stopAllSounds(); + return stopAllSounds_internal(); case 12: // Sam & Max: Player-scope commands player = findActivePlayer(a[1]); - if (!player) + if (player == NULL) return -1; switch (a[3]) { @@ -766,7 +727,7 @@ int32 IMuseInternal::doCommand(int numargs, int a[]) { } return -1; case 13: - return getSoundStatus(a[1], true); + return getSoundStatus_internal (a[1], true); case 14: // Sam and Max: Parameter fade player = findActivePlayer(a[1]); @@ -786,7 +747,7 @@ int32 IMuseInternal::doCommand(int numargs, int a[]) { debug(0, "IMuse doCommand(16) - set_volchan (%d, %d)", a[1], a[2]); return set_volchan(a[1], a[2]); case 17: - if (g_scumm->_game.id != GID_SAMNMAX) { + if (_game_id != GID_SAMNMAX) { debug(0, "IMuse doCommand(17) - set_channel_volume (%d, %d)", a[1], a[2]); return set_channel_volume(a[1], a[2]); } else { @@ -801,7 +762,7 @@ int32 IMuseInternal::doCommand(int numargs, int a[]) { } } case 18: - if (g_scumm->_game.id != GID_SAMNMAX) { + if (_game_id != GID_SAMNMAX) { return set_volchan_entry(a[1], a[2]); } else { // Sam & Max: ImCheckTrigger. @@ -849,7 +810,7 @@ int32 IMuseInternal::doCommand(int numargs, int a[]) { switch (cmd) { case 0: - if (g_scumm->_game.id == GID_SAMNMAX) { + if (_game_id == GID_SAMNMAX) { if (a[3] == 1) // Measure number return ((player->getBeatIndex() - 1) >> 2) + 1; else if (a[3] == 2) // Beat number @@ -859,7 +820,7 @@ int32 IMuseInternal::doCommand(int numargs, int a[]) { return player->getParam(a[2], a[3]); } case 1: - if (g_scumm->_game.id == GID_SAMNMAX) { + if (_game_id == GID_SAMNMAX) { // FIXME: Could someone verify this? // // This jump instruction is known to be used in @@ -946,6 +907,265 @@ int32 IMuseInternal::doCommand(int numargs, int a[]) { return -1; } +// mixin end + +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_internal (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::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::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::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::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 @@ -1003,8 +1223,8 @@ int32 IMuseInternal::ImSetTrigger(int sound, int id, int a, int b, int c, int d, // 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],true) && getSoundStatus(sound,true)) - stopSound(trig->command[1]); + if (trig->command[0] == 8 && getSoundStatus_internal (trig->command[1],true) && getSoundStatus_internal (sound,true)) + stopSound_internal (trig->command[1]); return 0; } @@ -1029,7 +1249,7 @@ int32 IMuseInternal::ImFireAllTriggers(int sound) { 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); + doCommand_internal (8, _snm_triggers[i].command); ++count; } } @@ -1141,57 +1361,6 @@ int IMuseInternal::get_volchan_entry(uint 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); @@ -1201,12 +1370,19 @@ IMuseInternal *IMuseInternal::create(OSystem *syst, MidiDriver *nativeMidiDriver int IMuseInternal::initialize(OSystem *syst, MidiDriver *native_midi, MidiDriver *adlib_midi) { int i; + _system = syst; _midi_native = native_midi; _midi_adlib = adlib_midi; - if (native_midi != NULL) - initMidiDriver(native_midi); - if (adlib_midi != NULL) - initMidiDriver(adlib_midi); + if (native_midi != NULL) { + _timer_info_native.imuse = this; + _timer_info_native.driver = native_midi; + initMidiDriver (&_timer_info_native); + } + if (adlib_midi != NULL) { + _timer_info_adlib.imuse = this; + _timer_info_adlib.driver = adlib_midi; + initMidiDriver (&_timer_info_adlib); + } if (!_tempoFactor) _tempoFactor = 100; @@ -1224,14 +1400,14 @@ int IMuseInternal::initialize(OSystem *syst, MidiDriver *native_midi, MidiDriver return 0; } -void IMuseInternal::initMidiDriver(MidiDriver *midi) { +void IMuseInternal::initMidiDriver (TimerCallbackInfo *info) { // Open MIDI driver - int result = midi->open(); + int result = info->driver->open(); if (result) error("IMuse initialization - %s", MidiDriver::getErrorName(result)); // Connect to the driver's timer - midi->setTimerCallback(midi, &IMuseInternal::midiTimerCallback); + info->driver->setTimerCallback (info, &IMuseInternal::midiTimerCallback); } void IMuseInternal::initMT32(MidiDriver *midi) { @@ -1241,16 +1417,16 @@ void IMuseInternal::initMT32(MidiDriver *midi) { // Reset the MT-32 midi->sysEx((const byte *) "\x41\x10\x16\x12\x7f\x00\x00\x01\x00", 9); - g_system->delayMillis(250); + _system->delayMillis(250); // Setup master tune, reverb mode, reverb time, reverb level, // channel mapping, partial reserve and master volume midi->sysEx((const byte *) "\x41\x10\x16\x12\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", 31); - g_system->delayMillis(250); + _system->delayMillis(250); // Map percussion to notes 24 - 34 without reverb midi->sysEx((const byte *) "\x41\x10\x16\x12\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", 52); - g_system->delayMillis(250); + _system->delayMillis(250); // Compute version string (truncated to 20 chars max.) strcat(info, gScummVMVersion); @@ -1267,7 +1443,7 @@ void IMuseInternal::initMT32(MidiDriver *midi) { checksum -= buffer[i]; buffer[27] = checksum & 0x7F; midi->sysEx(buffer, 28); - g_system->delayMillis(1000); + _system->delayMillis(1000); } void IMuseInternal::initGM(MidiDriver *midi) { @@ -1279,7 +1455,7 @@ void IMuseInternal::initGM(MidiDriver *midi) { 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); + _system->delayMillis(200); if (_enable_gs) { @@ -1297,7 +1473,7 @@ void IMuseInternal::initGM(MidiDriver *midi) { memcpy(&buffer[5], "\x40\x00\x7F\x00\x41\xF7", 6); midi->sysEx(buffer, 11); debug(2, "GS SysEx: GS Reset"); - g_system->delayMillis(200); + _system->delayMillis(200); if (_sc55) { // This mode is for GS devices that support an MT-32-compatible @@ -1419,29 +1595,6 @@ void IMuseInternal::init_queue() { _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(); @@ -1451,7 +1604,7 @@ void IMuseInternal::handleDeferredCommands(MidiDriver *midi) { 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); + doCommand_internal (ptr->a, ptr->b, ptr->c, ptr->d, ptr->e, ptr->f, 0, 0); ptr->time_left = advance; } ptr->time_left -= advance; @@ -1480,102 +1633,6 @@ void IMuseInternal::addDeferredCommand(int time, int a, int b, int c, int d, int } } -//////////////////////////////////////////////////////////// -// -// 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; @@ -1606,10 +1663,9 @@ void IMuseInternal::fix_players_after_load(ScummEngine *scumm) { // //////////////////////////////////////// -void IMuseInternal::midiTimerCallback(void *data) { - MidiDriver *driver = (MidiDriver *)data; - if (g_scumm->_imuse) - g_scumm->_imuse->on_timer(driver); +void IMuseInternal::midiTimerCallback (void *data) { + TimerCallbackInfo *info = (TimerCallbackInfo *) data; + info->imuse->on_timer (info->driver); } void IMuseInternal::reallocateMidiChannels(MidiDriver *midi) { @@ -1666,60 +1722,20 @@ void IMuseInternal::copyGlobalAdlibInstrument(byte slot, Instrument *dest) { _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. + + +/** + * IMuseInternal factory creation method. + * This method provides a means for creating an IMuse + * implementation without requiring that the details + * of that implementation be exposed to the client + * through a header file. This allows the internals + * of the implementation to be changed and updated + * without requiring a recompile of the client code. + */ IMuse *IMuse::create(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver) { IMuseInternal *engine = IMuseInternal::create(syst, nativeMidiDriver, adlibMidiDriver); - return new IMuse(syst, engine); + return engine; } } // End of namespace Scumm diff --git a/engines/scumm/imuse/imuse.h b/engines/scumm/imuse/imuse.h index 790deb5dc5..4f25d4b897 100644 --- a/engines/scumm/imuse/imuse.h +++ b/engines/scumm/imuse/imuse.h @@ -37,47 +37,41 @@ class IMuseInternal; class ScummEngine; class Serializer; +/** + * iMuse implementation interface. + * MusicEngine derivative for state-tracked, interactive, + * persistent event-based music playback and control. + * This class serves as an interface to actual implementations + * so that client code is not exposed to the details of + * any specific implementation. + */ class IMuse : public MusicEngine { -private: - OSystem *_system; - IMuseInternal *_target; - mutable Common::MutexRef _mutex; - - IMuse(OSystem *system, IMuseInternal *target); - void in() const; - void out() const; - public: - ~IMuse(); - enum { PROP_TEMPO_BASE, PROP_NATIVE_MT32, PROP_GS, PROP_LIMIT_PLAYERS, PROP_RECYCLE_PLAYERS, - PROP_DIRECT_PASSTHROUGH + PROP_DIRECT_PASSTHROUGH, + PROP_GAME_ID }; - void on_timer(MidiDriver *midi); - void pause(bool paused); - int save_or_load(Serializer *ser, ScummEngine *scumm); - bool get_sound_active(int sound) const; - int32 doCommand(int a, int b, int c, int d, int e, int f, int g, int h); - int32 doCommand(int numargs, int args[]); - int clear_queue(); - void setBase(byte **base); - uint32 property(int prop, uint32 value); +public: + virtual void on_timer(MidiDriver *midi) = 0; + virtual void pause(bool paused) = 0; + virtual int save_or_load(Serializer *ser, ScummEngine *scumm) = 0; + virtual bool get_sound_active(int sound) const = 0; + virtual int32 doCommand(int numargs, int args[]) = 0; + virtual int clear_queue() = 0; + virtual void setBase(byte **base) = 0; + virtual uint32 property(int prop, uint32 value) = 0; - // MusicEngine base class methods - virtual void setMusicVolume(int vol); - virtual void startSound(int sound); - virtual void stopSound(int sound); - virtual void stopAllSounds(); - virtual int getSoundStatus(int sound) const; - virtual int getMusicTimer() const; - virtual void terminate(); +public: + // MusicEngine base class methods. + // Not actually redefined here because none are implemented. +public: // Factory methods static IMuse *create(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver); }; diff --git a/engines/scumm/imuse/imuse_internal.h b/engines/scumm/imuse/imuse_internal.h index 8bd19cee7b..f90ee87601 100644 --- a/engines/scumm/imuse/imuse_internal.h +++ b/engines/scumm/imuse/imuse_internal.h @@ -24,6 +24,7 @@ #define DEFINED_IMUSE_INTERNAL #include "common/scummsys.h" +#include "scumm/imuse/imuse.h" #include "scumm/imuse/instrument.h" #include "scumm/saveload.h" #include "sound/mididrv.h" @@ -33,10 +34,6 @@ class OSystem; namespace Scumm { -// Unremark this statement to activate some of -// the most common iMuse diagnostic messages. -// #define IMUSE_DEBUG - struct ParameterFader; struct DeferredCommand; struct ImTrigger; @@ -96,6 +93,11 @@ inline int transpose_clamp(int a, int b, int c) { // ////////////////////////////////////////////////// +struct TimerCallbackInfo { + IMuseInternal *imuse; + MidiDriver *driver; +}; + struct HookDatas { byte _jump[2]; byte _transpose; @@ -145,6 +147,14 @@ struct CommandQueue { CommandQueue() { memset(this, 0, sizeof(CommandQueue)); } }; + + +////////////////////////////////////////////////// +// +// Player class definition +// +////////////////////////////////////////////////// + class Player : public MidiDriver { protected: // Moved from IMuseInternal. @@ -273,6 +283,14 @@ public: MidiChannel *getPercussionChannel() { return 0; } }; + + +////////////////////////////////////////////////// +// +// Part pseudo-class definition +// +////////////////////////////////////////////////// + struct Part : public Serializable { IMuseInternal *_se; int _slot; @@ -341,10 +359,13 @@ struct Part : public Serializable { void saveLoadWithSerializer(Serializer *ser); }; -// WARNING: This is the internal variant of the IMUSE class. -// imuse.h contains a public version of the same class. -// the public version, only contains a set of methods. -class IMuseInternal { + + +/** + * SCUMM implementation of IMuse. + * This class implements the IMuse mixin interface for the SCUMM environment. + */ +class IMuseInternal : public IMuse { friend class Player; friend struct Part; @@ -354,9 +375,15 @@ protected: bool _sc55; MidiDriver *_midi_adlib; MidiDriver *_midi_native; + TimerCallbackInfo _timer_info_adlib; + TimerCallbackInfo _timer_info_native; + uint32 _game_id; byte **_base_sounds; + OSystem *_system; + Common::Mutex _mutex; + protected: bool _paused; bool _initialized; @@ -391,13 +418,19 @@ protected: DeferredCommand _deferredCommands[4]; protected: + IMuseInternal(); + int initialize(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver); + + static void midiTimerCallback (void *data); + void on_timer (MidiDriver *midi); + byte *findStartOfSound(int sound); bool isMT32(int sound); bool isMIDI(int sound); int get_queue_sound_status(int sound) const; void handle_marker(uint id, byte data); int get_channel_volume(uint a); - void initMidiDriver(MidiDriver *midi); + void initMidiDriver (TimerCallbackInfo *info); void initGM(MidiDriver *midi); void initMT32(MidiDriver *midi); void init_players(); @@ -419,6 +452,7 @@ protected: int enqueue_command(int a, int b, int c, int d, int e, int f, int g); int enqueue_trigger(int sound, int marker); + int clear_queue(); int query_queue(int param); Player *findActivePlayer(int id); @@ -432,38 +466,43 @@ protected: void fix_parts_after_load(); void fix_players_after_load(ScummEngine *scumm); + int setImuseMasterVolume(uint vol); - static void midiTimerCallback(void *data); - -public: - IMuseInternal(); - - int initialize(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver); void reallocateMidiChannels(MidiDriver *midi); void setGlobalAdlibInstrument(byte slot, byte *data); void copyGlobalAdlibInstrument(byte slot, Instrument *dest); bool isNativeMT32() { return _native_mt32; } - // IMuse interface +protected: + // Internal mutex-free versions of the IMuse and MusicEngine methods. + bool startSound_internal (int sound); + int stopSound_internal (int sound); + int stopAllSounds_internal(); + int getSoundStatus_internal (int sound, bool ignoreFadeouts) const; + int32 doCommand_internal (int a, int b, int c, int d, int e, int f, int g, int h); + int32 doCommand_internal (int numargs, int args[]); - void on_timer(MidiDriver *midi); +public: + // IMuse interface void pause(bool paused); - int terminate1(); - int terminate2(); int save_or_load(Serializer *ser, ScummEngine *scumm); - int setMusicVolume(uint vol); - int setImuseMasterVolume(uint vol); - bool startSound(int sound); - int stopSound(int sound); - int stopAllSounds(); - int getSoundStatus(int sound, bool ignoreFadeouts) const; - int getMusicTimer() const; - int32 doCommand (int a, int b, int c, int d, int e, int f, int g, int h); + bool get_sound_active(int sound) const; int32 doCommand (int numargs, int args[]); - int clear_queue(); void setBase(byte **base); uint32 property(int prop, uint32 value); +public: + // MusicEngine interface + void setMusicVolume(int vol); + void startSound(int sound); + void stopSound(int sound); + void stopAllSounds(); + int getSoundStatus (int sound) const; + int getMusicTimer() const; + void terminate(); + +public: + // Factory function static IMuseInternal *create(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver); }; diff --git a/engines/scumm/imuse/imuse_player.cpp b/engines/scumm/imuse/imuse_player.cpp index e2f7994b68..f271fddb92 100644 --- a/engines/scumm/imuse/imuse_player.cpp +++ b/engines/scumm/imuse/imuse_player.cpp @@ -395,7 +395,7 @@ void Player::sysEx(const byte *p, uint16 len) { switch (code = *p++) { case 0: - if (g_scumm->_game.id != GID_SAMNMAX) { + if (_se->_game_id != GID_SAMNMAX) { // There are 17 bytes of useful information beyond // what we've read so far. All we know about them is // as follows: diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index e6ef88548a..f077149fee 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -1692,6 +1692,7 @@ void ScummEngine::setupMusic(int midi) { _musicEngine = _imuse = IMuse::create(_system, nativeMidiDriver, adlibMidiDriver); if (_imuse) { + _imuse->property(IMuse::PROP_GAME_ID, _game.id); if (ConfMan.hasKey("tempo")) _imuse->property(IMuse::PROP_TEMPO_BASE, ConfMan.getInt("tempo")); _imuse->property(IMuse::PROP_NATIVE_MT32, _native_mt32); diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index 823156c32a..b4fb62cf34 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -876,11 +876,6 @@ void Sound::stopAllSounds() { if (_vm->_musicEngine) { _vm->_musicEngine->stopAllSounds(); } - if (_vm->_imuse) { - // FIXME: Maybe we could merge this call to clear_queue() - // into IMuse::stopAllSounds() ? - _vm->_imuse->clear_queue(); - } // Stop all SFX if (!_vm->_imuseDigital) { |