From 29a5c6a45b90e64cf5d618da4cdcd6e6da3425fb Mon Sep 17 00:00:00 2001 From: Florian Kagerer Date: Wed, 18 Aug 2010 21:38:43 +0000 Subject: SCUMM/FM-TOWNS: start rewriting audio code - Start rewriting audio code for FM-TOWNS versions of Loom, Indy3 and Monkey Island 1 using the recently added code in towns_audio.cpp (Zak should work the same way, but I can't test, since I don't own that one). - All sound types (pcm, euphony and cd audio) now support volume and balance control (e.g. try walking into/out of the kitchen and opening/closing the door in the Scumm Bar in Monkey Island 1 or walking into/out of the circus tent). - Pcm sounds now support proper loop start/end and note offsets (e.g. try out the hammer sound in the forge in LOOM for example). - some other minor improvements - The FM-Towns versions of Indy 4 and Monkey Island 2 are not affected. I don't have Monkey Island 2, but I presume that it will work like Indy 4. Adding support for these will be a separate task, since they work quite differently. svn-id: r52198 --- engines/scumm/detection_tables.h | 9 +- engines/scumm/imuse/imuse_player.cpp | 7 +- engines/scumm/midiparser_eup.cpp | 222 -------------- engines/scumm/module.mk | 2 +- engines/scumm/player_towns.cpp | 557 +++++++++++++++++++++++++++++++++++ engines/scumm/player_towns.h | 120 ++++++++ engines/scumm/saveload.cpp | 9 + engines/scumm/saveload.h | 2 +- engines/scumm/script_v5.cpp | 17 +- engines/scumm/scumm.cpp | 10 + engines/scumm/scumm.h | 2 + engines/scumm/sound.cpp | 94 +----- 12 files changed, 725 insertions(+), 326 deletions(-) delete mode 100644 engines/scumm/midiparser_eup.cpp create mode 100644 engines/scumm/player_towns.cpp create mode 100644 engines/scumm/player_towns.h (limited to 'engines') diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h index f275b1c93f..98fab9468a 100644 --- a/engines/scumm/detection_tables.h +++ b/engines/scumm/detection_tables.h @@ -186,6 +186,7 @@ using Common::GUIO_NONE; using Common::GUIO_NOLAUNCHLOAD; using Common::GUIO_NOMIDI; using Common::GUIO_NOSPEECH; +using Common::GUIO_MIDITOWNS; // The following table contains information about variants of our various // games. We index into it with help of md5table (from scumm-md5.h), to find @@ -217,19 +218,19 @@ static const GameSettings gameVariantsTable[] = { {"zak", "V1", "v1", GID_ZAK, 1, 0, MDT_PCSPK | MDT_PCJR, 0, UNK, GUIO_NOSPEECH | GUIO_NOMIDI}, {"zak", "V2", "v2", GID_ZAK, 2, 0, MDT_PCSPK | MDT_PCJR, 0, UNK, GUIO_NOSPEECH | GUIO_NOMIDI}, - {"zak", "FM-TOWNS", 0, GID_ZAK, 3, 0, MDT_TOWNS, GF_OLD256 | GF_AUDIOTRACKS, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_NOMIDI}, + {"zak", "FM-TOWNS", 0, GID_ZAK, 3, 0, MDT_TOWNS, GF_OLD256 | GF_AUDIOTRACKS, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_NOMIDI | GUIO_MIDITOWNS}, {"indy3", "EGA", "ega", GID_INDY3, 3, 0, MDT_PCSPK | MDT_PCJR | MDT_CMS | MDT_ADLIB, 0, UNK, GUIO_NOSPEECH | GUIO_NOMIDI}, {"indy3", "No AdLib", "ega", GID_INDY3, 3, 0, MDT_PCSPK | MDT_PCJR, 0, UNK, GUIO_NOSPEECH | GUIO_NOMIDI}, {"indy3", "VGA", "vga", GID_INDY3, 3, 0, MDT_PCSPK | MDT_PCJR | MDT_ADLIB, GF_OLD256 | GF_FEW_LOCALS, Common::kPlatformPC, GUIO_NOSPEECH | GUIO_NOMIDI}, - {"indy3", "FM-TOWNS", 0, GID_INDY3, 3, 0, MDT_TOWNS, GF_OLD256 | GF_FEW_LOCALS | GF_AUDIOTRACKS, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_NOMIDI}, + {"indy3", "FM-TOWNS", 0, GID_INDY3, 3, 0, MDT_TOWNS, GF_OLD256 | GF_FEW_LOCALS | GF_AUDIOTRACKS, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_NOMIDI | GUIO_MIDITOWNS}, {"loom", "EGA", "ega", GID_LOOM, 3, 0, MDT_PCSPK | MDT_PCJR | MDT_CMS | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NOSPEECH}, {"loom", "No AdLib", "ega", GID_LOOM, 3, 0, MDT_PCSPK | MDT_PCJR | MDT_CMS, 0, UNK, GUIO_NOSPEECH | GUIO_NOMIDI}, #ifdef USE_RGB_COLOR {"loom", "PC-Engine", 0, GID_LOOM, 3, 0, MDT_NONE, GF_AUDIOTRACKS | GF_OLD256 | GF_16BIT_COLOR, Common::kPlatformPCEngine, GUIO_NOSPEECH | GUIO_NOMIDI}, #endif - {"loom", "FM-TOWNS", 0, GID_LOOM, 3, 0, MDT_TOWNS, GF_AUDIOTRACKS | GF_OLD256, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_NOMIDI}, + {"loom", "FM-TOWNS", 0, GID_LOOM, 3, 0, MDT_TOWNS, GF_AUDIOTRACKS | GF_OLD256, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_NOMIDI | GUIO_MIDITOWNS}, {"loom", "VGA", "vga", GID_LOOM, 4, 0, MDT_NONE, GF_AUDIOTRACKS, Common::kPlatformPC, GUIO_NOSPEECH | GUIO_NOMIDI}, {"pass", 0, 0, GID_PASS, 4, 0, MDT_PCSPK | MDT_PCJR | MDT_ADLIB, GF_16COLOR, Common::kPlatformPC, GUIO_NOSPEECH | GUIO_NOMIDI}, @@ -239,7 +240,7 @@ static const GameSettings gameVariantsTable[] = { {"monkey", "No AdLib", "ega", GID_MONKEY_EGA, 4, 0, MDT_PCSPK | MDT_PCJR, GF_16COLOR, Common::kPlatformAtariST, GUIO_NOSPEECH | GUIO_NOMIDI}, {"monkey", "Demo", "ega", GID_MONKEY_EGA, 4, 0, MDT_PCSPK | MDT_PCJR | MDT_ADLIB, GF_16COLOR, Common::kPlatformPC, GUIO_NOSPEECH | GUIO_NOMIDI}, {"monkey", "CD", 0, GID_MONKEY, 5, 0, MDT_ADLIB, GF_AUDIOTRACKS, UNK, GUIO_NOSPEECH | GUIO_NOMIDI}, - {"monkey", "FM-TOWNS", 0, GID_MONKEY, 5, 0, MDT_ADLIB, GF_AUDIOTRACKS, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_NOMIDI}, + {"monkey", "FM-TOWNS", 0, GID_MONKEY, 5, 0, MDT_TOWNS, GF_AUDIOTRACKS, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_NOMIDI | GUIO_MIDITOWNS}, {"monkey", "SEGA", 0, GID_MONKEY, 5, 0, MDT_NONE, GF_AUDIOTRACKS, Common::kPlatformSegaCD, GUIO_NOSPEECH | GUIO_NOMIDI}, {"monkey2", 0, 0, GID_MONKEY2, 5, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NOSPEECH}, diff --git a/engines/scumm/imuse/imuse_player.cpp b/engines/scumm/imuse/imuse_player.cpp index 73aec472e4..6b38f80df1 100644 --- a/engines/scumm/imuse/imuse_player.cpp +++ b/engines/scumm/imuse/imuse_player.cpp @@ -47,7 +47,6 @@ namespace Scumm { #define PERCUSSION_CHANNEL 9 extern MidiParser *MidiParser_createRO(); -extern MidiParser *MidiParser_createEUP(); uint16 Player::_active_notes[128]; @@ -195,7 +194,11 @@ int Player::start_seq_sound(int sound, bool reset_vars) { _parser = MidiParser_createRO(); } else if (!memcmp(ptr, "SO", 2)) { // Euphony (FM-TOWNS) resource - _parser = MidiParser_createEUP(); + + //////////// REMOVE + //_parser = MidiParser_createEUP(); + /////////// + } else if (!memcmp(ptr, "FORM", 4)) { // Humongous Games XMIDI resource _parser = MidiParser::createParser_XMIDI(); diff --git a/engines/scumm/midiparser_eup.cpp b/engines/scumm/midiparser_eup.cpp deleted file mode 100644 index 592d43f7fe..0000000000 --- a/engines/scumm/midiparser_eup.cpp +++ /dev/null @@ -1,222 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - - -#include "sound/midiparser.h" -#include "sound/mididrv.h" -#include "common/util.h" - -namespace Scumm { - -/** - * The FM-TOWNS Euphony version of MidiParser. - */ -class MidiParser_EUP : public MidiParser { -protected: - byte _instruments[6][50]; // Two extra bytes for SysEx ID and channel # - byte *_instr_to_channel; - struct { - byte *enable; - int8 *channel; - int8 *volume; - int8 *transpose; - } _presets; - bool _loop; - byte _presend; // Tracks which startup implied events have been sent. - uint32 _base_tick; // Events times are relative to this base. - -protected: - void parseNextEvent (EventInfo &info); - void resetTracking(); - -public: - bool loadMusic (byte *data, uint32 size); -}; - - - -////////////////////////////////////////////////// -// -// MidiParser_EUP implementation -// -////////////////////////////////////////////////// - -void MidiParser_EUP::parseNextEvent (EventInfo &info) { - byte *pos = _position._play_pos; - - // FIXME: The presend is for sending init events - // that aren't actually in the stream. This would - // be for, e.g., instrument setup. Right now, we - // don't actually use the instruments specified - // in the music header. We're sending fixed GM - // program changes to get a reasonable "one-size- - // fits-all" sound until we actually support the - // FM synthesis capabilities of FM-TOWNS. - for (; _presend < 12; ++_presend) { - if (_instr_to_channel[_presend >> 1] >= 16) - continue; - info.start = pos; - info.delta = 0; - if (_presend & 1) { - byte *data = &_instruments[_presend >> 1][0]; - data[1] = _instr_to_channel[_presend >> 1]; - info.event = 0xF0; - info.ext.data = data; - info.length = 48; - } else { - info.event = 0xB0 | (_presend >> 1); - info.basic.param1 = 121; - info.basic.param2 = 0; - } - ++_presend; - return; - } - - while (true) { - byte cmd = *pos; - if ((cmd & 0xF0) == 0x90) { - byte preset = pos[1]; - byte channel = _presets.channel[preset]; - if (channel >= 16) - channel = cmd & 0x0F; - uint16 tick = (pos[2] | ((uint16) pos[3] << 7)) + _base_tick; - int note = (int) pos[4] + _presets.transpose[preset]; - int volume = (int) pos[5]; - // HACK: Loom-Towns distaff tracks seem to - // contain zero-volume note events, so change - // those to full volume. - if (!volume) - volume = 127; - volume += _presets.volume[preset]; - if (volume > 127) - volume = 127; - else if (volume < 0) - volume = 0; - pos += 6; - if (_presets.enable[preset]) { - uint16 duration = pos[1] | (pos[2] << 4); - info.start = pos; - uint32 last = _position._last_event_tick; - info.delta = (tick < last) ? 0 : (tick - last); - info.event = 0x90 | channel; - info.length = duration; - info.basic.param1 = note; - info.basic.param2 = volume; - pos += 6; - break; - } - pos += 6; - } else if (cmd == 0xF2) { - // This is a "measure marker" of sorts. - // It advances the "base time", to which - // all event times are relative. - _base_tick += (pos[3] << 7) | pos[2]; - pos += 6; - } else if (cmd == 0xF8) { - // TODO: Implement this. - pos += 6; - } else if (cmd == 0xFD || cmd == 0xFE) { - // End of track. - if (_loop && false) { - // TODO: Implement this. - } else { - info.start = pos; - uint32 last = _position._last_event_tick; - info.delta = (_base_tick < last) ? 0 : (_base_tick - last); - info.event = 0xFF; - info.length = 0; - info.ext.type = 0x2F; - info.ext.data = pos; - break; - } - } else { - error("Unknown Euphony music event 0x%02X", (int) cmd); - memset(&info, 0, sizeof(info)); - pos = 0; - break; - } - } - _position._play_pos = pos; -} - -bool MidiParser_EUP::loadMusic (byte *data, uint32 size) { - unloadMusic(); - byte *pos = data; - int i; - - if (memcmp(pos, "SO", 2)) { - error("'SO' header expected but found '%c%c' instead.", pos[0], pos[1]); - return false; - } - - byte numInstruments = pos[16]; - pos += 16 + 2; - for (i = 0; i < numInstruments; ++i) { - _instruments[i][0] = 0x7C; - memcpy (&_instruments[i][2], pos, 48); - pos += 48; - } - - // Load the prest pointers - _presets.enable = pos; - pos += 32; - _presets.channel = (int8 *) pos; - pos += 32; - _presets.volume = (int8 *) pos; - pos += 32; - _presets.transpose = (int8 *) pos; - pos += 32; - - pos += 8; // Unknown bytes - _instr_to_channel = pos; // Instrument-to-channel mapping - pos += 6; - pos += 4; // Skip the music size for now. - pos++; // Unknown byte - byte tempo = *pos++; - _loop = (*pos++ != 1); - pos++; // Unknown byte - - _num_tracks = 1; - _ppqn = 120; - _tracks[0] = pos; - - // Note that we assume the original data passed in - // will persist beyond this call, i.e. we do NOT - // copy the data to our own buffer. Take warning.... - resetTracking(); - setTempo (1000000 * 60 / tempo); - setTrack (0); - return true; -} - -void MidiParser_EUP::resetTracking() { - MidiParser::resetTracking(); - _presend = 0; - _base_tick = 0; -} - -MidiParser *MidiParser_createEUP() { return new MidiParser_EUP; } - -} // End of namespace Scumm diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk index e5f0745dd6..14d1f5fdd4 100644 --- a/engines/scumm/module.mk +++ b/engines/scumm/module.mk @@ -29,7 +29,6 @@ MODULE_OBJS := \ imuse/sysex_samnmax.o \ imuse/sysex_scumm.o \ input.o \ - midiparser_eup.o \ midiparser_ro.o \ object.o \ palette.o \ @@ -37,6 +36,7 @@ MODULE_OBJS := \ player_nes.o \ player_pce.o \ player_sid.o \ + player_towns.o \ player_v1.o \ player_v2.o \ player_v2a.o \ diff --git a/engines/scumm/player_towns.cpp b/engines/scumm/player_towns.cpp new file mode 100644 index 0000000000..ab1b1ed585 --- /dev/null +++ b/engines/scumm/player_towns.cpp @@ -0,0 +1,557 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + + +#include "scumm/sound.h" +#include "scumm/player_towns.h" + +namespace Scumm { + +Player_Towns::Player_Towns(ScummEngine *vm, Audio::Mixer *mixer) : _vm(vm) { + _cdaCurrentSound = _eupCurrentSound = _cdaNumLoops = 0; + _cdaForceRestart = 0; + memset(_pcmCurrentSound, 0, sizeof(_pcmCurrentSound)); + _cdaVolLeft = _cdaVolRight = 0; + + _eupVolLeft = _eupVolRight = 0; + memset(&_ovrCur, 0, sizeof(SoundOvrParameters)); + _soundOverride = 0; + + if (_vm->_game.version == 3) { + _soundOverride = new SoundOvrParameters[200]; + memset(_soundOverride, 0, 200 * sizeof(SoundOvrParameters)); + } + + _eupLooping = false; + _unkFlags = 0x33; + + _driver = new TownsEuphonyDriver(mixer); +} + +Player_Towns::~Player_Towns() { + delete[] _soundOverride; + delete _driver; +} + +bool Player_Towns::init() { + if (!_driver) + return false; + + if (!_driver->init()) + return false; + + _driver->reserveSoundEffectChannels(8); + + // Treat all 6 fm channels and all 8 pcm channels as sound effect channels + // since music seems to exist as CD audio only in the games which use this + // MusicEngine implementation. + _driver->intf()->setSoundEffectChanMask(-1); + + setVolumeCD(255, 255); + + return true; +} + +void Player_Towns::setMusicVolume(int vol) { + _driver->setMusicVolume(vol); +} + +void Player_Towns::setSfxVolume(int vol) { + _driver->setSoundEffectVolume(vol); +} + +void Player_Towns::startSound(int sound) { + uint8 *ptr = _vm->getResourceAddress(rtSound, sound); + if (_vm->_game.version != 3) { + ptr += 2; + } else if (_soundOverride && sound > 0 && sound < 200) { + memcpy(&_ovrCur, &_soundOverride[sound], sizeof(SoundOvrParameters)); + memset(&_soundOverride[sound], 0, sizeof(SoundOvrParameters)); + } + + int type = ptr[13]; + + if (type == 0) { + playPcmTrack(sound, ptr + 6); + } else if (type == 1) { + playEuphonyTrack(sound, ptr + 6); + } else if (type == 2) { + playCdaTrack(sound, ptr + 6); + } + memset(&_ovrCur, 0, sizeof(SoundOvrParameters)); +} + +void Player_Towns::stopSound(int sound) { + if (sound != 0 && sound == _cdaCurrentSound) { + _cdaCurrentSound = 0; + _vm->_sound->stopCD(); + _vm->_sound->stopCDTimer(); + } + + if (sound != 0 && sound == _eupCurrentSound) { + _eupCurrentSound = 0; + _eupLooping = false; + _driver->stopParser(); + } + + stopPcmTrack(sound); +} + +void Player_Towns::stopAllSounds() { + _cdaCurrentSound = 0; + _vm->_sound->stopCD(); + _vm->_sound->stopCDTimer(); + + // Loom disasm seems to stop only CD audio and PCM sounds here + /*_eupCurrentSound = 0; + _eupLooping = false; + _driver->stopParser();*/ + + stopPcmTrack(0); +} + +int Player_Towns::getSoundStatus(int sound) const { + if (sound == _cdaCurrentSound) + return _vm->_sound->pollCD(); + if (sound == _eupCurrentSound) + return _driver->parserIsPlaying() ? 1 : 0; + for (int i = 1; i < 9; i++) { + if (_pcmCurrentSound[i].index == sound) + return _driver->soundEffectIsPlaying(i + 0x3f) ? 1 : 0; + } + return 0; +} + +int32 Player_Towns::doCommand(int numargs, int args[]) { + int32 res = 0; + + switch (args[0]) { + case 2: + _driver->intf()->callback(73, 0); + break; + + case 3: + restartLoopingSounds(); + break; + + case 8: + startSound(args[1]); + break; + + case 9: + _vm->_sound->stopSound(args[1]); + break; + + case 11: + stopPcmTrack(0); + break; + + case 14: + startSoundEx(args[1], args[2], args[3], args[4]); + break; + + case 15: + stopSoundSuspendLooping(args[1]); + break; + + default: + warning("Player_Towns::doCommand: Unknown command %d", args[0]); + break; + } + + return res; +} + +void Player_Towns::setVolumeCD(int left, int right) { + _cdaVolLeft = left & 0xff; + _cdaVolRight = right & 0xff; + _driver->setOutputVolume(1, left >> 1, right >> 1); +} + +void Player_Towns::setSoundVolume(int sound, int left, int right) { + if (_soundOverride && sound > 0 && sound < 200) { + _soundOverride[sound].vLeft = left; + _soundOverride[sound].vRight = right; + } +} + +void Player_Towns::setSoundNote(int sound, int note) { + if (_soundOverride && sound > 0 && sound < 200) + _soundOverride[sound].note = note; +} + +void Player_Towns::saveLoadWithSerializer(Serializer *ser) { + _cdaCurrentSoundTemp = (_vm->_sound->pollCD() && _cdaNumLoops > 1) ? _cdaCurrentSound & 0xff : 0; + _cdaNumLoopsTemp = _cdaNumLoops & 0xff; + + static const SaveLoadEntry cdEntries[] = { + MKLINE(Player_Towns, _cdaCurrentSoundTemp, sleUint8, VER(81)), + MKLINE(Player_Towns, _cdaNumLoopsTemp, sleUint8, VER(81)), + MKLINE(Player_Towns, _cdaVolLeft, sleUint8, VER(81)), + MKLINE(Player_Towns, _cdaVolRight, sleUint8, VER(81)), + MKEND() + }; + + ser->saveLoadEntries(this, cdEntries); + + if (!_eupLooping && !_driver->parserIsPlaying()) + _eupCurrentSound = 0; + + static const SaveLoadEntry eupEntries[] = { + MKLINE(Player_Towns, _eupCurrentSound, sleUint8, VER(81)), + MKLINE(Player_Towns, _eupLooping, sleUint8, VER(81)), + MKLINE(Player_Towns, _eupVolLeft, sleUint8, VER(81)), + MKLINE(Player_Towns, _eupVolRight, sleUint8, VER(81)), + MKEND() + }; + + ser->saveLoadEntries(this, eupEntries); + + static const SaveLoadEntry pcmEntries[] = { + MKLINE(PcmCurrentSound, index, sleInt16, VER(81)), + MKLINE(PcmCurrentSound, chan, sleInt16, VER(81)), + MKLINE(PcmCurrentSound, note, sleUint8, VER(81)), + MKLINE(PcmCurrentSound, velo, sleUint8, VER(81)), + MKLINE(PcmCurrentSound, pan, sleUint8, VER(81)), + MKLINE(PcmCurrentSound, paused, sleUint8, VER(81)), + MKLINE(PcmCurrentSound, looping, sleUint8, VER(81)), + MKLINE(PcmCurrentSound, priority, sleUint32, VER(81)), + MKEND() + }; + + for (int i = 1; i < 9; i++) { + if (!_pcmCurrentSound[i].index) + continue; + + if (_driver->soundEffectIsPlaying(i + 0x3f)) + continue; + + _driver->stopSoundEffect(i + 0x3f); + + _pcmCurrentSound[i].index = 0; + } + + ser->saveLoadArrayOf(_pcmCurrentSound, 9, sizeof(PcmCurrentSound), pcmEntries); +} + +void Player_Towns::restoreAfterLoad() { + setVolumeCD(_cdaVolLeft, _cdaVolRight); + + if (_cdaCurrentSoundTemp) { + uint8 *ptr = _vm->getResourceAddress(rtSound, _cdaCurrentSoundTemp) + 6; + if (_vm->_game.version != 3) + ptr += 2; + + if (ptr[7] == 2) { + playCdaTrack(_cdaCurrentSoundTemp, ptr, true); + _cdaCurrentSound = _cdaCurrentSoundTemp; + _cdaNumLoops = _cdaNumLoopsTemp; + } + } + + if (_eupCurrentSound) { + uint8 *ptr = _vm->getResourceAddress(rtSound, _eupCurrentSound) + 6; + if (_vm->_game.version != 3) + ptr += 2; + + if (ptr[7] == 1) { + setSoundVolume(_eupCurrentSound, _eupVolLeft, _eupVolRight); + playEuphonyTrack(_eupCurrentSound, ptr); + } + } + + for (int i = 1; i < 9; i++) { + if (!_pcmCurrentSound[i].index) + continue; + + uint8 *ptr = _vm->getResourceAddress(rtSound, _pcmCurrentSound[i].index); + if (!ptr) + continue; + + if (_vm->_game.version != 3) + ptr += 2; + + if (ptr[13]) + continue; + + playPcmTrack(_pcmCurrentSound[i].index, ptr + 6, _pcmCurrentSound[i].velo, _pcmCurrentSound[i].pan, _pcmCurrentSound[i].note); + } +} + +int Player_Towns::getNextFreePcmChannel(int sound, int sfxChanRelIndex) { + int chan = 0; + for (int i = 8; i; i--) { + if (!_pcmCurrentSound[i].index) { + chan = i; + continue; + } + + if (_driver->soundEffectIsPlaying(i + 0x3f)) + continue; + + chan = i; + _vm->_sound->stopSound(_pcmCurrentSound[chan].index); + } + + if (!chan) { + uint16 l = 0xffff; + uint8 *ptr = 0; + for (int i = 8; i; i--) { + ptr = _vm->getResourceAddress(rtSound, _pcmCurrentSound[i].index) + 6; + uint16 a = READ_LE_UINT16(ptr + 10); + if (a <= l) { + chan = i; + l = a; + } + } + + ptr = _vm->getResourceAddress(rtSound, sound) + 6; + if (l <= READ_LE_UINT16(ptr + 10)) + _vm->_sound->stopSound(_pcmCurrentSound[chan].index); + else + chan = 0; + } + + if (chan) { + _pcmCurrentSound[chan].index = sound; + _pcmCurrentSound[chan].chan = sfxChanRelIndex; + } + + return chan; +} + +void Player_Towns::restartLoopingSounds() { + if (_cdaNumLoops && !_cdaForceRestart) + _cdaForceRestart = 1; + + for (int i = 1; i < 9; i++) { + if (!_pcmCurrentSound[i].paused) + continue; + + _pcmCurrentSound[i].paused = 0; + + uint8 *ptr = _vm->getResourceAddress(rtSound, _pcmCurrentSound[i].index); + if (!ptr) + continue; + ptr += 24; + + int c = 1; + while (_pcmCurrentSound[i].chan != c) { + ptr = ptr + READ_LE_UINT32(&ptr[12]) + 32; + c++; + } + + _driver->playSoundEffect(i + 0x3f, _pcmCurrentSound[i].note, _pcmCurrentSound[i].velo, ptr); + } + + _driver->intf()->callback(73, 1); +} + +void Player_Towns::startSoundEx(int sound, int velo, int pan, int note) { + uint8 *ptr = _vm->getResourceAddress(rtSound, sound) + 2; + + if (pan > 99) + pan = 99; + + velo = velo ? (velo * ptr[14] + 50) / 100 : ptr[14]; + velo = CLIP(velo, 1, 255); + + if (ptr[13] == 0) { + velo >>= 1; + + if (!velo) + velo = 1; + + pan = pan ? (((pan << 7) - pan) + 50) / 100 : 64; + + playPcmTrack(sound, ptr + 6, velo, pan, note); + + } else if (ptr[13] == 2) { + int volLeft = velo; + int volRight = velo; + + if (pan < 50) + volRight = ((pan * 2 + 1) * velo + 50) / 100; + else if (pan > 50) + volLeft = (((99 - pan) * 2 + 1) * velo + 50) / 100; + + setVolumeCD(volLeft, volRight); + + if (!_cdaForceRestart && sound == _cdaCurrentSound) + return; + + playCdaTrack(sound, ptr + 6, true); + } +} + +void Player_Towns::stopSoundSuspendLooping(int sound) { + if (!sound) { + return; + } else if (sound == _cdaCurrentSound) { + if (_cdaNumLoops && _cdaForceRestart) + _cdaForceRestart = 1; + } else { + for (int i = 1; i < 9; i++) { + if (sound == _pcmCurrentSound[i].index) { + if (!_driver->soundEffectIsPlaying(i + 0x3f)) + continue; + _driver->stopSoundEffect(i + 0x3f); + if (_pcmCurrentSound[i].looping) + _pcmCurrentSound[i].paused = 1; + else + _pcmCurrentSound[i].index = 0; + } + } + } +} + +void Player_Towns::playEuphonyTrack(int sound, const uint8 *data) { + const uint8 *pos = data + 16; + const uint8 *src = pos + data[14] * 48; + const uint8 *trackData = src + 150; + + for (int i = 0; i < 32; i++) + _driver->chanEnable(i, *src++); + for (int i = 0; i < 32; i++) + _driver->chanMode(i, 0xff); + for (int i = 0; i < 32; i++) + _driver->chanOrdr(i, *src++); + for (int i = 0; i < 32; i++) + _driver->chanVolumeShift(i, *src++); + for (int i = 0; i < 32; i++) + _driver->chanNoteShift(i, *src++); + + src += 8; + for (int i = 0; i < 6; i++) + _driver->assignChannel(i, *src++); + + for (int i = 0; i < data[14]; i++) { + _driver->loadInstrument(i, i, pos + i * 48); + _driver->intf()->callback(4, i, i); + } + + _eupVolLeft = _ovrCur.vLeft; + _eupVolRight = _ovrCur.vRight; + int lvl = _ovrCur.vLeft + _ovrCur.vRight; + if (!lvl) + lvl = data[8] + data[9]; + lvl >>= 2; + + for (int i = 0; i < 6; i++) + _driver->chanVolume(i, lvl); + + uint32 trackSize = READ_LE_UINT32(src); + src += 4; + uint8 startTick = *src++; + + _driver->setMusicTempo(*src++); + _driver->startMusicTrack(trackData, trackSize, startTick); + + _eupLooping = (*src != 1) ? 1 : 0; + _driver->setMusicLoop(_eupLooping != 0); + _driver->continueParsing(); + _eupCurrentSound = sound; +} + +void Player_Towns::playPcmTrack(int sound, const uint8 *data, int velo, int pan, int note) { + const uint8 *ptr = data; + const uint8 *sfxData = ptr + 16; + + int note2, velocity; + + if (velo) + velocity = velo; + else if (_ovrCur.vLeft + _ovrCur.vRight) + velocity = (_ovrCur.vLeft + _ovrCur.vRight) >> 2; + else + velocity = ptr[8] >> 1; + + int numChan = ptr[14]; + for (int i = 0; i < numChan; i++) { + int chan = getNextFreePcmChannel(sound, i); + if (!chan) + return; + + _driver->intf()->callback(70, _unkFlags); + _driver->chanPanPos(chan + 0x3f, pan); + + if (note) + note2 = note; + else if (_ovrCur.note) + note2 = _ovrCur.note; + else + note2 = sfxData[28]; + + _driver->playSoundEffect(chan + 0x3f, note2, velocity, sfxData); + + _pcmCurrentSound[chan].note = note2; + _pcmCurrentSound[chan].velo = velocity; + _pcmCurrentSound[chan].pan = pan; + _pcmCurrentSound[chan].paused = 0; + _pcmCurrentSound[chan].looping = READ_LE_UINT32(&sfxData[20]) ? 1 : 0; + + sfxData += (READ_LE_UINT32(&sfxData[12]) + 32); + } +} + +void Player_Towns::playCdaTrack(int sound, const uint8 *data, bool skipTrackVelo) { + const uint8 *ptr = data; + + if (!sound) + return; + + if (!skipTrackVelo) { + if (_ovrCur.vLeft + _ovrCur.vRight) + setVolumeCD(_ovrCur.vLeft, _ovrCur.vRight); + else + setVolumeCD(ptr[8], ptr[9]); + } + + if (sound == _cdaCurrentSound && _vm->_sound->pollCD() == 1) + return; + + ptr += 16; + + int track = ptr[0]; + _cdaNumLoops = ptr[1]; + int start = (ptr[2] * 60 + ptr[3]) * 75 + ptr[4]; + int end = (ptr[5] * 60 + ptr[6]) * 75 + ptr[7]; + + _vm->_sound->playCDTrack(track, _cdaNumLoops == 0xff ? -1 : _cdaNumLoops, start, end <= start ? 0 : end - start); + _cdaForceRestart = 0; + _cdaCurrentSound = sound; +} + +void Player_Towns::stopPcmTrack(int sound) { + for (int i = 1; i < 9; i++) { + if (sound == _pcmCurrentSound[i].index || !sound) { + _driver->stopSoundEffect(i + 0x3f); + _pcmCurrentSound[i].index = 0; + } + } +} + +} // End of namespace Scumm + diff --git a/engines/scumm/player_towns.h b/engines/scumm/player_towns.h new file mode 100644 index 0000000000..6d87c93c09 --- /dev/null +++ b/engines/scumm/player_towns.h @@ -0,0 +1,120 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SCUMM_PLAYER_TOWNS_H +#define SCUMM_PLAYER_TOWNS_H + +#include "scumm/scumm.h" +#include "scumm/music.h" +#include "sound/softsynth/fmtowns_pc98/towns_euphony.h" + +namespace Scumm { + +class Player_Towns : public MusicEngine { +public: + Player_Towns(ScummEngine *vm, Audio::Mixer *mixer); + virtual ~Player_Towns(); + + bool init(); + + void setMusicVolume(int vol); + void setSfxVolume(int vol); + void startSound(int sound); + void stopSound(int sound); + void stopAllSounds(); + + int getSoundStatus(int sound) const; + int getCurrentCdaSound() { return _cdaCurrentSound; } + int getCurrentCdaVolume() { return (_cdaVolLeft + _cdaVolRight + 1) >> 1; } + + virtual int32 doCommand(int numargs, int args[]); + + void setVolumeCD(int left, int right); + void setSoundVolume(int sound, int left, int right); + void setSoundNote(int sound, int note); + + void saveLoadWithSerializer(Serializer *ser); + void restoreAfterLoad(); + + TownsEuphonyDriver *driver() { return _driver; } + +protected: + virtual int getNextFreePcmChannel(int sound, int sfxChanRelIndex); + +private: + void restartLoopingSounds(); + void startSoundEx(int sound, int velo, int pan, int note); + void stopSoundSuspendLooping(int sound); + + void playEuphonyTrack(int sound, const uint8 *data); + void playPcmTrack(int sound, const uint8 *data, int velo = 0, int pan = 64, int note = 0); + void playCdaTrack(int sound, const uint8 *data, bool skipTrackVelo = false); + + void stopPcmTrack(int sound); + + uint8 _cdaVolLeft; + uint8 _cdaVolRight; + + struct SoundOvrParameters { + uint8 vLeft; + uint8 vRight; + uint8 note; + }; + + SoundOvrParameters *_soundOverride; + SoundOvrParameters _ovrCur; + + uint8 _unkFlags; + + struct PcmCurrentSound { + uint16 index; + uint16 chan; + uint8 note; + uint8 velo; + uint8 pan; + uint8 paused; + uint8 looping; + uint32 priority; + } _pcmCurrentSound[9]; + + uint8 _eupCurrentSound; + uint8 _eupLooping; + uint8 _eupVolLeft; + uint8 _eupVolRight; + + uint8 _cdaCurrentSound; + uint8 _cdaNumLoops; + uint8 _cdaForceRestart; + + uint8 _cdaCurrentSoundTemp; + uint8 _cdaNumLoopsTemp; + + TownsEuphonyDriver *_driver; + ScummEngine *_vm; +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp index 3cc619f630..ca48a2b86a 100644 --- a/engines/scumm/saveload.cpp +++ b/engines/scumm/saveload.cpp @@ -32,6 +32,7 @@ #include "scumm/charset.h" #include "scumm/imuse_digi/dimuse.h" #include "scumm/imuse/imuse.h" +#include "player_towns.h" #include "scumm/he/intern_he.h" #include "scumm/object.h" #include "scumm/resource.h" @@ -447,6 +448,9 @@ bool ScummEngine::loadState(int slot, bool compat) { // Update volume settings syncSoundSettings(); + if (_townsPlayer && (hdr.ver >= VER(81))) + _townsPlayer->restoreAfterLoad(); + // Init NES costume data if (_game.platform == Common::kPlatformNES) { if (hdr.ver < VER(47)) @@ -1394,6 +1398,11 @@ void ScummEngine::saveOrLoad(Serializer *s) { _imuse->save_or_load(s, this); } + + // Save/load FM-Towns audio status + if (_townsPlayer) + _townsPlayer->saveLoadWithSerializer(s); + // // Save/load the charset renderer state // diff --git a/engines/scumm/saveload.h b/engines/scumm/saveload.h index fafb6b383f..44c8cc7d60 100644 --- a/engines/scumm/saveload.h +++ b/engines/scumm/saveload.h @@ -50,7 +50,7 @@ namespace Scumm { * only saves/loads those which are valid for the version of the savegame * which is being loaded/saved currently. */ -#define CURRENT_VER 80 +#define CURRENT_VER 81 /** * An auxillary macro, used to specify savegame versions. We use this instead diff --git a/engines/scumm/script_v5.cpp b/engines/scumm/script_v5.cpp index 556f5b0af1..39e691c891 100644 --- a/engines/scumm/script_v5.cpp +++ b/engines/scumm/script_v5.cpp @@ -29,6 +29,7 @@ #include "scumm/scumm_v3.h" #include "scumm/scumm_v5.h" #include "scumm/sound.h" +#include "scumm/player_towns.h" #include "scumm/util.h" #include "scumm/verbs.h" @@ -1595,21 +1596,18 @@ void ScummEngine_v5::o5_resourceRoutines() { debug(0, "o5_resourceRoutines %d not yet handled (script %d)", op, vm.slot[_currentScript].number); break; case 35: - // TODO: Might be used to set CD volume in FM-TOWNS Loom - foo = getVarOrDirectByte(PARAM_2); - debug(0, "o5_resourceRoutines %d not yet handled (script %d)", op, vm.slot[_currentScript].number); + if (_townsPlayer) + _townsPlayer->setVolumeCD(getVarOrDirectByte(PARAM_2), resid); break; case 36: - // TODO: Sets the loudness of a sound resource. Used in Indy3 and Zak. foo = getVarOrDirectByte(PARAM_2); bar = fetchScriptByte(); - debug(0, "o5_resourceRoutines %d not yet handled (script %d)", op, vm.slot[_currentScript].number); + if (_townsPlayer) + _townsPlayer->setSoundVolume(resid, foo, bar); break; case 37: - // TODO: Sets the pitch of a sound resource (pitch = foo - center semitones. - // "center" is at 0x32 in the sfx resource (always 0x3C in zak256, but sometimes different in Indy3). - foo = getVarOrDirectByte(PARAM_2); - debug(0, "o5_resourceRoutines %d not yet handled (script %d)", op, vm.slot[_currentScript].number); + if (_townsPlayer) + _townsPlayer->setSoundNote(resid, getVarOrDirectByte(PARAM_2)); break; default: @@ -1981,6 +1979,7 @@ void ScummEngine_v5::o5_startMusic() { break; case 0xFF: // TODO: Might return current CD volume in FM-TOWNS Loom. See also bug #805691. + result = _townsPlayer->getCurrentCdaVolume(); break; default: // TODO: return track length in seconds. We'll have to extend Sound and OSystem for this. diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index 6e3815d314..46708b3c4f 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -48,6 +48,7 @@ #include "scumm/imuse_digi/dimuse.h" #include "scumm/smush/smush_mixer.h" #include "scumm/smush/smush_player.h" +#include "scumm/player_towns.h" #include "scumm/insane/insane.h" #include "scumm/he/animation_he.h" #include "scumm/he/intern_he.h" @@ -146,6 +147,7 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr) _imuse = NULL; _imuseDigital = NULL; _musicEngine = NULL; + _townsPlayer = NULL; _verbs = NULL; _objs = NULL; _sound = NULL; @@ -1757,6 +1759,10 @@ void ScummEngine::setupMusic(int midi) { _musicEngine = new Player_V2CMS(this, _mixer); } else if (_game.platform == Common::kPlatform3DO && _game.heversion <= 62) { // 3DO versions use digital music and sound samples. + } else if (_game.platform == Common::kPlatformFMTowns && (_game.version == 3 || _game.id == GID_MONKEY)) { + _musicEngine = _townsPlayer = new Player_Towns(this, _mixer); + if (!_townsPlayer->init()) + error("Failed to initialize FM-Towns audio driver."); } else if (_game.version >= 3 && _game.heversion <= 62) { MidiDriver *nativeMidiDriver = 0; MidiDriver *adlibMidiDriver = 0; @@ -1806,6 +1812,10 @@ void ScummEngine::syncSoundSettings() { _musicEngine->setMusicVolume(soundVolumeMusic); } + if (_townsPlayer) { + _townsPlayer->setSfxVolume(soundVolumeSfx); + } + _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, soundVolumeSfx); _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, soundVolumeMusic); _mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, soundVolumeSpeech); diff --git a/engines/scumm/scumm.h b/engines/scumm/scumm.h index 8c3df21238..1357bad8cf 100644 --- a/engines/scumm/scumm.h +++ b/engines/scumm/scumm.h @@ -70,6 +70,7 @@ class CharsetRenderer; class IMuse; class IMuseDigital; class MusicEngine; +class Player_Towns; class ScummEngine; class ScummDebugger; class Serializer; @@ -426,6 +427,7 @@ public: IMuse *_imuse; IMuseDigital *_imuseDigital; MusicEngine *_musicEngine; + Player_Towns *_townsPlayer; Sound *_sound; VerbSlot *_verbs; diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index 28f1372746..f12625c9e3 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -31,6 +31,7 @@ #include "scumm/file.h" #include "scumm/imuse/imuse.h" #include "scumm/imuse_digi/dimuse.h" +#include "scumm/player_towns.h" #include "scumm/scumm.h" #include "scumm/sound.h" #include "scumm/util.h" @@ -150,9 +151,10 @@ void Sound::processSoundQueues() { data[0] >> 8, data[0] & 0xFF, data[1], data[2], data[3], data[4], data[5], data[6], data[7]); - if (_vm->_imuse) { + if (_vm->_townsPlayer) + _vm->VAR(_vm->VAR_SOUNDRESULT) = (short)_vm->_townsPlayer->doCommand(num, data); + else if (_vm->_imuse) _vm->VAR(_vm->VAR_SOUNDRESULT) = (short)_vm->_imuse->doCommand(num, data); - } } } _soundQuePos = 0; @@ -312,91 +314,6 @@ void Sound::playSound(int soundID) { stream = Audio::makeRawStream(sound, size, rate, Audio::FLAG_UNSIGNED); _mixer->playStream(Audio::Mixer::kSFXSoundType, NULL, stream, soundID); } - else if ((_vm->_game.platform == Common::kPlatformFMTowns && _vm->_game.version == 3) || READ_BE_UINT32(ptr) == MKID_BE('SOUN') || READ_BE_UINT32(ptr) == MKID_BE('TOWS')) { - - bool tows = READ_BE_UINT32(ptr) == MKID_BE('TOWS'); - if (_vm->_game.version == 3) { - size = READ_LE_UINT32(ptr); - } else { - size = READ_BE_UINT32(ptr + 4) - 2; - if (tows) - size += 8; - ptr += 2; - } - - rate = 11025; - int type = *(ptr + 0x0D); - int numInstruments; - - if (tows) - type = 0; - - switch (type) { - case 0: // Sound effect - numInstruments = *(ptr + 0x14); - if (tows) - numInstruments = 1; - ptr += 0x16; - size -= 0x16; - - while (numInstruments--) { - int waveSize = READ_LE_UINT32(ptr + 0x0C); - int loopStart = READ_LE_UINT32(ptr + 0x10) * 2; - int loopEnd = READ_LE_UINT32(ptr + 0x14) - 1; - rate = READ_LE_UINT32(ptr + 0x18) * 1000 / 0x62; - ptr += 0x20; - size -= 0x20; - if (size < waveSize) { - warning("Wrong wave size in sound #%i: %i", soundID, waveSize); - waveSize = size; - } - sound = (byte *)malloc(waveSize); - for (int x = 0; x < waveSize; x++) { - byte b = *ptr++; - if (b < 0x80) - sound[x] = 0x7F - b; - else - sound[x] = b; - } - size -= waveSize; - - if (loopEnd > 0) { - Audio::SeekableAudioStream *s = Audio::makeRawStream(sound, waveSize, rate, Audio::FLAG_UNSIGNED); - stream = new Audio::SubLoopingAudioStream(s, 0, Audio::Timestamp(0, loopStart, rate), Audio::Timestamp(0, loopEnd, rate)); - } else { - stream = Audio::makeRawStream(sound, waveSize, rate, Audio::FLAG_UNSIGNED); - } - _mixer->playStream(Audio::Mixer::kSFXSoundType, NULL, stream, soundID, 255, 0); - } - break; - case 1: - // Music (Euphony format) - if (_vm->_musicEngine) - _vm->_musicEngine->startSound(soundID); - break; - case 2: // CD track resource - ptr += 0x16; - - if (soundID == _currentCDSound && pollCD() == 1) { - return; - } - - { - int track = ptr[0]; - int loops = ptr[1]; - int start = (ptr[2] * 60 + ptr[3]) * 75 + ptr[4]; - int end = (ptr[5] * 60 + ptr[6]) * 75 + ptr[7]; - - playCDTrack(track, loops == 0xff ? -1 : loops, start, end <= start ? 0 : end - start); - } - - _currentCDSound = soundID; - break; - default: - // All other sound types are ignored - break; - } - } else if ((_vm->_game.id == GID_LOOM) && (_vm->_game.platform == Common::kPlatformMacintosh)) { // Mac version of Loom uses yet another sound format /* @@ -480,6 +397,9 @@ void Sound::playSound(int soundID) { if (_vm->_musicEngine) { _vm->_musicEngine->startSound(soundID); } + + if (_vm->_townsPlayer) + _currentCDSound = _vm->_townsPlayer->getCurrentCdaSound(); } } -- cgit v1.2.3