From 8bb595453b7bce6e219acc94c105a1f76e5ff734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Sun, 11 Nov 2012 13:56:06 +0100 Subject: [PATCH 01/26] SCUMM: Added support for Macintosh music in Monkey Island 1 This is based on the old Mac0-to-General MIDI conversion that we used to do (and which this patch removes), as well as the code for playing the Monkey Island 2 and Fate of Atlantis Macintosh music. I'm not sure how accurate it is, particularly in tempo and volume, but at this point it seems to work pretty well. Looping music is perhaps a bit off, but it was before as well. There is an annoying drawn out note in the music when you're following the shopkeeper, but that appears to have been there in the original as well. --- engines/scumm/module.mk | 1 + engines/scumm/player_v5m.cpp | 425 +++++++++++++++++++++++++++++++++++ engines/scumm/player_v5m.h | 113 ++++++++++ engines/scumm/scumm.cpp | 3 + engines/scumm/sound.cpp | 220 +----------------- 5 files changed, 543 insertions(+), 219 deletions(-) create mode 100644 engines/scumm/player_v5m.cpp create mode 100644 engines/scumm/player_v5m.h diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk index 8499c9bad3c6..474d517cbdd4 100644 --- a/engines/scumm/module.mk +++ b/engines/scumm/module.mk @@ -48,6 +48,7 @@ MODULE_OBJS := \ player_v2cms.o \ player_v3a.o \ player_v4a.o \ + player_v5m.o \ resource_v2.o \ resource_v3.o \ resource_v4.o \ diff --git a/engines/scumm/player_v5m.cpp b/engines/scumm/player_v5m.cpp new file mode 100644 index 000000000000..2ad0d10b5016 --- /dev/null +++ b/engines/scumm/player_v5m.cpp @@ -0,0 +1,425 @@ +/* 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. + * + */ + +/* + From Markus Magnuson (superqult) we got this information: + Mac0 + --- + 4 bytes - 'SOUN' + BE 4 bytes - block length + + 4 bytes - 'Mac0' + BE 4 bytes - (blockLength - 27) + 28 bytes - ??? + + do this three times (once for each channel): + 4 bytes - 'Chan' + BE 4 bytes - channel length + 4 bytes - instrument name (e.g. 'MARI') + + do this for ((chanLength-24)/4) times: + 2 bytes - note duration + 1 byte - note value + 1 byte - note velocity + + 4 bytes - ??? + 4 bytes - 'Loop'/'Done' + 4 bytes - ??? + + 1 byte - 0x09 + --- + + The instruments presumably correspond to the snd resource names in the + Monkey Island executable: + + Instruments + "MARI" - MARIMBA + "PLUC" - PLUCK + "HARM" - HARMONIC + "PIPE" - PIPEORGAN + "TROM" - TROMBONE + "STRI" - STRINGS + "HORN" - HORN + "VIBE" - VIBES + "SHAK" - SHAKUHACHI + "PANP" - PANPIPE + "WHIS" - WHISTLE + "ORGA" - ORGAN3 + "BONG" - BONGO + "BASS" - BASS + + --- + + Note values <= 1 are silent. +*/ + +#include "common/macresman.h" +#include "common/translation.h" +#include "engines/engine.h" +#include "gui/message.h" +#include "scumm/player_v5m.h" +#include "scumm/scumm.h" + +#define RES_SND MKTAG('s', 'n', 'd', ' ') + +namespace Scumm { + +Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer) + : _vm(scumm), + _mixer(mixer), + _sampleRate(_mixer->getOutputRate()), + _soundPlaying(-1) { + + assert(scumm); + assert(mixer); + assert(_vm->_game.id == GID_MONKEY); + + int i; + + for (i = 0; i < 3; i++) { + _channel[i]._looped = false; + _channel[i]._length = 0; + _channel[i]._data = NULL; + _channel[i]._pos = 0; + _channel[i]._pitchModifier = 0; + _channel[i]._velocity = 0; + _channel[i]._remaining = 0; + _channel[i]._notesLeft = false; + } + + _pitchTable[116] = 1664510; + _pitchTable[117] = 1763487; + _pitchTable[118] = 1868350; + _pitchTable[119] = 1979447; + _pitchTable[120] = 2097152; + _pitchTable[121] = 2221855; + _pitchTable[122] = 2353973; + _pitchTable[123] = 2493948; + _pitchTable[124] = 2642246; + _pitchTable[125] = 2799362; + _pitchTable[126] = 2965820; + _pitchTable[127] = 3142177; + for (i = 115; i >= 0; --i) { + _pitchTable[i] = _pitchTable[i + 12] / 2; + } + + setMusicVolume(255); + + Common::MacResManager resource; + if (!resource.exists("Monkey Island")) { + GUI::MessageDialog dialog(_( + "Could not find the 'Monkey Island' Macintosh executable to read the\n" + "instruments from. Music will be disabled."), _("OK")); + dialog.runModal(); + return; + } + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +Player_V5M::~Player_V5M() { + Common::StackLock lock(_mutex); + _mixer->stopHandle(_soundHandle); + stopAllSounds_Internal(); +} + +void Player_V5M::setMusicVolume(int vol) { + debug(5, "Player_V5M::setMusicVolume(%d)", vol); +} + +void Player_V5M::stopAllSounds_Internal() { + _soundPlaying = -1; + for (int i = 0; i < 3; i++) { + // The channel data is managed by the resource manager, so + // don't delete that. + delete[] _channel[i]._instrument._data; + _channel[i]._instrument._data = NULL; + + _channel[i]._remaining = 0; + _channel[i]._notesLeft = false; + } +} + +void Player_V5M::stopAllSounds() { + Common::StackLock lock(_mutex); + debug(5, "Player_V5M::stopAllSounds()"); + stopAllSounds_Internal(); +} + +void Player_V5M::stopSound(int nr) { + Common::StackLock lock(_mutex); + debug(5, "Player_V5M::stopSound(%d)", nr); + + if (nr == _soundPlaying) { + stopAllSounds(); + } +} + +void Player_V5M::startSound(int nr) { + Common::StackLock lock(_mutex); + debug(5, "Player_V5M::startSound(%d)", nr); + + Common::MacResManager resource; + if (!resource.open("Monkey Island")) { + return; + } + + const byte *ptr = _vm->getResourceAddress(rtSound, nr); + assert(ptr); + + const byte *src = ptr; + uint i; + + src += 8; + // TODO: Decipher the unknown bytes in the header. For now, skip 'em + src += 28; + + Common::MacResIDArray idArray = resource.getResIDArray(RES_SND); + + // Load the three channels and their instruments + for (i = 0; i < 3; i++) { + assert(READ_BE_UINT32(src) == MKTAG('C', 'h', 'a', 'n')); + uint32 len = READ_BE_UINT32(src + 4); + uint32 instrument = READ_BE_UINT32(src + 8); + + _channel[i]._length = len - 24; + _channel[i]._data = src + 12; + _channel[i]._looped = (READ_BE_UINT32(src + len - 8) == MKTAG('L', 'o', 'o', 'p')); + _channel[i]._pos = 0; + _channel[i]._pitchModifier = 0; + _channel[i]._velocity = 0; + _channel[i]._remaining = 0; + _channel[i]._notesLeft = true; + + for (uint j = 0; j < idArray.size(); j++) { + Common::String name = resource.getResName(RES_SND, idArray[j]); + if (instrument == READ_BE_UINT32(name.c_str())) { + debug(6, "Channel %d: Loading instrument '%s'", i, name.c_str()); + Common::SeekableReadStream *stream = resource.getResource(RES_SND, idArray[j]); + + if (!_channel[i].loadInstrument(stream)) { + resource.close(); + return; + } + + break; + } + } + + src += len; + } + + resource.close(); + _soundPlaying = nr; +} + +bool Player_V5M::Channel::loadInstrument(Common::SeekableReadStream *stream) { + // Load the sound + uint16 soundType = stream->readUint16BE(); + if (soundType != 1) { + warning("Player_V5M::loadInstrument: Unsupported sound type %d", soundType); + return false; + } + uint16 typeCount = stream->readUint16BE(); + if (typeCount != 1) { + warning("Player_V5M::loadInstrument: Unsupported data type count %d", typeCount); + return false; + } + uint16 dataType = stream->readUint16BE(); + if (dataType != 5) { + warning("Player_V5M::loadInstrument: Unsupported data type %d", dataType); + return false; + } + + stream->readUint32BE(); // initialization option + + uint16 cmdCount = stream->readUint16BE(); + if (cmdCount != 1) { + warning("Player_V5M::loadInstrument: Unsupported command count %d", cmdCount); + return false; + } + uint16 command = stream->readUint16BE(); + if (command != 0x8050 && command != 0x8051) { + warning("Player_V5M::loadInstrument: Unsupported command 0x%04X", command); + return false; + } + + stream->readUint16BE(); // 0 + uint32 soundHeaderOffset = stream->readUint32BE(); + + stream->seek(soundHeaderOffset); + + uint32 soundDataOffset = stream->readUint32BE(); + uint32 size = stream->readUint32BE(); + uint32 rate = stream->readUint32BE() >> 16; + uint32 loopStart = stream->readUint32BE(); + uint32 loopEnd = stream->readUint32BE(); + byte encoding = stream->readByte(); + byte baseFreq = stream->readByte(); + + if (encoding != 0) { + warning("Player_V5M::loadInstrument: Unsupported encoding %d", encoding); + return false; + } + + stream->skip(soundDataOffset); + + byte *data = new byte[size]; + stream->read(data, size); + + _instrument._data = data; + _instrument._size = size; + _instrument._rate = rate; + _instrument._loopStart = loopStart; + _instrument._loopEnd = loopEnd; + _instrument._baseFreq = baseFreq; + + return true; +} + +int Player_V5M::getMusicTimer() { + return 0; +} + +int Player_V5M::getSoundStatus(int nr) const { + return _soundPlaying == nr; +} + +int Player_V5M::readBuffer(int16 *data, const int numSamples) { + Common::StackLock lock(_mutex); + + memset(data, 0, numSamples * 2); + if (_soundPlaying == -1) { + return numSamples; + } + + bool notesLeft = false; + + for (int i = 0; i < 3; i++) { + uint samplesLeft = numSamples; + int16 *ptr = data; + + while (samplesLeft > 0) { + int generated; + if (_channel[i]._remaining == 0) { + uint16 duration; + byte note, velocity; + if (_channel[i].getNextNote(duration, note, velocity)) { + if (note > 1) { + const int pitchIdx = note + 60 - _channel[i]._instrument._baseFreq; + assert(pitchIdx >= 0); + // I don't want to use floating-point arithmetics here, + // but I ran into overflow problems with the church + // music. It's only once per note, so it should be ok. + double mult = (double)(_channel[i]._instrument._rate) / (double)_sampleRate; + _channel[i]._pitchModifier = mult * _pitchTable[pitchIdx]; + _channel[i]._velocity = velocity; + } else { + _channel[i]._pitchModifier = 0; + _channel[i]._velocity = 0; + } + + // The correct formula should be: + // + // (duration * 473 * _sampleRate) / (4 * 480 * 480) + // + // But that's likely to cause integer overflow, so + // we do it in two steps and hope that the rounding + // error won't be noticeable. + // + // The original code is a bit unclear on if it should + // be 473 or 437, but since the comments indicated + // 473 I'm assuming 437 was a typo. + _channel[i]._remaining = (duration * _sampleRate) / (4 * 480); + _channel[i]._remaining = (_channel[i]._remaining * 473) / 480; + } else { + _channel[i]._pitchModifier = 0; + _channel[i]._velocity = 0; + _channel[i]._remaining = samplesLeft; + } + } + generated = MIN(_channel[i]._remaining, samplesLeft); + if (_channel[i]._velocity != 0) { + _channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated); + } + ptr += generated; + samplesLeft -= generated; + _channel[i]._remaining -= generated; + } + + if (_channel[i]._notesLeft) { + notesLeft = true; + } + } + + if (!notesLeft) { + stopAllSounds_Internal(); + } + + return numSamples; +} + +bool Player_V5M::Channel::getNextNote(uint16 &duration, byte ¬e, byte &velocity) { + _instrument.newNote(); + if (_pos >= _length) { + if (!_looped) { + _notesLeft = false; + return false; + } + // FIXME: Jamieson630: The jump seems to be happening + // too quickly! There should maybe be a pause after + // the last Note Off? But I couldn't find one in the + // MI1 Lookout music, where I was hearing problems. + _pos = 0; + } + duration = READ_BE_UINT16(&_data[_pos]); + note = _data[_pos + 2]; + velocity = _data[_pos + 3]; + _pos += 4; + return true; +} + +void Player_V5M::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples) { + int samplesLeft = numSamples; + while (samplesLeft) { + _subPos += pitchModifier; + while (_subPos >= 0x10000) { + _subPos -= 0x10000; + _pos++; + if (_pos >= _loopEnd) { + _pos = _loopStart; + } + } + + int sample = *data + ((_data[_pos] - 129) * 128 * volume) / 255; + if (sample > 32767) { + sample = 32767; + } else if (sample < -32768) { + sample = -32768; + } + + *data++ = sample; // (_data[_pos] * 127) / 100; + samplesLeft--; + } +} + +} // End of namespace Scumm diff --git a/engines/scumm/player_v5m.h b/engines/scumm/player_v5m.h new file mode 100644 index 000000000000..c245554800bf --- /dev/null +++ b/engines/scumm/player_v5m.h @@ -0,0 +1,113 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYER_V5M_H +#define SCUMM_PLAYER_V5M_H + +#include "common/scummsys.h" +#include "common/util.h" +#include "common/mutex.h" +#include "scumm/music.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" + +class Mixer; + +namespace Scumm { + +class ScummEngine; + +/** + * Scumm V5 Macintosh music driver. + */ + class Player_V5M : public Audio::AudioStream, public MusicEngine { +public: + Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer); + virtual ~Player_V5M(); + + // MusicEngine API + virtual void setMusicVolume(int vol); + virtual void startSound(int sound); + virtual void stopSound(int sound); + virtual void stopAllSounds(); + virtual int getMusicTimer(); + virtual int getSoundStatus(int sound) const; + + // AudioStream API + virtual int readBuffer(int16 *buffer, const int numSamples); + virtual bool isStereo() const { return false; } + virtual bool endOfData() const { return false; } + virtual int getRate() const { return _sampleRate; } + +private: + ScummEngine *const _vm; + Common::Mutex _mutex; + Audio::Mixer *const _mixer; + Audio::SoundHandle _soundHandle; + const uint32 _sampleRate; + int _soundPlaying; + + void stopAllSounds_Internal(); + + struct Instrument { + byte *_data; + uint32 _size; + uint32 _rate; + uint32 _loopStart; + uint32 _loopEnd; + byte _baseFreq; + + uint _pos; + uint _subPos; + + void newNote() { + _pos = 0; + _subPos = 0; + } + + void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples); + }; + + struct Channel { + Instrument _instrument; + bool _looped; + uint32 _length; + const byte *_data; + + uint _pos; + int _pitchModifier; + byte _velocity; + uint32 _remaining; + + bool _notesLeft; + + bool loadInstrument(Common::SeekableReadStream *stream); + bool getNextNote(uint16 &duration, byte &value, byte &velocity); + }; + + Channel _channel[3]; + int _pitchTable[128]; +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index 2c79fb8de08d..67ca071a7632 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -62,6 +62,7 @@ #include "scumm/player_v2a.h" #include "scumm/player_v3a.h" #include "scumm/player_v4a.h" +#include "scumm/player_v5m.h" #include "scumm/resource.h" #include "scumm/he/resource_he.h" #include "scumm/scumm_v0.h" @@ -1819,6 +1820,8 @@ void ScummEngine::setupMusic(int midi) { #endif } else if (_game.platform == Common::kPlatformAmiga && _game.version <= 4) { _musicEngine = new Player_V4A(this, _mixer); + } else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_MONKEY) { + _musicEngine = new Player_V5M(this, _mixer); } else if (_game.id == GID_MANIAC && _game.version == 1) { _musicEngine = new Player_V1(this, _mixer, MidiDriver::getMusicType(dev) != MT_PCSPK); } else if (_game.version <= 2) { diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index a1cecfa0b332..57345126d31e 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -1086,9 +1086,6 @@ void Sound::saveLoadWithSerializer(Serializer *ser) { #pragma mark --- Sound resource handling --- #pragma mark - -static void convertMac0Resource(ResourceManager *res, ResId idx, byte *src_ptr, int size); - - /* * TODO: The way we handle sound/music resources really is one huge hack. * We probably should reconsider how we do this, and maybe come up with a @@ -1208,11 +1205,9 @@ int ScummEngine::readSoundResource(ResId idx) { case MKTAG('M','a','c','0'): _fileHandle->seek(-12, SEEK_CUR); total_size = _fileHandle->readUint32BE() - 8; - ptr = (byte *)calloc(total_size, 1); + ptr = _res->createResource(rtSound, idx, total_size); _fileHandle->read(ptr, total_size); //dumpResource("sound-", idx, ptr); - convertMac0Resource(_res, idx, ptr, total_size); - free(ptr); return 1; case MKTAG('M','a','c','1'): @@ -1445,219 +1440,6 @@ static byte *writeVLQ(byte *ptr, int value) { return ptr; } -static byte Mac0ToGMInstrument(uint32 type, int &transpose) { - transpose = 0; - switch (type) { - case MKTAG('M','A','R','I'): return 12; - case MKTAG('P','L','U','C'): return 45; - case MKTAG('H','A','R','M'): return 22; - case MKTAG('P','I','P','E'): return 19; - case MKTAG('T','R','O','M'): transpose = -12; return 57; - case MKTAG('S','T','R','I'): return 48; - case MKTAG('H','O','R','N'): return 60; - case MKTAG('V','I','B','E'): return 11; - case MKTAG('S','H','A','K'): return 77; - case MKTAG('P','A','N','P'): return 75; - case MKTAG('W','H','I','S'): return 76; - case MKTAG('O','R','G','A'): return 17; - case MKTAG('B','O','N','G'): return 115; - case MKTAG('B','A','S','S'): transpose = -24; return 35; - default: - error("Unknown Mac0 instrument %s found", tag2str(type)); - } -} - -static void convertMac0Resource(ResourceManager *res, ResId idx, byte *src_ptr, int size) { - /* - From Markus Magnuson (superqult) we got this information: - Mac0 - --- - 4 bytes - 'SOUN' - BE 4 bytes - block length - - 4 bytes - 'Mac0' - BE 4 bytes - (blockLength - 27) - 28 bytes - ??? - - do this three times (once for each channel): - 4 bytes - 'Chan' - BE 4 bytes - channel length - 4 bytes - instrument name (e.g. 'MARI') - - do this for ((chanLength-24)/4) times: - 2 bytes - note duration - 1 byte - note value - 1 byte - note velocity - - 4 bytes - ??? - 4 bytes - 'Loop'/'Done' - 4 bytes - ??? - - 1 byte - 0x09 - --- - - Instruments (General Midi): - "MARI" - Marimba (12) - "PLUC" - Pizzicato Strings (45) - "HARM" - Harmonica (22) - "PIPE" - Church Organ? (19) or Flute? (73) or Bag Pipe (109) - "TROM" - Trombone (57) - "STRI" - String Ensemble (48 or 49) - "HORN" - French Horn? (60) or English Horn? (69) - "VIBE" - Vibraphone (11) - "SHAK" - Shakuhachi? (77) - "PANP" - Pan Flute (75) - "WHIS" - Whistle (78) / Bottle (76) - "ORGA" - Drawbar Organ (16; but could also be 17-20) - "BONG" - Woodblock? (115) - "BASS" - Bass (32-39) - - - Now the task could be to convert this into MIDI, to be fed into iMuse. - Or we do something similiar to what is done in Player_V3, assuming - we can identify SFX in the MI datafiles for each of the instruments - listed above. - */ - -#if 0 - byte *ptr = _res->createResource(rtSound, idx, size); - memcpy(ptr, src_ptr, size); -#else - const int ppqn = 480; - byte *ptr, *start_ptr; - - int total_size = 0; - total_size += kMIDIHeaderSize; // Header - total_size += 7; // Tempo META - total_size += 3 * 3; // Three program change mesages - total_size += 22; // Possible jump SysEx - total_size += 5; // EOT META - - int i, len; - byte track_instr[3]; - byte *track_data[3]; - int track_len[3]; - int track_transpose[3]; - bool looped = false; - - src_ptr += 8; - // TODO: Decipher the unknown bytes in the header. For now, skip 'em - src_ptr += 28; - - // Parse the three channels - for (i = 0; i < 3; i++) { - assert(READ_BE_UINT32(src_ptr) == MKTAG('C','h','a','n')); - len = READ_BE_UINT32(src_ptr + 4); - track_len[i] = len - 24; - track_instr[i] = Mac0ToGMInstrument(READ_BE_UINT32(src_ptr + 8), track_transpose[i]); - track_data[i] = src_ptr + 12; - src_ptr += len; - looped = (READ_BE_UINT32(src_ptr - 8) == MKTAG('L','o','o','p')); - - // For each note event, we need up to 6 bytes for the - // Note On (3 VLQ, 3 event), and 6 bytes for the Note - // Off (3 VLQ, 3 event). So 12 bytes total. - total_size += 12 * track_len[i]; - } - assert(*src_ptr == 0x09); - - // Create sound resource - start_ptr = res->createResource(rtSound, idx, total_size); - - // Insert MIDI header - ptr = writeMIDIHeader(start_ptr, "GMD ", ppqn, total_size); - - // Write a tempo change Meta event - // 473 / 4 Hz, convert to micro seconds. - uint32 dw = 1000000 * 437 / 4 / ppqn; // 1000000 * ppqn * 4 / 473; - memcpy(ptr, "\x00\xFF\x51\x03", 4); ptr += 4; - *ptr++ = (byte)((dw >> 16) & 0xFF); - *ptr++ = (byte)((dw >> 8) & 0xFF); - *ptr++ = (byte)(dw & 0xFF); - - // Insert program change messages - *ptr++ = 0; // VLQ - *ptr++ = 0xC0; - *ptr++ = track_instr[0]; - *ptr++ = 0; // VLQ - *ptr++ = 0xC1; - *ptr++ = track_instr[1]; - *ptr++ = 0; // VLQ - *ptr++ = 0xC2; - *ptr++ = track_instr[2]; - - // And now, the actual composition. Please turn all cell phones - // and pagers off during the performance. Thank you. - uint16 nextTime[3] = { 1, 1, 1 }; - int stage[3] = { 0, 0, 0 }; - - while (track_len[0] | track_len[1] | track_len[2]) { - int best = -1; - uint16 bestTime = 0xFFFF; - for (i = 0; i < 3; ++i) { - if (track_len[i] && nextTime[i] < bestTime) { - bestTime = nextTime[i]; - best = i; - } - } - assert (best != -1); - - if (!stage[best]) { - // We are STARTING this event. - if (track_data[best][2] > 1) { - // Note On - ptr = writeVLQ(ptr, nextTime[best]); - *ptr++ = 0x90 | best; - *ptr++ = track_data[best][2] + track_transpose[best]; - *ptr++ = track_data[best][3] * 127 / 100; // Scale velocity - for (i = 0; i < 3; ++i) - nextTime[i] -= bestTime; - } - nextTime[best] += READ_BE_UINT16 (track_data[best]); - stage[best] = 1; - } else { - // We are ENDING this event. - if (track_data[best][2] > 1) { - // There was a Note On, so do a Note Off - ptr = writeVLQ(ptr, nextTime[best]); - *ptr++ = 0x80 | best; - *ptr++ = track_data[best][2] + track_transpose[best]; - *ptr++ = track_data[best][3] * 127 / 100; // Scale velocity - for (i = 0; i < 3; ++i) - nextTime[i] -= bestTime; - } - track_data[best] += 4; - track_len[best] -= 4; - stage[best] = 0; - } - } - - // Is this a looped song? If so, effect a loop by - // using the S&M maybe_jump SysEx command. - // FIXME: Jamieson630: The jump seems to be happening - // too quickly! There should maybe be a pause after - // the last Note Off? But I couldn't find one in the - // MI1 Lookout music, where I was hearing problems. - if (looped) { - memcpy(ptr, "\x00\xf0\x13\x7d\x30\00", 6); ptr += 6; // maybe_jump - memcpy(ptr, "\x00\x00", 2); ptr += 2; // cmd -> 0 means always jump - memcpy(ptr, "\x00\x00\x00\x00", 4); ptr += 4; // track -> 0 (only track) - memcpy(ptr, "\x00\x00\x00\x01", 4); ptr += 4; // beat -> 1 (first beat) - memcpy(ptr, "\x00\x00\x00\x01", 4); ptr += 4; // tick -> 1 - memcpy(ptr, "\x00\xf7", 2); ptr += 2; // SysEx end marker - } - - // Insert end of song META - memcpy(ptr, "\x00\xff\x2f\x00\x00", 5); ptr += 5; - - assert(ptr <= start_ptr + total_size); - - // Rewrite MIDI header, this time with true size - total_size = ptr - start_ptr; - ptr = writeMIDIHeader(start_ptr, "GMD ", ppqn, total_size); -#endif -} - static void convertADResource(ResourceManager *res, const GameSettings& game, ResId idx, byte *src_ptr, int size) { // We will ignore the PPQN in the original resource, because // it's invalid anyway. We use a constant PPQN of 480. From 107a1af125cc77082cc205fcfa1b86c7509c26c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Mon, 12 Nov 2012 22:10:10 +0100 Subject: [PATCH 02/26] SCUMM: Initialize the Macintosh MI1 instruments, along with the channels. Otherwise it may crash if you quit before any instruments have been loaded. Oops. --- engines/scumm/player_v5m.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/engines/scumm/player_v5m.cpp b/engines/scumm/player_v5m.cpp index 2ad0d10b5016..640361cb61a4 100644 --- a/engines/scumm/player_v5m.cpp +++ b/engines/scumm/player_v5m.cpp @@ -104,6 +104,14 @@ Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer) _channel[i]._velocity = 0; _channel[i]._remaining = 0; _channel[i]._notesLeft = false; + _channel[i]._instrument._data = NULL; + _channel[i]._instrument._size = 0; + _channel[i]._instrument._rate = 0; + _channel[i]._instrument._loopStart = 0; + _channel[i]._instrument._loopEnd = 0; + _channel[i]._instrument._baseFreq = 0; + _channel[i]._instrument._pos = 0; + _channel[i]._instrument._subPos = 0; } _pitchTable[116] = 1664510; From b1d10e6a62ef49994e78b3e2c86b27050f2f0938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Tue, 13 Nov 2012 22:49:12 +0100 Subject: [PATCH 03/26] SCUMM: Add support for Mac Loom music and sound It turns out that playing the Mac Loom music isn't particularly different from playing the Monkey Island 1 music, except the data layout is a bit different and there's no per-note volume. --- engines/scumm/module.mk | 1 + engines/scumm/player_v3m.cpp | 433 +++++++++++++++++++++++++++++++++++ engines/scumm/player_v3m.h | 113 +++++++++ engines/scumm/scumm.cpp | 3 + engines/scumm/sound.cpp | 23 +- 5 files changed, 553 insertions(+), 20 deletions(-) create mode 100644 engines/scumm/player_v3m.cpp create mode 100644 engines/scumm/player_v3m.h diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk index 474d517cbdd4..57a495a77234 100644 --- a/engines/scumm/module.mk +++ b/engines/scumm/module.mk @@ -47,6 +47,7 @@ MODULE_OBJS := \ player_v2base.o \ player_v2cms.o \ player_v3a.o \ + player_v3m.o \ player_v4a.o \ player_v5m.o \ resource_v2.o \ diff --git a/engines/scumm/player_v3m.cpp b/engines/scumm/player_v3m.cpp new file mode 100644 index 000000000000..081f48f25901 --- /dev/null +++ b/engines/scumm/player_v3m.cpp @@ -0,0 +1,433 @@ +/* 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. + * + */ + +/* + We have the following information from Lars Christensen (lechimp) and + Jamieson Christian (jamieson630): + + RESOURCE DATA + LE 2 bytes Resource size + 2 bytes Unknown + 2 bytes 'so' + 14 bytes Unknown + BE 2 bytes Instrument for Stream 1 + BE 2 bytes Instrument for Stream 2 + BE 2 bytes Instrument for Stream 3 + BE 2 bytes Instrument for Stream 4 + BE 2 bytes Instrument for Stream 5 + BE 2 bytes Offset to Stream 1 + BE 2 bytes Offset to Stream 2 + BE 2 bytes Offset to Stream 3 + BE 2 bytes Offset to Stream 4 + BE 2 bytes Offset to Stream 5 + ? bytes The streams + + STREAM DATA + BE 2 bytes Unknown (always 1?) + 2 bytes Unknown (always 0?) + BE 2 bytes Number of events in stream + ? bytes Stream data + + Each stream event is exactly 3 bytes, therefore one can + assert that numEvents == (streamSize - 6) / 3. The + polyphony of a stream appears to be 1; in other words, only + one note at a time can be playing in each stream. The next + event is not executed until the current note (or rest) is + finished playing; therefore, note duration also serves as the + time delta between events. + + FOR EACH EVENTS + BE 2 bytes Note duration + 1 byte Note number to play (0 = rest/silent) + + Oh, and quick speculation -- Stream 1 may be used for a + single-voice interleaved version of the music, where Stream 2- + 5 represent a version of the music in up to 4-voice + polyphony, one voice per stream. I postulate thus because + the first stream of the Mac Loom theme music contains + interleaved voices, whereas the second stream seemed to + contain only the pizzicato bottom-end harp. Stream 5, in this + example, is empty, so if my speculation is correct, this + particular musical number supports 3-voice polyphony at + most. I must check out Streams 3 and 4 to see what they + contain. + + ========== + + The instruments appear to be identified by their resource IDs: + + 1000 Dual Harp + 10895 harp1 + 11445 strings1 + 11548 silent + 13811 staff1 + 15703 brass1 + 16324 flute1 + 25614 accordian 1 + 28110 f horn1 + 29042 bassoon1 +*/ + +#include "common/macresman.h" +#include "common/translation.h" +#include "engines/engine.h" +#include "gui/message.h" +#include "scumm/player_v3m.h" +#include "scumm/scumm.h" + +#define RES_SND MKTAG('s', 'n', 'd', ' ') + +namespace Scumm { + +Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer) + : _vm(scumm), + _mixer(mixer), + _sampleRate(_mixer->getOutputRate()), + _soundPlaying(-1) { + + assert(scumm); + assert(mixer); + assert(_vm->_game.id == GID_LOOM); + + int i; + + for (i = 0; i < 5; i++) { + _channel[i]._looped = false; + _channel[i]._length = 0; + _channel[i]._data = NULL; + _channel[i]._pos = 0; + _channel[i]._pitchModifier = 0; + _channel[i]._velocity = 0; + _channel[i]._remaining = 0; + _channel[i]._notesLeft = false; + _channel[i]._instrument._data = NULL; + _channel[i]._instrument._size = 0; + _channel[i]._instrument._rate = 0; + _channel[i]._instrument._loopStart = 0; + _channel[i]._instrument._loopEnd = 0; + _channel[i]._instrument._baseFreq = 0; + _channel[i]._instrument._pos = 0; + _channel[i]._instrument._subPos = 0; + } + + _pitchTable[116] = 1664510; + _pitchTable[117] = 1763487; + _pitchTable[118] = 1868350; + _pitchTable[119] = 1979447; + _pitchTable[120] = 2097152; + _pitchTable[121] = 2221855; + _pitchTable[122] = 2353973; + _pitchTable[123] = 2493948; + _pitchTable[124] = 2642246; + _pitchTable[125] = 2799362; + _pitchTable[126] = 2965820; + _pitchTable[127] = 3142177; + for (i = 115; i >= 0; --i) { + _pitchTable[i] = _pitchTable[i + 12] / 2; + } + + setMusicVolume(255); + + Common::MacResManager resource; + // \252 is a trademark glyph. I don't think we should assume that it + // will be preserved when copying from the Mac disk. + if (!resource.exists("Loom\252") && !resource.exists("Loom")) { + GUI::MessageDialog dialog(_( + "Could not find the 'Loom' Macintosh executable to read the\n" + "instruments from. Music will be disabled."), _("OK")); + dialog.runModal(); + return; + } + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +Player_V3M::~Player_V3M() { + Common::StackLock lock(_mutex); + _mixer->stopHandle(_soundHandle); + stopAllSounds_Internal(); +} + +void Player_V3M::setMusicVolume(int vol) { + debug(5, "Player_V3M::setMusicVolume(%d)", vol); +} + +void Player_V3M::stopAllSounds_Internal() { + _soundPlaying = -1; + for (int i = 0; i < 3; i++) { + // The channel data is managed by the resource manager, so + // don't delete that. + delete[] _channel[i]._instrument._data; + _channel[i]._instrument._data = NULL; + + _channel[i]._remaining = 0; + _channel[i]._notesLeft = false; + } +} + +void Player_V3M::stopAllSounds() { + Common::StackLock lock(_mutex); + debug(5, "Player_V3M::stopAllSounds()"); + stopAllSounds_Internal(); +} + +void Player_V3M::stopSound(int nr) { + Common::StackLock lock(_mutex); + debug(5, "Player_V3M::stopSound(%d)", nr); + + if (nr == _soundPlaying) { + stopAllSounds(); + } +} + +void Player_V3M::startSound(int nr) { + Common::StackLock lock(_mutex); + debug(5, "Player_V3M::startSound(%d)", nr); + + Common::MacResManager resource; + if (!resource.open("Loom\252") && !resource.open("Loom")) { + return; + } + + const byte *ptr = _vm->getResourceAddress(rtSound, nr); + assert(ptr); + + const byte *src = ptr; + uint i; + + assert(src[4] == 's' && src[5] == 'o'); + for (i = 0; i < 5; i++) { + int instrument = READ_BE_UINT16(src + 20 + 2 * i); + int offset = READ_BE_UINT16(src + 30 + 2 * i); + _channel[i]._looped = false; + _channel[i]._length = READ_BE_UINT16(src + offset + 4) * 3; + _channel[i]._data = src + offset + 6; + _channel[i]._pos = 0; + _channel[i]._pitchModifier = 0; + _channel[i]._velocity = 0; + _channel[i]._remaining = 0; + _channel[i]._notesLeft = true; + + Common::SeekableReadStream *stream = resource.getResource(RES_SND, instrument); + if (_channel[i].loadInstrument(stream)) { + debug(6, "Player_V3M::startSound: Channel %d - Loaded Instrument %d (%s)", i, instrument, resource.getResName(RES_SND, instrument).c_str()); + } else { + resource.close(); + return; + } + } + + resource.close(); + _soundPlaying = nr; +} + +bool Player_V3M::Channel::loadInstrument(Common::SeekableReadStream *stream) { + // Load the sound + uint16 soundType = stream->readUint16BE(); + if (soundType != 1) { + warning("Player_V3M::loadInstrument: Unsupported sound type %d", soundType); + return false; + } + uint16 typeCount = stream->readUint16BE(); + if (typeCount != 1) { + warning("Player_V3M::loadInstrument: Unsupported data type count %d", typeCount); + return false; + } + uint16 dataType = stream->readUint16BE(); + if (dataType != 5) { + warning("Player_V3M::loadInstrument: Unsupported data type %d", dataType); + return false; + } + + stream->readUint32BE(); // initialization option + + uint16 cmdCount = stream->readUint16BE(); + if (cmdCount != 1) { + warning("Player_V3M::loadInstrument: Unsupported command count %d", cmdCount); + return false; + } + uint16 command = stream->readUint16BE(); + if (command != 0x8050 && command != 0x8051) { + warning("Player_V3M::loadInstrument: Unsupported command 0x%04X", command); + return false; + } + + stream->readUint16BE(); // 0 + uint32 soundHeaderOffset = stream->readUint32BE(); + + stream->seek(soundHeaderOffset); + + uint32 soundDataOffset = stream->readUint32BE(); + uint32 size = stream->readUint32BE(); + uint32 rate = stream->readUint32BE() >> 16; + uint32 loopStart = stream->readUint32BE(); + uint32 loopEnd = stream->readUint32BE(); + byte encoding = stream->readByte(); + byte baseFreq = stream->readByte(); + + if (encoding != 0) { + warning("Player_V3M::loadInstrument: Unsupported encoding %d", encoding); + return false; + } + + stream->skip(soundDataOffset); + + byte *data = new byte[size]; + stream->read(data, size); + + _instrument._data = data; + _instrument._size = size; + _instrument._rate = rate; + _instrument._loopStart = loopStart; + _instrument._loopEnd = loopEnd; + _instrument._baseFreq = baseFreq; + + return true; +} + +int Player_V3M::getMusicTimer() { + return 0; +} + +int Player_V3M::getSoundStatus(int nr) const { + return _soundPlaying == nr; +} + +int Player_V3M::readBuffer(int16 *data, const int numSamples) { + Common::StackLock lock(_mutex); + + memset(data, 0, numSamples * 2); + if (_soundPlaying == -1) { + return numSamples; + } + + bool notesLeft = false; + + // Channel 0 seems to be what was played on low-end macs, that couldn't + // handle multi-channel music and play the game at the same time. I'm + // not sure if stream 4 is ever used, but let's use it just in case. + + for (int i = 1; i < 5; i++) { + uint samplesLeft = numSamples; + int16 *ptr = data; + + while (samplesLeft > 0) { + int generated; + if (_channel[i]._remaining == 0) { + uint16 duration; + byte note, velocity; + if (_channel[i].getNextNote(duration, note, velocity)) { + if (note > 0) { + const int pitchIdx = note + 60 - _channel[i]._instrument._baseFreq; + assert(pitchIdx >= 0); + // I don't want to use floating-point arithmetics here, + // but I ran into overflow problems with the church + // music. It's only once per note, so it should be ok. + double mult = (double)(_channel[i]._instrument._rate) / (double)_sampleRate; + _channel[i]._pitchModifier = mult * _pitchTable[pitchIdx]; + _channel[i]._velocity = velocity; + } else { + _channel[i]._pitchModifier = 0; + _channel[i]._velocity = 0; + } + + // The correct formula should be: + // + // (duration * 473 * _sampleRate) / (4 * 480 * 480) + // + // But that's likely to cause integer overflow, so + // we do it in two steps and hope that the rounding + // error won't be noticeable. + // + // The original code is a bit unclear on if it should + // be 473 or 437, but since the comments indicated + // 473 I'm assuming 437 was a typo. + _channel[i]._remaining = (duration * _sampleRate) / (4 * 480); + _channel[i]._remaining = (_channel[i]._remaining * 473) / 480; + } else { + _channel[i]._pitchModifier = 0; + _channel[i]._velocity = 0; + _channel[i]._remaining = samplesLeft; + } + } + generated = MIN(_channel[i]._remaining, samplesLeft); + if (_channel[i]._velocity != 0) { + _channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated); + } + ptr += generated; + samplesLeft -= generated; + _channel[i]._remaining -= generated; + } + + if (_channel[i]._notesLeft) { + notesLeft = true; + } + } + + if (!notesLeft) { + stopAllSounds_Internal(); + } + + return numSamples; +} + +bool Player_V3M::Channel::getNextNote(uint16 &duration, byte ¬e, byte &velocity) { + _instrument.newNote(); + if (_pos >= _length) { + if (!_looped) { + _notesLeft = false; + return false; + } + _pos = 0; + } + duration = READ_BE_UINT16(&_data[_pos]); + note = _data[_pos + 2]; + velocity = 127; + _pos += 3; + return true; +} + +void Player_V3M::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples) { + int samplesLeft = numSamples; + while (samplesLeft) { + _subPos += pitchModifier; + while (_subPos >= 0x10000) { + _subPos -= 0x10000; + _pos++; + if (_pos >= _loopEnd) { + _pos = _loopStart; + } + } + + int sample = *data + ((_data[_pos] - 129) * 128 * volume) / 255; + if (sample > 32767) { + sample = 32767; + } else if (sample < -32768) { + sample = -32768; + } + + *data++ = sample; // (_data[_pos] * 127) / 100; + samplesLeft--; + } +} + +} // End of namespace Scumm diff --git a/engines/scumm/player_v3m.h b/engines/scumm/player_v3m.h new file mode 100644 index 000000000000..3465621ca39a --- /dev/null +++ b/engines/scumm/player_v3m.h @@ -0,0 +1,113 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYER_V3M_H +#define SCUMM_PLAYER_V3M_H + +#include "common/scummsys.h" +#include "common/util.h" +#include "common/mutex.h" +#include "scumm/music.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" + +class Mixer; + +namespace Scumm { + +class ScummEngine; + +/** + * Scumm V3 Macintosh music driver. + */ + class Player_V3M : public Audio::AudioStream, public MusicEngine { +public: + Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer); + virtual ~Player_V3M(); + + // MusicEngine API + virtual void setMusicVolume(int vol); + virtual void startSound(int sound); + virtual void stopSound(int sound); + virtual void stopAllSounds(); + virtual int getMusicTimer(); + virtual int getSoundStatus(int sound) const; + + // AudioStream API + virtual int readBuffer(int16 *buffer, const int numSamples); + virtual bool isStereo() const { return false; } + virtual bool endOfData() const { return false; } + virtual int getRate() const { return _sampleRate; } + +private: + ScummEngine *const _vm; + Common::Mutex _mutex; + Audio::Mixer *const _mixer; + Audio::SoundHandle _soundHandle; + const uint32 _sampleRate; + int _soundPlaying; + + void stopAllSounds_Internal(); + + struct Instrument { + byte *_data; + uint32 _size; + uint32 _rate; + uint32 _loopStart; + uint32 _loopEnd; + byte _baseFreq; + + uint _pos; + uint _subPos; + + void newNote() { + _pos = 0; + _subPos = 0; + } + + void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples); + }; + + struct Channel { + Instrument _instrument; + bool _looped; + uint32 _length; + const byte *_data; + + uint _pos; + int _pitchModifier; + byte _velocity; + uint32 _remaining; + + bool _notesLeft; + + bool loadInstrument(Common::SeekableReadStream *stream); + bool getNextNote(uint16 &duration, byte &value, byte &velocity); + }; + + Channel _channel[5]; + int _pitchTable[128]; +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index 67ca071a7632..4ca4df44ab69 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -61,6 +61,7 @@ #include "scumm/player_v2cms.h" #include "scumm/player_v2a.h" #include "scumm/player_v3a.h" +#include "scumm/player_v3m.h" #include "scumm/player_v4a.h" #include "scumm/player_v5m.h" #include "scumm/resource.h" @@ -1820,6 +1821,8 @@ void ScummEngine::setupMusic(int midi) { #endif } else if (_game.platform == Common::kPlatformAmiga && _game.version <= 4) { _musicEngine = new Player_V4A(this, _mixer); + } else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_LOOM) { + _musicEngine = new Player_V3M(this, _mixer); } else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_MONKEY) { _musicEngine = new Player_V5M(this, _mixer); } else if (_game.id == GID_MANIAC && _game.version == 1) { diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index 57345126d31e..d6de1cddd55f 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -348,26 +348,9 @@ void Sound::playSound(int soundID) { } else if ((_vm->_game.id == GID_LOOM) && (_vm->_game.platform == Common::kPlatformMacintosh)) { // Mac version of Loom uses yet another sound format - /* - playSound #9 (room 70) - 000000: 55 00 00 45 73 6f 00 64 01 00 00 00 00 00 00 00 |U..Eso.d........| - 000010: 00 05 00 8e 2a 8f 2d 1c 2a 8f 2a 8f 2d 1c 00 28 |....*.-.*.*.-..(| - 000020: 00 31 00 3a 00 43 00 4c 00 01 00 00 00 01 00 64 |.1.:.C.L.......d| - 000030: 5a 00 01 00 00 00 01 00 64 00 00 01 00 00 00 01 |Z.......d.......| - 000040: 00 64 5a 00 01 00 00 00 01 00 64 5a 00 01 00 00 |.dZ.......dZ....| - 000050: 00 01 00 64 00 00 00 00 00 00 00 07 00 00 00 64 |...d...........d| - 000060: 64 00 00 4e 73 6f 00 64 01 00 00 00 00 00 00 00 |d..Nso.d........| - 000070: 00 05 00 89 3d 57 2d 1c 3d 57 3d 57 2d 1c 00 28 |....=W-.=W=W-..(| - playSound #16 (room 69) - 000000: dc 00 00 a5 73 6f 00 64 01 00 00 00 00 00 00 00 |....so.d........| - 000010: 00 05 00 00 2a 8f 03 e8 03 e8 03 e8 03 e8 00 28 |....*..........(| - 000020: 00 79 00 7f 00 85 00 d6 00 01 00 00 00 19 01 18 |.y..............| - 000030: 2f 00 18 00 01 18 32 00 18 00 01 18 36 00 18 00 |/.....2.....6...| - 000040: 01 18 3b 00 18 00 01 18 3e 00 18 00 01 18 42 00 |..;.....>.....B.| - 000050: 18 00 01 18 47 00 18 00 01 18 4a 00 18 00 01 18 |....G.....J.....| - 000060: 4e 00 10 00 01 18 53 00 10 00 01 18 56 00 10 00 |N.....S.....V...| - 000070: 01 18 5a 00 10 00 02 28 5f 00 01 00 00 00 00 00 |..Z....(_.......| - */ + if (_vm->_musicEngine) { + _vm->_musicEngine->startSound(soundID); + } } else if ((_vm->_game.platform == Common::kPlatformMacintosh) && (_vm->_game.id == GID_INDY3) && READ_BE_UINT16(ptr + 8) == 0x1C) { // Sound format as used in Indy3 EGA Mac. From 148a6d334770d992d306e7b399d42259c8efc649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Wed, 14 Nov 2012 01:27:53 +0100 Subject: [PATCH 04/26] SCUMM: Move most of the Macintosh player code into its own class The Monkey Island and Loom mac music is really quite similar. The data layout is a bit different, but most of the code was easy to separate into its own class. The Loom player doesn't do looped music but I don't remember off-hand if it ever should. --- engines/scumm/module.mk | 1 + engines/scumm/player_mac.cpp | 319 +++++++++++++++++++++++++++++++++++ engines/scumm/player_mac.h | 124 ++++++++++++++ engines/scumm/player_v3m.cpp | 313 +++------------------------------- engines/scumm/player_v3m.h | 69 +------- engines/scumm/player_v5m.cpp | 314 +++------------------------------- engines/scumm/player_v5m.h | 69 +------- 7 files changed, 508 insertions(+), 701 deletions(-) create mode 100644 engines/scumm/player_mac.cpp create mode 100644 engines/scumm/player_mac.h diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk index 57a495a77234..28884d7f782e 100644 --- a/engines/scumm/module.mk +++ b/engines/scumm/module.mk @@ -36,6 +36,7 @@ MODULE_OBJS := \ object.o \ palette.o \ player_apple2.o \ + player_mac.o \ player_mod.o \ player_nes.o \ player_pce.o \ diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp new file mode 100644 index 000000000000..565b13d74a49 --- /dev/null +++ b/engines/scumm/player_mac.cpp @@ -0,0 +1,319 @@ +/* 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. + * + */ + +#include "common/macresman.h" +#include "common/translation.h" +#include "engines/engine.h" +#include "gui/message.h" +#include "scumm/player_mac.h" +#include "scumm/scumm.h" + +namespace Scumm { + +Player_Mac::Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask) + : _vm(scumm), + _mixer(mixer), + _sampleRate(_mixer->getOutputRate()), + _soundPlaying(-1), + _numberOfChannels(numberOfChannels), + _channelMask(channelMask) { + + assert(scumm); + assert(mixer); + + if (checkMusicAvailable()) { + _channel = NULL; + return; + } + + _channel = new Player_Mac::Channel[_numberOfChannels]; + + int i; + + for (i = 0; i < _numberOfChannels; i++) { + _channel[i]._looped = false; + _channel[i]._length = 0; + _channel[i]._data = NULL; + _channel[i]._pos = 0; + _channel[i]._pitchModifier = 0; + _channel[i]._velocity = 0; + _channel[i]._remaining = 0; + _channel[i]._notesLeft = false; + _channel[i]._instrument._data = NULL; + _channel[i]._instrument._size = 0; + _channel[i]._instrument._rate = 0; + _channel[i]._instrument._loopStart = 0; + _channel[i]._instrument._loopEnd = 0; + _channel[i]._instrument._baseFreq = 0; + _channel[i]._instrument._pos = 0; + _channel[i]._instrument._subPos = 0; + } + + _pitchTable[116] = 1664510; + _pitchTable[117] = 1763487; + _pitchTable[118] = 1868350; + _pitchTable[119] = 1979447; + _pitchTable[120] = 2097152; + _pitchTable[121] = 2221855; + _pitchTable[122] = 2353973; + _pitchTable[123] = 2493948; + _pitchTable[124] = 2642246; + _pitchTable[125] = 2799362; + _pitchTable[126] = 2965820; + _pitchTable[127] = 3142177; + for (i = 115; i >= 0; --i) { + _pitchTable[i] = _pitchTable[i + 12] / 2; + } + + setMusicVolume(255); + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +Player_Mac::~Player_Mac() { + Common::StackLock lock(_mutex); + _mixer->stopHandle(_soundHandle); + stopAllSounds_Internal(); + delete[] _channel; +} + +void Player_Mac::setMusicVolume(int vol) { + debug(5, "Player_Mac::setMusicVolume(%d)", vol); +} + +void Player_Mac::stopAllSounds_Internal() { + _soundPlaying = -1; + for (int i = 0; i < _numberOfChannels; i++) { + // The channel data is managed by the resource manager, so + // don't delete that. + delete[] _channel[i]._instrument._data; + _channel[i]._instrument._data = NULL; + + _channel[i]._remaining = 0; + _channel[i]._notesLeft = false; + } +} + +void Player_Mac::stopAllSounds() { + Common::StackLock lock(_mutex); + debug(5, "Player_Mac::stopAllSounds()"); + stopAllSounds_Internal(); +} + +void Player_Mac::stopSound(int nr) { + Common::StackLock lock(_mutex); + debug(5, "Player_Mac::stopSound(%d)", nr); + + if (nr == _soundPlaying) { + stopAllSounds(); + } +} + +void Player_Mac::startSound(int nr) { + Common::StackLock lock(_mutex); + debug(5, "Player_Mac::startSound(%d)", nr); + + const byte *ptr = _vm->getResourceAddress(rtSound, nr); + assert(ptr); + + if (!loadMusic(ptr)) { + return; + } + + _soundPlaying = nr; +} + +bool Player_Mac::Channel::loadInstrument(Common::SeekableReadStream *stream) { + // Load the sound + uint16 soundType = stream->readUint16BE(); + if (soundType != 1) { + warning("Player_Mac::loadInstrument: Unsupported sound type %d", soundType); + return false; + } + uint16 typeCount = stream->readUint16BE(); + if (typeCount != 1) { + warning("Player_Mac::loadInstrument: Unsupported data type count %d", typeCount); + return false; + } + uint16 dataType = stream->readUint16BE(); + if (dataType != 5) { + warning("Player_Mac::loadInstrument: Unsupported data type %d", dataType); + return false; + } + + stream->readUint32BE(); // initialization option + + uint16 cmdCount = stream->readUint16BE(); + if (cmdCount != 1) { + warning("Player_Mac::loadInstrument: Unsupported command count %d", cmdCount); + return false; + } + uint16 command = stream->readUint16BE(); + if (command != 0x8050 && command != 0x8051) { + warning("Player_Mac::loadInstrument: Unsupported command 0x%04X", command); + return false; + } + + stream->readUint16BE(); // 0 + uint32 soundHeaderOffset = stream->readUint32BE(); + + stream->seek(soundHeaderOffset); + + uint32 soundDataOffset = stream->readUint32BE(); + uint32 size = stream->readUint32BE(); + uint32 rate = stream->readUint32BE() >> 16; + uint32 loopStart = stream->readUint32BE(); + uint32 loopEnd = stream->readUint32BE(); + byte encoding = stream->readByte(); + byte baseFreq = stream->readByte(); + + if (encoding != 0) { + warning("Player_Mac::loadInstrument: Unsupported encoding %d", encoding); + return false; + } + + stream->skip(soundDataOffset); + + byte *data = new byte[size]; + stream->read(data, size); + + _instrument._data = data; + _instrument._size = size; + _instrument._rate = rate; + _instrument._loopStart = loopStart; + _instrument._loopEnd = loopEnd; + _instrument._baseFreq = baseFreq; + + return true; +} + +int Player_Mac::getMusicTimer() { + return 0; +} + +int Player_Mac::getSoundStatus(int nr) const { + return _soundPlaying == nr; +} + +int Player_Mac::readBuffer(int16 *data, const int numSamples) { + Common::StackLock lock(_mutex); + + memset(data, 0, numSamples * 2); + if (_soundPlaying == -1) { + return numSamples; + } + + bool notesLeft = false; + + for (int i = 0; i < _numberOfChannels; i++) { + if (!(_channelMask & (1 << i))) { + continue; + } + + uint samplesLeft = numSamples; + int16 *ptr = data; + + while (samplesLeft > 0) { + int generated; + if (_channel[i]._remaining == 0) { + uint16 duration; + byte note, velocity; + if (getNextNote(i, duration, note, velocity)) { + if (note > 1) { + const int pitchIdx = note + 60 - _channel[i]._instrument._baseFreq; + assert(pitchIdx >= 0); + // I don't want to use floating-point arithmetics here, + // but I ran into overflow problems with the church + // music. It's only once per note, so it should be ok. + double mult = (double)(_channel[i]._instrument._rate) / (double)_sampleRate; + _channel[i]._pitchModifier = mult * _pitchTable[pitchIdx]; + _channel[i]._velocity = velocity; + } else { + _channel[i]._pitchModifier = 0; + _channel[i]._velocity = 0; + } + + // The correct formula should be: + // + // (duration * 473 * _sampleRate) / (4 * 480 * 480) + // + // But that's likely to cause integer overflow, so + // we do it in two steps and hope that the rounding + // error won't be noticeable. + // + // The original code is a bit unclear on if it should + // be 473 or 437, but since the comments indicated + // 473 I'm assuming 437 was a typo. + _channel[i]._remaining = (duration * _sampleRate) / (4 * 480); + _channel[i]._remaining = (_channel[i]._remaining * 473) / 480; + } else { + _channel[i]._pitchModifier = 0; + _channel[i]._velocity = 0; + _channel[i]._remaining = samplesLeft; + } + } + generated = MIN(_channel[i]._remaining, samplesLeft); + if (_channel[i]._velocity != 0) { + _channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated); + } + ptr += generated; + samplesLeft -= generated; + _channel[i]._remaining -= generated; + } + + if (_channel[i]._notesLeft) { + notesLeft = true; + } + } + + if (!notesLeft) { + stopAllSounds_Internal(); + } + + return numSamples; +} + +void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples) { + int samplesLeft = numSamples; + while (samplesLeft) { + _subPos += pitchModifier; + while (_subPos >= 0x10000) { + _subPos -= 0x10000; + _pos++; + if (_pos >= _loopEnd) { + _pos = _loopStart; + } + } + + int sample = *data + ((_data[_pos] - 129) * 128 * volume) / 255; + if (sample > 32767) { + sample = 32767; + } else if (sample < -32768) { + sample = -32768; + } + + *data++ = sample; // (_data[_pos] * 127) / 100; + samplesLeft--; + } +} + +} // End of namespace Scumm diff --git a/engines/scumm/player_mac.h b/engines/scumm/player_mac.h new file mode 100644 index 000000000000..505a94e03a1d --- /dev/null +++ b/engines/scumm/player_mac.h @@ -0,0 +1,124 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYER_MAC_H +#define SCUMM_PLAYER_MAC_H + +#include "common/scummsys.h" +#include "common/util.h" +#include "common/mutex.h" +#include "scumm/music.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" + +#define RES_SND MKTAG('s', 'n', 'd', ' ') + +class Mixer; + +namespace Scumm { + +class ScummEngine; + +/** + * Scumm Macintosh music driver, base class. + */ +class Player_Mac : public Audio::AudioStream, public MusicEngine { +public: + Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask); + virtual ~Player_Mac(); + + // MusicEngine API + virtual void setMusicVolume(int vol); + virtual void startSound(int sound); + virtual void stopSound(int sound); + virtual void stopAllSounds(); + virtual int getMusicTimer(); + virtual int getSoundStatus(int sound) const; + + // AudioStream API + virtual int readBuffer(int16 *buffer, const int numSamples); + virtual bool isStereo() const { return false; } + virtual bool endOfData() const { return false; } + virtual int getRate() const { return _sampleRate; } + +private: + Common::Mutex _mutex; + Audio::Mixer *const _mixer; + Audio::SoundHandle _soundHandle; + const uint32 _sampleRate; + int _soundPlaying; + + void stopAllSounds_Internal(); + + struct Instrument { + byte *_data; + uint32 _size; + uint32 _rate; + uint32 _loopStart; + uint32 _loopEnd; + byte _baseFreq; + + uint _pos; + uint _subPos; + + void newNote() { + _pos = 0; + _subPos = 0; + } + + void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples); + }; + + int _pitchTable[128]; + int _numberOfChannels; + int _channelMask; + + virtual bool checkMusicAvailable() { return false; } + virtual bool loadMusic(const byte *ptr) { return false; } + virtual bool getNextNote(int ch, uint16 &duration, byte &value, byte &velocity) { return false; } + +protected: + struct Channel { + virtual ~Channel() {} + + Instrument _instrument; + bool _looped; + uint32 _length; + const byte *_data; + + uint _pos; + int _pitchModifier; + byte _velocity; + uint32 _remaining; + + bool _notesLeft; + + bool loadInstrument(Common::SeekableReadStream *stream); + }; + + ScummEngine *const _vm; + Channel *_channel; +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/player_v3m.cpp b/engines/scumm/player_v3m.cpp index 081f48f25901..2d7c2eadc409 100644 --- a/engines/scumm/player_v3m.cpp +++ b/engines/scumm/player_v3m.cpp @@ -94,59 +94,18 @@ #include "scumm/player_v3m.h" #include "scumm/scumm.h" -#define RES_SND MKTAG('s', 'n', 'd', ' ') - namespace Scumm { Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer) - : _vm(scumm), - _mixer(mixer), - _sampleRate(_mixer->getOutputRate()), - _soundPlaying(-1) { - - assert(scumm); - assert(mixer); + : Player_Mac(scumm, mixer, 5, 0x1E) { assert(_vm->_game.id == GID_LOOM); - int i; - - for (i = 0; i < 5; i++) { - _channel[i]._looped = false; - _channel[i]._length = 0; - _channel[i]._data = NULL; - _channel[i]._pos = 0; - _channel[i]._pitchModifier = 0; - _channel[i]._velocity = 0; - _channel[i]._remaining = 0; - _channel[i]._notesLeft = false; - _channel[i]._instrument._data = NULL; - _channel[i]._instrument._size = 0; - _channel[i]._instrument._rate = 0; - _channel[i]._instrument._loopStart = 0; - _channel[i]._instrument._loopEnd = 0; - _channel[i]._instrument._baseFreq = 0; - _channel[i]._instrument._pos = 0; - _channel[i]._instrument._subPos = 0; - } - - _pitchTable[116] = 1664510; - _pitchTable[117] = 1763487; - _pitchTable[118] = 1868350; - _pitchTable[119] = 1979447; - _pitchTable[120] = 2097152; - _pitchTable[121] = 2221855; - _pitchTable[122] = 2353973; - _pitchTable[123] = 2493948; - _pitchTable[124] = 2642246; - _pitchTable[125] = 2799362; - _pitchTable[126] = 2965820; - _pitchTable[127] = 3142177; - for (i = 115; i >= 0; --i) { - _pitchTable[i] = _pitchTable[i + 12] / 2; - } - - setMusicVolume(255); + // Channel 0 seems to be what was played on low-end macs, that couldn't + // handle multi-channel music and play the game at the same time. I'm + // not sure if stream 4 is ever used, but let's use it just in case. +} +bool Player_V3M::checkMusicAvailable() { Common::MacResManager resource; // \252 is a trademark glyph. I don't think we should assume that it // will be preserved when copying from the Mac disk. @@ -155,72 +114,28 @@ Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer) "Could not find the 'Loom' Macintosh executable to read the\n" "instruments from. Music will be disabled."), _("OK")); dialog.runModal(); - return; - } - - _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); -} - -Player_V3M::~Player_V3M() { - Common::StackLock lock(_mutex); - _mixer->stopHandle(_soundHandle); - stopAllSounds_Internal(); -} - -void Player_V3M::setMusicVolume(int vol) { - debug(5, "Player_V3M::setMusicVolume(%d)", vol); -} - -void Player_V3M::stopAllSounds_Internal() { - _soundPlaying = -1; - for (int i = 0; i < 3; i++) { - // The channel data is managed by the resource manager, so - // don't delete that. - delete[] _channel[i]._instrument._data; - _channel[i]._instrument._data = NULL; - - _channel[i]._remaining = 0; - _channel[i]._notesLeft = false; + return false; } -} - -void Player_V3M::stopAllSounds() { - Common::StackLock lock(_mutex); - debug(5, "Player_V3M::stopAllSounds()"); - stopAllSounds_Internal(); -} - -void Player_V3M::stopSound(int nr) { - Common::StackLock lock(_mutex); - debug(5, "Player_V3M::stopSound(%d)", nr); - if (nr == _soundPlaying) { - stopAllSounds(); - } + return true; } -void Player_V3M::startSound(int nr) { - Common::StackLock lock(_mutex); - debug(5, "Player_V3M::startSound(%d)", nr); - +bool Player_V3M::loadMusic(const byte *ptr) { Common::MacResManager resource; if (!resource.open("Loom\252") && !resource.open("Loom")) { - return; + return false; } - const byte *ptr = _vm->getResourceAddress(rtSound, nr); - assert(ptr); + assert(ptr[4] == 's' && ptr[5] == 'o'); - const byte *src = ptr; uint i; - - assert(src[4] == 's' && src[5] == 'o'); for (i = 0; i < 5; i++) { - int instrument = READ_BE_UINT16(src + 20 + 2 * i); - int offset = READ_BE_UINT16(src + 30 + 2 * i); + int instrument = READ_BE_UINT16(ptr + 20 + 2 * i); + int offset = READ_BE_UINT16(ptr + 30 + 2 * i); + _channel[i]._looped = false; - _channel[i]._length = READ_BE_UINT16(src + offset + 4) * 3; - _channel[i]._data = src + offset + 6; + _channel[i]._length = READ_BE_UINT16(ptr + offset + 4) * 3; + _channel[i]._data = ptr + offset + 6; _channel[i]._pos = 0; _channel[i]._pitchModifier = 0; _channel[i]._velocity = 0; @@ -229,205 +144,31 @@ void Player_V3M::startSound(int nr) { Common::SeekableReadStream *stream = resource.getResource(RES_SND, instrument); if (_channel[i].loadInstrument(stream)) { - debug(6, "Player_V3M::startSound: Channel %d - Loaded Instrument %d (%s)", i, instrument, resource.getResName(RES_SND, instrument).c_str()); + debug(6, "Player_V3M::loadMusic: Channel %d - Loaded Instrument %d (%s)", i, instrument, resource.getResName(RES_SND, instrument).c_str()); } else { resource.close(); - return; + return false; } } resource.close(); - _soundPlaying = nr; -} - -bool Player_V3M::Channel::loadInstrument(Common::SeekableReadStream *stream) { - // Load the sound - uint16 soundType = stream->readUint16BE(); - if (soundType != 1) { - warning("Player_V3M::loadInstrument: Unsupported sound type %d", soundType); - return false; - } - uint16 typeCount = stream->readUint16BE(); - if (typeCount != 1) { - warning("Player_V3M::loadInstrument: Unsupported data type count %d", typeCount); - return false; - } - uint16 dataType = stream->readUint16BE(); - if (dataType != 5) { - warning("Player_V3M::loadInstrument: Unsupported data type %d", dataType); - return false; - } - - stream->readUint32BE(); // initialization option - - uint16 cmdCount = stream->readUint16BE(); - if (cmdCount != 1) { - warning("Player_V3M::loadInstrument: Unsupported command count %d", cmdCount); - return false; - } - uint16 command = stream->readUint16BE(); - if (command != 0x8050 && command != 0x8051) { - warning("Player_V3M::loadInstrument: Unsupported command 0x%04X", command); - return false; - } - - stream->readUint16BE(); // 0 - uint32 soundHeaderOffset = stream->readUint32BE(); - - stream->seek(soundHeaderOffset); - - uint32 soundDataOffset = stream->readUint32BE(); - uint32 size = stream->readUint32BE(); - uint32 rate = stream->readUint32BE() >> 16; - uint32 loopStart = stream->readUint32BE(); - uint32 loopEnd = stream->readUint32BE(); - byte encoding = stream->readByte(); - byte baseFreq = stream->readByte(); - - if (encoding != 0) { - warning("Player_V3M::loadInstrument: Unsupported encoding %d", encoding); - return false; - } - - stream->skip(soundDataOffset); - - byte *data = new byte[size]; - stream->read(data, size); - - _instrument._data = data; - _instrument._size = size; - _instrument._rate = rate; - _instrument._loopStart = loopStart; - _instrument._loopEnd = loopEnd; - _instrument._baseFreq = baseFreq; - return true; } -int Player_V3M::getMusicTimer() { - return 0; -} - -int Player_V3M::getSoundStatus(int nr) const { - return _soundPlaying == nr; -} - -int Player_V3M::readBuffer(int16 *data, const int numSamples) { - Common::StackLock lock(_mutex); - - memset(data, 0, numSamples * 2); - if (_soundPlaying == -1) { - return numSamples; - } - - bool notesLeft = false; - - // Channel 0 seems to be what was played on low-end macs, that couldn't - // handle multi-channel music and play the game at the same time. I'm - // not sure if stream 4 is ever used, but let's use it just in case. - - for (int i = 1; i < 5; i++) { - uint samplesLeft = numSamples; - int16 *ptr = data; - - while (samplesLeft > 0) { - int generated; - if (_channel[i]._remaining == 0) { - uint16 duration; - byte note, velocity; - if (_channel[i].getNextNote(duration, note, velocity)) { - if (note > 0) { - const int pitchIdx = note + 60 - _channel[i]._instrument._baseFreq; - assert(pitchIdx >= 0); - // I don't want to use floating-point arithmetics here, - // but I ran into overflow problems with the church - // music. It's only once per note, so it should be ok. - double mult = (double)(_channel[i]._instrument._rate) / (double)_sampleRate; - _channel[i]._pitchModifier = mult * _pitchTable[pitchIdx]; - _channel[i]._velocity = velocity; - } else { - _channel[i]._pitchModifier = 0; - _channel[i]._velocity = 0; - } - - // The correct formula should be: - // - // (duration * 473 * _sampleRate) / (4 * 480 * 480) - // - // But that's likely to cause integer overflow, so - // we do it in two steps and hope that the rounding - // error won't be noticeable. - // - // The original code is a bit unclear on if it should - // be 473 or 437, but since the comments indicated - // 473 I'm assuming 437 was a typo. - _channel[i]._remaining = (duration * _sampleRate) / (4 * 480); - _channel[i]._remaining = (_channel[i]._remaining * 473) / 480; - } else { - _channel[i]._pitchModifier = 0; - _channel[i]._velocity = 0; - _channel[i]._remaining = samplesLeft; - } - } - generated = MIN(_channel[i]._remaining, samplesLeft); - if (_channel[i]._velocity != 0) { - _channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated); - } - ptr += generated; - samplesLeft -= generated; - _channel[i]._remaining -= generated; - } - - if (_channel[i]._notesLeft) { - notesLeft = true; - } - } - - if (!notesLeft) { - stopAllSounds_Internal(); - } - - return numSamples; -} - -bool Player_V3M::Channel::getNextNote(uint16 &duration, byte ¬e, byte &velocity) { - _instrument.newNote(); - if (_pos >= _length) { - if (!_looped) { - _notesLeft = false; +bool Player_V3M::getNextNote(int ch, uint16 &duration, byte ¬e, byte &velocity) { + _channel[ch]._instrument.newNote(); + if (_channel[ch]._pos >= _channel[ch]._length) { + if (!_channel[ch]._looped) { + _channel[ch]._notesLeft = false; return false; } - _pos = 0; + _channel[ch]._pos = 0; } - duration = READ_BE_UINT16(&_data[_pos]); - note = _data[_pos + 2]; + duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]); + note = _channel[ch]._data[_channel[ch]._pos + 2]; velocity = 127; - _pos += 3; + _channel[ch]._pos += 3; return true; } -void Player_V3M::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples) { - int samplesLeft = numSamples; - while (samplesLeft) { - _subPos += pitchModifier; - while (_subPos >= 0x10000) { - _subPos -= 0x10000; - _pos++; - if (_pos >= _loopEnd) { - _pos = _loopStart; - } - } - - int sample = *data + ((_data[_pos] - 129) * 128 * volume) / 255; - if (sample > 32767) { - sample = 32767; - } else if (sample < -32768) { - sample = -32768; - } - - *data++ = sample; // (_data[_pos] * 127) / 100; - samplesLeft--; - } -} - } // End of namespace Scumm diff --git a/engines/scumm/player_v3m.h b/engines/scumm/player_v3m.h index 3465621ca39a..0bbb52221da4 100644 --- a/engines/scumm/player_v3m.h +++ b/engines/scumm/player_v3m.h @@ -27,6 +27,7 @@ #include "common/util.h" #include "common/mutex.h" #include "scumm/music.h" +#include "scumm/player_mac.h" #include "audio/audiostream.h" #include "audio/mixer.h" @@ -39,73 +40,13 @@ class ScummEngine; /** * Scumm V3 Macintosh music driver. */ - class Player_V3M : public Audio::AudioStream, public MusicEngine { + class Player_V3M : public Player_Mac { public: Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer); - virtual ~Player_V3M(); - // MusicEngine API - virtual void setMusicVolume(int vol); - virtual void startSound(int sound); - virtual void stopSound(int sound); - virtual void stopAllSounds(); - virtual int getMusicTimer(); - virtual int getSoundStatus(int sound) const; - - // AudioStream API - virtual int readBuffer(int16 *buffer, const int numSamples); - virtual bool isStereo() const { return false; } - virtual bool endOfData() const { return false; } - virtual int getRate() const { return _sampleRate; } - -private: - ScummEngine *const _vm; - Common::Mutex _mutex; - Audio::Mixer *const _mixer; - Audio::SoundHandle _soundHandle; - const uint32 _sampleRate; - int _soundPlaying; - - void stopAllSounds_Internal(); - - struct Instrument { - byte *_data; - uint32 _size; - uint32 _rate; - uint32 _loopStart; - uint32 _loopEnd; - byte _baseFreq; - - uint _pos; - uint _subPos; - - void newNote() { - _pos = 0; - _subPos = 0; - } - - void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples); - }; - - struct Channel { - Instrument _instrument; - bool _looped; - uint32 _length; - const byte *_data; - - uint _pos; - int _pitchModifier; - byte _velocity; - uint32 _remaining; - - bool _notesLeft; - - bool loadInstrument(Common::SeekableReadStream *stream); - bool getNextNote(uint16 &duration, byte &value, byte &velocity); - }; - - Channel _channel[5]; - int _pitchTable[128]; + virtual bool checkMusicAvailable(); + virtual bool loadMusic(const byte *ptr); + virtual bool getNextNote(int ch, uint16 &duration, byte ¬e, byte &velocity); }; } // End of namespace Scumm diff --git a/engines/scumm/player_v5m.cpp b/engines/scumm/player_v5m.cpp index 640361cb61a4..20519097bd1d 100644 --- a/engines/scumm/player_v5m.cpp +++ b/engines/scumm/player_v5m.cpp @@ -79,139 +79,49 @@ #include "scumm/player_v5m.h" #include "scumm/scumm.h" -#define RES_SND MKTAG('s', 'n', 'd', ' ') - namespace Scumm { Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer) - : _vm(scumm), - _mixer(mixer), - _sampleRate(_mixer->getOutputRate()), - _soundPlaying(-1) { - - assert(scumm); - assert(mixer); + : Player_Mac(scumm, mixer, 3, 0x07) { assert(_vm->_game.id == GID_MONKEY); +} - int i; - - for (i = 0; i < 3; i++) { - _channel[i]._looped = false; - _channel[i]._length = 0; - _channel[i]._data = NULL; - _channel[i]._pos = 0; - _channel[i]._pitchModifier = 0; - _channel[i]._velocity = 0; - _channel[i]._remaining = 0; - _channel[i]._notesLeft = false; - _channel[i]._instrument._data = NULL; - _channel[i]._instrument._size = 0; - _channel[i]._instrument._rate = 0; - _channel[i]._instrument._loopStart = 0; - _channel[i]._instrument._loopEnd = 0; - _channel[i]._instrument._baseFreq = 0; - _channel[i]._instrument._pos = 0; - _channel[i]._instrument._subPos = 0; - } - - _pitchTable[116] = 1664510; - _pitchTable[117] = 1763487; - _pitchTable[118] = 1868350; - _pitchTable[119] = 1979447; - _pitchTable[120] = 2097152; - _pitchTable[121] = 2221855; - _pitchTable[122] = 2353973; - _pitchTable[123] = 2493948; - _pitchTable[124] = 2642246; - _pitchTable[125] = 2799362; - _pitchTable[126] = 2965820; - _pitchTable[127] = 3142177; - for (i = 115; i >= 0; --i) { - _pitchTable[i] = _pitchTable[i + 12] / 2; - } - - setMusicVolume(255); - +bool Player_V5M::checkMusicAvailable() { Common::MacResManager resource; if (!resource.exists("Monkey Island")) { GUI::MessageDialog dialog(_( "Could not find the 'Monkey Island' Macintosh executable to read the\n" "instruments from. Music will be disabled."), _("OK")); dialog.runModal(); - return; - } - - _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); -} - -Player_V5M::~Player_V5M() { - Common::StackLock lock(_mutex); - _mixer->stopHandle(_soundHandle); - stopAllSounds_Internal(); -} - -void Player_V5M::setMusicVolume(int vol) { - debug(5, "Player_V5M::setMusicVolume(%d)", vol); -} - -void Player_V5M::stopAllSounds_Internal() { - _soundPlaying = -1; - for (int i = 0; i < 3; i++) { - // The channel data is managed by the resource manager, so - // don't delete that. - delete[] _channel[i]._instrument._data; - _channel[i]._instrument._data = NULL; - - _channel[i]._remaining = 0; - _channel[i]._notesLeft = false; + return false; } -} - -void Player_V5M::stopAllSounds() { - Common::StackLock lock(_mutex); - debug(5, "Player_V5M::stopAllSounds()"); - stopAllSounds_Internal(); -} -void Player_V5M::stopSound(int nr) { - Common::StackLock lock(_mutex); - debug(5, "Player_V5M::stopSound(%d)", nr); - - if (nr == _soundPlaying) { - stopAllSounds(); - } + return true; } -void Player_V5M::startSound(int nr) { - Common::StackLock lock(_mutex); - debug(5, "Player_V5M::startSound(%d)", nr); - +bool Player_V5M::loadMusic(const byte *ptr) { Common::MacResManager resource; if (!resource.open("Monkey Island")) { - return; + return false; } - const byte *ptr = _vm->getResourceAddress(rtSound, nr); - assert(ptr); - - const byte *src = ptr; uint i; - src += 8; + ptr += 8; // TODO: Decipher the unknown bytes in the header. For now, skip 'em - src += 28; + ptr += 28; Common::MacResIDArray idArray = resource.getResIDArray(RES_SND); // Load the three channels and their instruments for (i = 0; i < 3; i++) { - assert(READ_BE_UINT32(src) == MKTAG('C', 'h', 'a', 'n')); - uint32 len = READ_BE_UINT32(src + 4); - uint32 instrument = READ_BE_UINT32(src + 8); + assert(READ_BE_UINT32(ptr) == MKTAG('C', 'h', 'a', 'n')); + uint32 len = READ_BE_UINT32(ptr + 4); + uint32 instrument = READ_BE_UINT32(ptr + 8); _channel[i]._length = len - 24; - _channel[i]._data = src + 12; - _channel[i]._looped = (READ_BE_UINT32(src + len - 8) == MKTAG('L', 'o', 'o', 'p')); + _channel[i]._data = ptr + 12; + _channel[i]._looped = (READ_BE_UINT32(ptr + len - 8) == MKTAG('L', 'o', 'o', 'p')); _channel[i]._pos = 0; _channel[i]._pitchModifier = 0; _channel[i]._velocity = 0; @@ -221,213 +131,43 @@ void Player_V5M::startSound(int nr) { for (uint j = 0; j < idArray.size(); j++) { Common::String name = resource.getResName(RES_SND, idArray[j]); if (instrument == READ_BE_UINT32(name.c_str())) { - debug(6, "Channel %d: Loading instrument '%s'", i, name.c_str()); + debug(6, "Player_V5M::loadMusic: Channel %d: Loading instrument '%s'", i, name.c_str()); Common::SeekableReadStream *stream = resource.getResource(RES_SND, idArray[j]); if (!_channel[i].loadInstrument(stream)) { resource.close(); - return; + return false; } break; } } - src += len; + ptr += len; } resource.close(); - _soundPlaying = nr; -} - -bool Player_V5M::Channel::loadInstrument(Common::SeekableReadStream *stream) { - // Load the sound - uint16 soundType = stream->readUint16BE(); - if (soundType != 1) { - warning("Player_V5M::loadInstrument: Unsupported sound type %d", soundType); - return false; - } - uint16 typeCount = stream->readUint16BE(); - if (typeCount != 1) { - warning("Player_V5M::loadInstrument: Unsupported data type count %d", typeCount); - return false; - } - uint16 dataType = stream->readUint16BE(); - if (dataType != 5) { - warning("Player_V5M::loadInstrument: Unsupported data type %d", dataType); - return false; - } - - stream->readUint32BE(); // initialization option - - uint16 cmdCount = stream->readUint16BE(); - if (cmdCount != 1) { - warning("Player_V5M::loadInstrument: Unsupported command count %d", cmdCount); - return false; - } - uint16 command = stream->readUint16BE(); - if (command != 0x8050 && command != 0x8051) { - warning("Player_V5M::loadInstrument: Unsupported command 0x%04X", command); - return false; - } - - stream->readUint16BE(); // 0 - uint32 soundHeaderOffset = stream->readUint32BE(); - - stream->seek(soundHeaderOffset); - - uint32 soundDataOffset = stream->readUint32BE(); - uint32 size = stream->readUint32BE(); - uint32 rate = stream->readUint32BE() >> 16; - uint32 loopStart = stream->readUint32BE(); - uint32 loopEnd = stream->readUint32BE(); - byte encoding = stream->readByte(); - byte baseFreq = stream->readByte(); - - if (encoding != 0) { - warning("Player_V5M::loadInstrument: Unsupported encoding %d", encoding); - return false; - } - - stream->skip(soundDataOffset); - - byte *data = new byte[size]; - stream->read(data, size); - - _instrument._data = data; - _instrument._size = size; - _instrument._rate = rate; - _instrument._loopStart = loopStart; - _instrument._loopEnd = loopEnd; - _instrument._baseFreq = baseFreq; - return true; } -int Player_V5M::getMusicTimer() { - return 0; -} - -int Player_V5M::getSoundStatus(int nr) const { - return _soundPlaying == nr; -} - -int Player_V5M::readBuffer(int16 *data, const int numSamples) { - Common::StackLock lock(_mutex); - - memset(data, 0, numSamples * 2); - if (_soundPlaying == -1) { - return numSamples; - } - - bool notesLeft = false; - - for (int i = 0; i < 3; i++) { - uint samplesLeft = numSamples; - int16 *ptr = data; - - while (samplesLeft > 0) { - int generated; - if (_channel[i]._remaining == 0) { - uint16 duration; - byte note, velocity; - if (_channel[i].getNextNote(duration, note, velocity)) { - if (note > 1) { - const int pitchIdx = note + 60 - _channel[i]._instrument._baseFreq; - assert(pitchIdx >= 0); - // I don't want to use floating-point arithmetics here, - // but I ran into overflow problems with the church - // music. It's only once per note, so it should be ok. - double mult = (double)(_channel[i]._instrument._rate) / (double)_sampleRate; - _channel[i]._pitchModifier = mult * _pitchTable[pitchIdx]; - _channel[i]._velocity = velocity; - } else { - _channel[i]._pitchModifier = 0; - _channel[i]._velocity = 0; - } - - // The correct formula should be: - // - // (duration * 473 * _sampleRate) / (4 * 480 * 480) - // - // But that's likely to cause integer overflow, so - // we do it in two steps and hope that the rounding - // error won't be noticeable. - // - // The original code is a bit unclear on if it should - // be 473 or 437, but since the comments indicated - // 473 I'm assuming 437 was a typo. - _channel[i]._remaining = (duration * _sampleRate) / (4 * 480); - _channel[i]._remaining = (_channel[i]._remaining * 473) / 480; - } else { - _channel[i]._pitchModifier = 0; - _channel[i]._velocity = 0; - _channel[i]._remaining = samplesLeft; - } - } - generated = MIN(_channel[i]._remaining, samplesLeft); - if (_channel[i]._velocity != 0) { - _channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated); - } - ptr += generated; - samplesLeft -= generated; - _channel[i]._remaining -= generated; - } - - if (_channel[i]._notesLeft) { - notesLeft = true; - } - } - - if (!notesLeft) { - stopAllSounds_Internal(); - } - - return numSamples; -} - -bool Player_V5M::Channel::getNextNote(uint16 &duration, byte ¬e, byte &velocity) { - _instrument.newNote(); - if (_pos >= _length) { - if (!_looped) { - _notesLeft = false; +bool Player_V5M::getNextNote(int ch, uint16 &duration, byte ¬e, byte &velocity) { + _channel[ch]._instrument.newNote(); + if (_channel[ch]._pos >= _channel[ch]._length) { + if (!_channel[ch]._looped) { + _channel[ch]._notesLeft = false; return false; } // FIXME: Jamieson630: The jump seems to be happening // too quickly! There should maybe be a pause after // the last Note Off? But I couldn't find one in the // MI1 Lookout music, where I was hearing problems. - _pos = 0; + _channel[ch]._pos = 0; } - duration = READ_BE_UINT16(&_data[_pos]); - note = _data[_pos + 2]; - velocity = _data[_pos + 3]; - _pos += 4; + duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]); + note = _channel[ch]._data[_channel[ch]._pos + 2]; + velocity = _channel[ch]._data[_channel[ch]._pos + 3]; + _channel[ch]._pos += 4; return true; } -void Player_V5M::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples) { - int samplesLeft = numSamples; - while (samplesLeft) { - _subPos += pitchModifier; - while (_subPos >= 0x10000) { - _subPos -= 0x10000; - _pos++; - if (_pos >= _loopEnd) { - _pos = _loopStart; - } - } - - int sample = *data + ((_data[_pos] - 129) * 128 * volume) / 255; - if (sample > 32767) { - sample = 32767; - } else if (sample < -32768) { - sample = -32768; - } - - *data++ = sample; // (_data[_pos] * 127) / 100; - samplesLeft--; - } -} - } // End of namespace Scumm diff --git a/engines/scumm/player_v5m.h b/engines/scumm/player_v5m.h index c245554800bf..169fa89320f2 100644 --- a/engines/scumm/player_v5m.h +++ b/engines/scumm/player_v5m.h @@ -27,6 +27,7 @@ #include "common/util.h" #include "common/mutex.h" #include "scumm/music.h" +#include "scumm/player_mac.h" #include "audio/audiostream.h" #include "audio/mixer.h" @@ -39,73 +40,13 @@ class ScummEngine; /** * Scumm V5 Macintosh music driver. */ - class Player_V5M : public Audio::AudioStream, public MusicEngine { +class Player_V5M : public Player_Mac { public: Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer); - virtual ~Player_V5M(); - // MusicEngine API - virtual void setMusicVolume(int vol); - virtual void startSound(int sound); - virtual void stopSound(int sound); - virtual void stopAllSounds(); - virtual int getMusicTimer(); - virtual int getSoundStatus(int sound) const; - - // AudioStream API - virtual int readBuffer(int16 *buffer, const int numSamples); - virtual bool isStereo() const { return false; } - virtual bool endOfData() const { return false; } - virtual int getRate() const { return _sampleRate; } - -private: - ScummEngine *const _vm; - Common::Mutex _mutex; - Audio::Mixer *const _mixer; - Audio::SoundHandle _soundHandle; - const uint32 _sampleRate; - int _soundPlaying; - - void stopAllSounds_Internal(); - - struct Instrument { - byte *_data; - uint32 _size; - uint32 _rate; - uint32 _loopStart; - uint32 _loopEnd; - byte _baseFreq; - - uint _pos; - uint _subPos; - - void newNote() { - _pos = 0; - _subPos = 0; - } - - void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples); - }; - - struct Channel { - Instrument _instrument; - bool _looped; - uint32 _length; - const byte *_data; - - uint _pos; - int _pitchModifier; - byte _velocity; - uint32 _remaining; - - bool _notesLeft; - - bool loadInstrument(Common::SeekableReadStream *stream); - bool getNextNote(uint16 &duration, byte &value, byte &velocity); - }; - - Channel _channel[3]; - int _pitchTable[128]; + virtual bool checkMusicAvailable(); + virtual bool loadMusic(const byte *ptr); + virtual bool getNextNote(int ch, uint16 &duration, byte ¬e, byte &velocity); }; } // End of namespace Scumm From 493644295da511e43289db49d8cafdf1150a73dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Wed, 14 Nov 2012 06:07:55 +0100 Subject: [PATCH 05/26] SCUMM: Remove unnecessary check for Mac Loom As clone2727 pointed out, the default case handles Loom. I guess it was a special case before to *prevent* it from trying to play the sound, and to keep some comments about the format. --- engines/scumm/sound.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index d6de1cddd55f..cfce0cbc6253 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -346,12 +346,6 @@ void Sound::playSound(int soundID) { warning("Scumm::Sound::playSound: encountered audio resoure with chunk type 'SOUN' and sound type %d", type); } } - else if ((_vm->_game.id == GID_LOOM) && (_vm->_game.platform == Common::kPlatformMacintosh)) { - // Mac version of Loom uses yet another sound format - if (_vm->_musicEngine) { - _vm->_musicEngine->startSound(soundID); - } - } else if ((_vm->_game.platform == Common::kPlatformMacintosh) && (_vm->_game.id == GID_INDY3) && READ_BE_UINT16(ptr + 8) == 0x1C) { // Sound format as used in Indy3 EGA Mac. // It seems to be closely related to the Amiga format, see player_v3a.cpp From 0b63ea1d8d0ddfbc2bbef629594eedecbc8b1273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Wed, 14 Nov 2012 20:54:25 +0100 Subject: [PATCH 06/26] SCUMM: Hopefully fix warning Excplicitly cast to int to avoid a warning that I don't get, but which clone2727 does. At least, I hope it avoids the warning. --- engines/scumm/player_mac.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp index 565b13d74a49..6bc09d31073c 100644 --- a/engines/scumm/player_mac.cpp +++ b/engines/scumm/player_mac.cpp @@ -245,7 +245,7 @@ int Player_Mac::readBuffer(int16 *data, const int numSamples) { // but I ran into overflow problems with the church // music. It's only once per note, so it should be ok. double mult = (double)(_channel[i]._instrument._rate) / (double)_sampleRate; - _channel[i]._pitchModifier = mult * _pitchTable[pitchIdx]; + _channel[i]._pitchModifier = (int)(mult * _pitchTable[pitchIdx]); _channel[i]._velocity = velocity; } else { _channel[i]._pitchModifier = 0; From b02ecf7a8acc5218b187bf8141ab1aa5b588edfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Wed, 14 Nov 2012 21:02:58 +0100 Subject: [PATCH 07/26] SCUMM: Try harder to open the Loom Macintosh executable. Try the Mac OS Roman form, the UTF-8 form and the filename without any trademark glyph. --- engines/scumm/player_v3m.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/engines/scumm/player_v3m.cpp b/engines/scumm/player_v3m.cpp index 2d7c2eadc409..a1e69e2434d2 100644 --- a/engines/scumm/player_v3m.cpp +++ b/engines/scumm/player_v3m.cpp @@ -107,9 +107,10 @@ Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer) bool Player_V3M::checkMusicAvailable() { Common::MacResManager resource; - // \252 is a trademark glyph. I don't think we should assume that it - // will be preserved when copying from the Mac disk. - if (!resource.exists("Loom\252") && !resource.exists("Loom")) { + // \xAA is a trademark glyph in Mac OS Roman. We try that, but also the + // UTF-8 version, and just plain without in case the file system can't + // handle exotic characters like that. + if (!resource.exists("Loom\xAA") && !resource.exists("Loom\xE2\x84\xA2") && !resource.exists("Loom")) { GUI::MessageDialog dialog(_( "Could not find the 'Loom' Macintosh executable to read the\n" "instruments from. Music will be disabled."), _("OK")); @@ -122,7 +123,7 @@ bool Player_V3M::checkMusicAvailable() { bool Player_V3M::loadMusic(const byte *ptr) { Common::MacResManager resource; - if (!resource.open("Loom\252") && !resource.open("Loom")) { + if (!resource.open("Loom\xAA") && !resource.open("Loom\xE2\x84\xA2") && !resource.open("Loom")) { return false; } From 0dcd4ba5a7bf40e03b11a475c1a6082002f486aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Wed, 14 Nov 2012 21:20:40 +0100 Subject: [PATCH 08/26] SCUMM: Move Mac player initialization to its own function Apparently we cannot (portably) call virtual functions from the constructor, so initialization has been moved to a separate function. --- engines/scumm/player_mac.cpp | 5 +++-- engines/scumm/player_mac.h | 2 ++ engines/scumm/scumm.cpp | 2 ++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp index 6bc09d31073c..58dab192bec7 100644 --- a/engines/scumm/player_mac.cpp +++ b/engines/scumm/player_mac.cpp @@ -36,11 +36,12 @@ Player_Mac::Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChan _soundPlaying(-1), _numberOfChannels(numberOfChannels), _channelMask(channelMask) { - assert(scumm); assert(mixer); +} - if (checkMusicAvailable()) { +void Player_Mac::init() { + if (!checkMusicAvailable()) { _channel = NULL; return; } diff --git a/engines/scumm/player_mac.h b/engines/scumm/player_mac.h index 505a94e03a1d..b850915bb2a0 100644 --- a/engines/scumm/player_mac.h +++ b/engines/scumm/player_mac.h @@ -46,6 +46,8 @@ class Player_Mac : public Audio::AudioStream, public MusicEngine { Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask); virtual ~Player_Mac(); + void init(); + // MusicEngine API virtual void setMusicVolume(int vol); virtual void startSound(int sound); diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index 4ca4df44ab69..888a7ff2f79f 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -1823,8 +1823,10 @@ void ScummEngine::setupMusic(int midi) { _musicEngine = new Player_V4A(this, _mixer); } else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_LOOM) { _musicEngine = new Player_V3M(this, _mixer); + ((Player_V3M *)_musicEngine)->init(); } else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_MONKEY) { _musicEngine = new Player_V5M(this, _mixer); + ((Player_V5M *)_musicEngine)->init(); } else if (_game.id == GID_MANIAC && _game.version == 1) { _musicEngine = new Player_V1(this, _mixer, MidiDriver::getMusicType(dev) != MT_PCSPK); } else if (_game.version <= 2) { From b75349383e834b47ad57f86a2c0f5ab48ca4aa2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Wed, 14 Nov 2012 22:12:46 +0100 Subject: [PATCH 09/26] SCUMM: Remove Mac version of MI1 from special case. We no longer use iMuse for MI1 Mac so this never happens. The Mac player can only play one song at a time, so it should be all right. --- engines/scumm/sound.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index cfce0cbc6253..2fe16c54418b 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -391,8 +391,7 @@ void Sound::playSound(int soundID) { } else { - if (_vm->_game.id == GID_MONKEY_VGA || _vm->_game.id == GID_MONKEY_EGA - || (_vm->_game.id == GID_MONKEY && _vm->_game.platform == Common::kPlatformMacintosh)) { + if (_vm->_game.id == GID_MONKEY_VGA || _vm->_game.id == GID_MONKEY_EGA) { // Works around the fact that in some places in MonkeyEGA/VGA, // the music is never explicitly stopped. // Rather it seems that starting a new music is supposed to From f784d683e0f42c059331b49dbf90f91ac442b820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Thu, 15 Nov 2012 22:23:44 +0100 Subject: [PATCH 10/26] SCUMM: Save/load Mac music engine state for Loom and MI1 Note that while this removes _townsPlayer->saveLoadWithSerializer(s) it really shouldn't break anything because _musicEngine also points to the FM Towns player. Famous last words... --- engines/scumm/music.h | 6 ++++++ engines/scumm/player_mac.cpp | 42 ++++++++++++++++++++++++++++++++++++ engines/scumm/player_mac.h | 3 +++ engines/scumm/saveload.cpp | 10 ++++++--- engines/scumm/saveload.h | 2 +- 5 files changed, 59 insertions(+), 4 deletions(-) diff --git a/engines/scumm/music.h b/engines/scumm/music.h index a527c77b728e..9fd14d830e8b 100644 --- a/engines/scumm/music.h +++ b/engines/scumm/music.h @@ -24,6 +24,7 @@ #define SCUMM_MUSIC_H #include "common/scummsys.h" +#include "engines/scumm/saveload.h" namespace Scumm { @@ -78,6 +79,11 @@ class MusicEngine { * @return the music timer */ virtual int getMusicTimer() { return 0; } + + /** + * Save or load the music state. + */ + virtual void saveLoadWithSerializer(Serializer *ser) {} }; } // End of namespace Scumm diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp index 58dab192bec7..7b0f671f6d3b 100644 --- a/engines/scumm/player_mac.cpp +++ b/engines/scumm/player_mac.cpp @@ -97,6 +97,48 @@ Player_Mac::~Player_Mac() { delete[] _channel; } +void Player_Mac::saveLoadWithSerializer(Serializer *ser) { + Common::StackLock lock(_mutex); + if (ser->getVersion() < VER(94)) { + if (_vm->_game.id == GID_MONKEY && ser->isLoading()) { + // TODO: Skip old iMUSE save/load information + } + } else { + static const SaveLoadEntry musicEntries[] = { + MKLINE(Player_Mac, _soundPlaying, sleInt16, VER(94)), + MKEND() + }; + + static const SaveLoadEntry channelEntries[] = { + MKLINE(Channel, _pos, sleUint16, VER(94)), + MKLINE(Channel, _pitchModifier, sleInt32, VER(94)), + MKLINE(Channel, _velocity, sleUint8, VER(94)), + MKLINE(Channel, _remaining, sleUint32, VER(94)), + MKLINE(Channel, _notesLeft, sleUint8, VER(94)), + MKEND() + }; + + static const SaveLoadEntry instrumentEntries[] = { + MKLINE(Instrument, _pos, sleUint32, VER(94)), + MKLINE(Instrument, _subPos, sleUint32, VER(94)), + MKEND() + }; + + ser->saveLoadEntries(this, musicEntries); + + if (ser->isLoading() && _soundPlaying != -1) { + const byte *ptr = _vm->getResourceAddress(rtSound, _soundPlaying); + assert(ptr); + loadMusic(ptr); + } + + ser->saveLoadArrayOf(_channel, _numberOfChannels, sizeof(Channel), channelEntries); + for (int i = 0; i < _numberOfChannels; i++) { + ser->saveLoadEntries(&_channel[i], instrumentEntries); + } + } +} + void Player_Mac::setMusicVolume(int vol) { debug(5, "Player_Mac::setMusicVolume(%d)", vol); } diff --git a/engines/scumm/player_mac.h b/engines/scumm/player_mac.h index b850915bb2a0..a30111bda0d4 100644 --- a/engines/scumm/player_mac.h +++ b/engines/scumm/player_mac.h @@ -27,6 +27,7 @@ #include "common/util.h" #include "common/mutex.h" #include "scumm/music.h" +#include "scumm/saveload.h" #include "audio/audiostream.h" #include "audio/mixer.h" @@ -62,6 +63,8 @@ class Player_Mac : public Audio::AudioStream, public MusicEngine { virtual bool endOfData() const { return false; } virtual int getRate() const { return _sampleRate; } + virtual void saveLoadWithSerializer(Serializer *ser); + private: Common::Mutex _mutex; Audio::Mixer *const _mixer; diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp index 72896e097ad6..3453e53a182e 100644 --- a/engines/scumm/saveload.cpp +++ b/engines/scumm/saveload.cpp @@ -1477,9 +1477,13 @@ void ScummEngine::saveOrLoad(Serializer *s) { } - // Save/load FM-Towns audio status - if (_townsPlayer) - _townsPlayer->saveLoadWithSerializer(s); + // + // Save/load music engine status + // + if (_musicEngine) { + _musicEngine->saveLoadWithSerializer(s); + } + // // Save/load the charset renderer state diff --git a/engines/scumm/saveload.h b/engines/scumm/saveload.h index a640bc1e1720..4bfa7d0e7180 100644 --- a/engines/scumm/saveload.h +++ b/engines/scumm/saveload.h @@ -47,7 +47,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 93 +#define CURRENT_VER 94 /** * An auxillary macro, used to specify savegame versions. We use this instead From f0c1d8dcc46b77895ad85b3cdaaf3b81e497b0d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Fri, 16 Nov 2012 07:49:17 +0100 Subject: [PATCH 11/26] SCUMM: Add hack to preserve savegame compatibility with Mac MI1 For old savegames, we now use a "dummy" iMUSE objet to skip the old iMUSE save state. I had hoped to be able to do this without making any changes to the iMUSE code itself, but I was unable to. Also added note about how the save state for the new music will not quite work if the mixer output rate changes. Personally, I'm not too worried about that. It breaks, but it shouldn't break badly. --- engines/scumm/imuse/imuse.cpp | 13 +++++++++++-- engines/scumm/imuse/imuse.h | 2 +- engines/scumm/imuse/imuse_internal.h | 2 +- engines/scumm/player_mac.cpp | 10 +++++++++- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/engines/scumm/imuse/imuse.cpp b/engines/scumm/imuse/imuse.cpp index 016ba89e7b14..b69ce552bc86 100644 --- a/engines/scumm/imuse/imuse.cpp +++ b/engines/scumm/imuse/imuse.cpp @@ -363,7 +363,7 @@ void IMuseInternal::pause(bool paused) { _paused = paused; } -int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm) { +int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm, bool fixAfterLoad) { Common::StackLock lock(_mutex, "IMuseInternal::save_or_load()"); const SaveLoadEntry mainEntries[] = { MKLINE(IMuseInternal, _queue_end, sleUint8, VER(8)), @@ -440,7 +440,16 @@ int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm) { for (i = 0; i < 8; ++i) ser->saveLoadEntries(0, volumeFaderEntries); - if (ser->isLoading()) { + // Normally, we have to fix up the data structures after loading a + // saved game. But there are cases where we don't. For instance, The + // Macintosh version of Monkey Island 1 used to convert the Mac0 music + // resources to General MIDI and play it through iMUSE as a rough + // approximation. Now it has its own player, but old savegame still + // have the iMUSE data in them. We have to skip that data, using a + // dummy iMUSE object, but since the resource is no longer recognizable + // to iMUSE, the fixup fails hard. So yes, this is a bit of a hack. + + if (ser->isLoading() && fixAfterLoad) { // Load all sounds that we need fix_players_after_load(scumm); fix_parts_after_load(); diff --git a/engines/scumm/imuse/imuse.h b/engines/scumm/imuse/imuse.h index 23449e470b22..cce53092296a 100644 --- a/engines/scumm/imuse/imuse.h +++ b/engines/scumm/imuse/imuse.h @@ -62,7 +62,7 @@ class IMuse : public MusicEngine { 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 int save_or_load(Serializer *ser, ScummEngine *scumm, bool fixAfterLoad = true) = 0; virtual bool get_sound_active(int sound) const = 0; virtual int32 doCommand(int numargs, int args[]) = 0; virtual int clear_queue() = 0; diff --git a/engines/scumm/imuse/imuse_internal.h b/engines/scumm/imuse/imuse_internal.h index 846e2d754585..6be564a517f8 100644 --- a/engines/scumm/imuse/imuse_internal.h +++ b/engines/scumm/imuse/imuse_internal.h @@ -518,7 +518,7 @@ class IMuseInternal : public IMuse { public: // IMuse interface void pause(bool paused); - int save_or_load(Serializer *ser, ScummEngine *scumm); + int save_or_load(Serializer *ser, ScummEngine *scumm, bool fixAfterLoad = true); bool get_sound_active(int sound) const; int32 doCommand(int numargs, int args[]); uint32 property(int prop, uint32 value); diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp index 7b0f671f6d3b..cd1df51938df 100644 --- a/engines/scumm/player_mac.cpp +++ b/engines/scumm/player_mac.cpp @@ -26,6 +26,7 @@ #include "gui/message.h" #include "scumm/player_mac.h" #include "scumm/scumm.h" +#include "scumm/imuse/imuse.h" namespace Scumm { @@ -101,7 +102,8 @@ void Player_Mac::saveLoadWithSerializer(Serializer *ser) { Common::StackLock lock(_mutex); if (ser->getVersion() < VER(94)) { if (_vm->_game.id == GID_MONKEY && ser->isLoading()) { - // TODO: Skip old iMUSE save/load information + IMuse *dummyImuse = IMuse::create(_vm->_system, NULL, NULL); + dummyImuse->save_or_load(ser, _vm, false); } } else { static const SaveLoadEntry musicEntries[] = { @@ -109,6 +111,12 @@ void Player_Mac::saveLoadWithSerializer(Serializer *ser) { MKEND() }; + // Note: This will fail slightly when loading a savegame if + // the mixer output rate has changed, because the pitch + // modifier and remaining samples were calculated from it. As + // a result, the first note to be played will be out of tune, + // and the channels will probably be slightly out of sync. + static const SaveLoadEntry channelEntries[] = { MKLINE(Channel, _pos, sleUint16, VER(94)), MKLINE(Channel, _pitchModifier, sleInt32, VER(94)), From b6a42e9faa4ae9b9508bba33c1adbcfd8228c56d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Fri, 16 Nov 2012 16:43:13 +0100 Subject: [PATCH 12/26] SCUMM: Store sample rate in Mac MI1 / Loom savegames This keeps the music from breaking when loading a savegame that was made with a different sample rate than the current one. It also breaks all savegames made in the past eight hours, but I don't think it's necessary to maintain savegame compatibility within a pull request, as long as it still works with savegames made before it. --- engines/scumm/player_mac.cpp | 25 ++++++++++++++++++------- engines/scumm/player_mac.h | 2 +- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp index cd1df51938df..04c66c400cf9 100644 --- a/engines/scumm/player_mac.cpp +++ b/engines/scumm/player_mac.cpp @@ -107,16 +107,11 @@ void Player_Mac::saveLoadWithSerializer(Serializer *ser) { } } else { static const SaveLoadEntry musicEntries[] = { + MKLINE(Player_Mac, _sampleRate, sleUint32, VER(94)), MKLINE(Player_Mac, _soundPlaying, sleInt16, VER(94)), MKEND() }; - // Note: This will fail slightly when loading a savegame if - // the mixer output rate has changed, because the pitch - // modifier and remaining samples were calculated from it. As - // a result, the first note to be played will be out of tune, - // and the channels will probably be slightly out of sync. - static const SaveLoadEntry channelEntries[] = { MKLINE(Channel, _pos, sleUint16, VER(94)), MKLINE(Channel, _pitchModifier, sleInt32, VER(94)), @@ -132,6 +127,9 @@ void Player_Mac::saveLoadWithSerializer(Serializer *ser) { MKEND() }; + uint32 mixerSampleRate = _sampleRate; + int i; + ser->saveLoadEntries(this, musicEntries); if (ser->isLoading() && _soundPlaying != -1) { @@ -141,9 +139,22 @@ void Player_Mac::saveLoadWithSerializer(Serializer *ser) { } ser->saveLoadArrayOf(_channel, _numberOfChannels, sizeof(Channel), channelEntries); - for (int i = 0; i < _numberOfChannels; i++) { + for (i = 0; i < _numberOfChannels; i++) { ser->saveLoadEntries(&_channel[i], instrumentEntries); } + + if (ser->isLoading()) { + // If necessary, adjust the channel data to fit the + // current sample rate. + if (_soundPlaying != -1 && _sampleRate != mixerSampleRate) { + double mult = (double)_sampleRate / (double)mixerSampleRate; + for (i = 0; i < _numberOfChannels; i++) { + _channel[i]._pitchModifier = (int)((double)_channel[i]._pitchModifier * mult); + _channel[i]._remaining = (int)((double)_channel[i]._remaining / mult); + } + } + _sampleRate = mixerSampleRate; + } } } diff --git a/engines/scumm/player_mac.h b/engines/scumm/player_mac.h index a30111bda0d4..0585eb16b049 100644 --- a/engines/scumm/player_mac.h +++ b/engines/scumm/player_mac.h @@ -69,7 +69,7 @@ class Player_Mac : public Audio::AudioStream, public MusicEngine { Common::Mutex _mutex; Audio::Mixer *const _mixer; Audio::SoundHandle _soundHandle; - const uint32 _sampleRate; + uint32 _sampleRate; int _soundPlaying; void stopAllSounds_Internal(); From 4626e211496aad5ffe27bea3121483faa98770d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Sat, 17 Nov 2012 11:42:41 +0100 Subject: [PATCH 13/26] SCUMM: Remove commented out code. It was the remains of an experiment and no longer serves a purpose. --- engines/scumm/player_mac.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp index 04c66c400cf9..ac4615c6062f 100644 --- a/engines/scumm/player_mac.cpp +++ b/engines/scumm/player_mac.cpp @@ -373,7 +373,7 @@ void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int sample = -32768; } - *data++ = sample; // (_data[_pos] * 127) / 100; + *data++ = sample; samplesLeft--; } } From ee65532a5e3af0a92384d6d777110b6e602cf1c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Sat, 17 Nov 2012 11:53:54 +0100 Subject: [PATCH 14/26] SCUMM: Avoid "pops" at the end of the note in Mac MI1/Loom music At least on my computer, when the note ended abruptly there would be an annoying "pop" at the end. This was particularly noticeable at the end of the distaff notes in Loom. To get around this, fade out the last 100 samples. There's nothing magical about 100 in particular, but it's a nice even number and it should be short enough that it's never a noticeable part of the note, even at low sample rates. --- engines/scumm/player_mac.cpp | 20 +++++++++++++++++--- engines/scumm/player_mac.h | 2 +- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp index ac4615c6062f..7c3e2c22b05f 100644 --- a/engines/scumm/player_mac.cpp +++ b/engines/scumm/player_mac.cpp @@ -335,7 +335,7 @@ int Player_Mac::readBuffer(int16 *data, const int numSamples) { } generated = MIN(_channel[i]._remaining, samplesLeft); if (_channel[i]._velocity != 0) { - _channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated); + _channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated, _channel[i]._remaining); } ptr += generated; samplesLeft -= generated; @@ -354,7 +354,7 @@ int Player_Mac::readBuffer(int16 *data, const int numSamples) { return numSamples; } -void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples) { +void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote) { int samplesLeft = numSamples; while (samplesLeft) { _subPos += pitchModifier; @@ -366,7 +366,21 @@ void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int } } - int sample = *data + ((_data[_pos] - 129) * 128 * volume) / 255; + int newSample = ((_data[_pos] - 129) * 128 * volume) / 255; + + // Fade out the last 100 samples on each note. Even at low + // output sample rates this is just a fraction of a second, + // but it gets rid of distracting "pops" at the end when the + // sample would otherwise go abruptly from something to + // nothing. This was particularly noticeable on the distaff + // notes in Loom. + + remainingSamplesOnNote--; + if (remainingSamplesOnNote < 100) { + newSample = (newSample * remainingSamplesOnNote) / 100; + } + + int sample = *data + newSample; if (sample > 32767) { sample = 32767; } else if (sample < -32768) { diff --git a/engines/scumm/player_mac.h b/engines/scumm/player_mac.h index 0585eb16b049..3f5184d2d870 100644 --- a/engines/scumm/player_mac.h +++ b/engines/scumm/player_mac.h @@ -90,7 +90,7 @@ class Player_Mac : public Audio::AudioStream, public MusicEngine { _subPos = 0; } - void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples); + void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote); }; int _pitchTable[128]; From f3c9b218065357ef0178e4d68143deada86b6ed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Sun, 18 Nov 2012 13:43:33 +0100 Subject: [PATCH 15/26] SCUMM: Fix whitespace --- engines/scumm/player_v3m.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engines/scumm/player_v3m.h b/engines/scumm/player_v3m.h index 0bbb52221da4..b39783fb9a3b 100644 --- a/engines/scumm/player_v3m.h +++ b/engines/scumm/player_v3m.h @@ -40,7 +40,7 @@ class ScummEngine; /** * Scumm V3 Macintosh music driver. */ - class Player_V3M : public Player_Mac { +class Player_V3M : public Player_Mac { public: Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer); From 4f18a92f5a91eb502518846771bc8e82ff7da20a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Sun, 18 Nov 2012 14:30:17 +0100 Subject: [PATCH 16/26] SCUMM: Prevent music channels from drifting out of sync in Mac MI1 In looped music, prevent the music channels from drifting out of sync over time. This was noticeable after a few minutes in the SCUMM Bar. We do this by extending the last note (which is just zeroes, so we didn't even use to play it) so that it has the exact number of samples needed to make all channels the exact same length. (This is calculated when the music is loaded, so it does not need any extra data in the save games, thankfully.) As a result, the getNextNote() is now responsible for converting the duration to number of samples (out of necessity) and for converting the note to a pitch modifier (out of symmetry). I made several false starts before I realized how much easier it would be this way. --- engines/scumm/player_mac.cpp | 66 ++++++++++++++++++++---------------- engines/scumm/player_mac.h | 5 ++- engines/scumm/player_v3m.cpp | 8 +++-- engines/scumm/player_v3m.h | 2 +- engines/scumm/player_v5m.cpp | 34 ++++++++++++++++--- engines/scumm/player_v5m.h | 5 ++- 6 files changed, 80 insertions(+), 40 deletions(-) diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp index 7c3e2c22b05f..470bdaff3002 100644 --- a/engines/scumm/player_mac.cpp +++ b/engines/scumm/player_mac.cpp @@ -276,6 +276,34 @@ int Player_Mac::getSoundStatus(int nr) const { return _soundPlaying == nr; } +uint32 Player_Mac::durationToSamples(uint16 duration) { + // The correct formula should be: + // + // (duration * 473 * _sampleRate) / (4 * 480 * 480) + // + // But that's likely to cause integer overflow, so we do it in two + // steps and hope that the rounding error won't be noticeable. + // + // The original code is a bit unclear on if it should be 473 or 437, + // but since the comments indicated 473 I'm assuming 437 was a typo. + uint32 samples = (duration * _sampleRate) / (4 * 480); + samples = (samples * 473) / 480; + return samples; +} + +int Player_Mac::noteToPitchModifier(byte note, Instrument *instrument) { + if (note > 1) { + const int pitchIdx = note + 60 - instrument->_baseFreq; + // I don't want to use floating-point arithmetics here, but I + // ran into overflow problems with the church music in Monkey + // Island. It's only once per note, so it should be ok. + double mult = (double)instrument->_rate / (double)_sampleRate; + return (int)(mult * _pitchTable[pitchIdx]); + } else { + return 0; + } +} + int Player_Mac::readBuffer(int16 *data, const int numSamples) { Common::StackLock lock(_mutex); @@ -297,36 +325,14 @@ int Player_Mac::readBuffer(int16 *data, const int numSamples) { while (samplesLeft > 0) { int generated; if (_channel[i]._remaining == 0) { - uint16 duration; - byte note, velocity; - if (getNextNote(i, duration, note, velocity)) { - if (note > 1) { - const int pitchIdx = note + 60 - _channel[i]._instrument._baseFreq; - assert(pitchIdx >= 0); - // I don't want to use floating-point arithmetics here, - // but I ran into overflow problems with the church - // music. It's only once per note, so it should be ok. - double mult = (double)(_channel[i]._instrument._rate) / (double)_sampleRate; - _channel[i]._pitchModifier = (int)(mult * _pitchTable[pitchIdx]); - _channel[i]._velocity = velocity; - } else { - _channel[i]._pitchModifier = 0; - _channel[i]._velocity = 0; - } - - // The correct formula should be: - // - // (duration * 473 * _sampleRate) / (4 * 480 * 480) - // - // But that's likely to cause integer overflow, so - // we do it in two steps and hope that the rounding - // error won't be noticeable. - // - // The original code is a bit unclear on if it should - // be 473 or 437, but since the comments indicated - // 473 I'm assuming 437 was a typo. - _channel[i]._remaining = (duration * _sampleRate) / (4 * 480); - _channel[i]._remaining = (_channel[i]._remaining * 473) / 480; + uint32 samples; + int pitchModifier; + byte velocity; + if (getNextNote(i, samples, pitchModifier, velocity)) { + _channel[i]._remaining = samples; + _channel[i]._pitchModifier = pitchModifier; + _channel[i]._velocity = velocity; + } else { _channel[i]._pitchModifier = 0; _channel[i]._velocity = 0; diff --git a/engines/scumm/player_mac.h b/engines/scumm/player_mac.h index 3f5184d2d870..c46495c33373 100644 --- a/engines/scumm/player_mac.h +++ b/engines/scumm/player_mac.h @@ -99,7 +99,7 @@ class Player_Mac : public Audio::AudioStream, public MusicEngine { virtual bool checkMusicAvailable() { return false; } virtual bool loadMusic(const byte *ptr) { return false; } - virtual bool getNextNote(int ch, uint16 &duration, byte &value, byte &velocity) { return false; } + virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) { return false; } protected: struct Channel { @@ -122,6 +122,9 @@ class Player_Mac : public Audio::AudioStream, public MusicEngine { ScummEngine *const _vm; Channel *_channel; + + uint32 durationToSamples(uint16 duration); + int noteToPitchModifier(byte note, Instrument *instrument); }; } // End of namespace Scumm diff --git a/engines/scumm/player_v3m.cpp b/engines/scumm/player_v3m.cpp index a1e69e2434d2..db532a9f6e0d 100644 --- a/engines/scumm/player_v3m.cpp +++ b/engines/scumm/player_v3m.cpp @@ -156,7 +156,7 @@ bool Player_V3M::loadMusic(const byte *ptr) { return true; } -bool Player_V3M::getNextNote(int ch, uint16 &duration, byte ¬e, byte &velocity) { +bool Player_V3M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) { _channel[ch]._instrument.newNote(); if (_channel[ch]._pos >= _channel[ch]._length) { if (!_channel[ch]._looped) { @@ -165,8 +165,10 @@ bool Player_V3M::getNextNote(int ch, uint16 &duration, byte ¬e, byte &velocit } _channel[ch]._pos = 0; } - duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]); - note = _channel[ch]._data[_channel[ch]._pos + 2]; + uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]); + byte note = _channel[ch]._data[_channel[ch]._pos + 2]; + samples = durationToSamples(duration); + pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument); velocity = 127; _channel[ch]._pos += 3; return true; diff --git a/engines/scumm/player_v3m.h b/engines/scumm/player_v3m.h index b39783fb9a3b..359bab32a99e 100644 --- a/engines/scumm/player_v3m.h +++ b/engines/scumm/player_v3m.h @@ -46,7 +46,7 @@ class Player_V3M : public Player_Mac { virtual bool checkMusicAvailable(); virtual bool loadMusic(const byte *ptr); - virtual bool getNextNote(int ch, uint16 &duration, byte ¬e, byte &velocity); + virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity); }; } // End of namespace Scumm diff --git a/engines/scumm/player_v5m.cpp b/engines/scumm/player_v5m.cpp index 20519097bd1d..d59cf9ade2ae 100644 --- a/engines/scumm/player_v5m.cpp +++ b/engines/scumm/player_v5m.cpp @@ -119,7 +119,7 @@ bool Player_V5M::loadMusic(const byte *ptr) { uint32 len = READ_BE_UINT32(ptr + 4); uint32 instrument = READ_BE_UINT32(ptr + 8); - _channel[i]._length = len - 24; + _channel[i]._length = len - 20; _channel[i]._data = ptr + 12; _channel[i]._looped = (READ_BE_UINT32(ptr + len - 8) == MKTAG('L', 'o', 'o', 'p')); _channel[i]._pos = 0; @@ -147,10 +147,30 @@ bool Player_V5M::loadMusic(const byte *ptr) { } resource.close(); + + // The last note of each channel is just zeroes. We will adjust this + // note so that all the channels end at the same time. + + uint32 samples[3]; + uint32 maxSamples = 0; + for (i = 0; i < 3; i++) { + samples[i] = 0; + for (uint j = 0; j < _channel[i]._length; j += 4) { + samples[i] += durationToSamples(READ_BE_UINT16(&_channel[i]._data[j])); + } + if (samples[i] > maxSamples) { + maxSamples = samples[i]; + } + } + + for (i = 0; i < 3; i++) { + _lastNoteSamples[i] = maxSamples - samples[i]; + } + return true; } -bool Player_V5M::getNextNote(int ch, uint16 &duration, byte ¬e, byte &velocity) { +bool Player_V5M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) { _channel[ch]._instrument.newNote(); if (_channel[ch]._pos >= _channel[ch]._length) { if (!_channel[ch]._looped) { @@ -163,10 +183,16 @@ bool Player_V5M::getNextNote(int ch, uint16 &duration, byte ¬e, byte &velocit // MI1 Lookout music, where I was hearing problems. _channel[ch]._pos = 0; } - duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]); - note = _channel[ch]._data[_channel[ch]._pos + 2]; + uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]); + byte note = _channel[ch]._data[_channel[ch]._pos + 2]; + samples = durationToSamples(duration); + pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument); velocity = _channel[ch]._data[_channel[ch]._pos + 3]; _channel[ch]._pos += 4; + + if (_channel[ch]._pos >= _channel[ch]._length) { + samples = _lastNoteSamples[ch]; + } return true; } diff --git a/engines/scumm/player_v5m.h b/engines/scumm/player_v5m.h index 169fa89320f2..b2079ee331f5 100644 --- a/engines/scumm/player_v5m.h +++ b/engines/scumm/player_v5m.h @@ -46,7 +46,10 @@ class Player_V5M : public Player_Mac { virtual bool checkMusicAvailable(); virtual bool loadMusic(const byte *ptr); - virtual bool getNextNote(int ch, uint16 &duration, byte ¬e, byte &velocity); + virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity); + +private: + uint32 _lastNoteSamples[3]; }; } // End of namespace Scumm From 34a8b5049e903f33a26b56da5fa32986a023feec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Sun, 18 Nov 2012 17:17:22 +0100 Subject: [PATCH 17/26] SCUMM: Use more correct (I think) way of converting samples It shouldn't make any real difference, but it's probably more formally correct. --- engines/scumm/player_mac.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp index 470bdaff3002..2286a158ab13 100644 --- a/engines/scumm/player_mac.cpp +++ b/engines/scumm/player_mac.cpp @@ -372,7 +372,7 @@ void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int } } - int newSample = ((_data[_pos] - 129) * 128 * volume) / 255; + int newSample = (((int16)((_data[_pos] << 8) ^ 0x8000)) * volume) / 255; // Fade out the last 100 samples on each note. Even at low // output sample rates this is just a fraction of a second, From ae823b5c6a31a568072545503dbc5c125d19c391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Sun, 18 Nov 2012 17:49:23 +0100 Subject: [PATCH 18/26] SCUMM: Fix regression that caused "pops" in MI1 jungle music Properly treat rests as rests, not notes. Otherwise, it would try to play a really low note which just came out as a "pop". --- engines/scumm/player_v3m.cpp | 9 +++++++-- engines/scumm/player_v5m.cpp | 11 +++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/engines/scumm/player_v3m.cpp b/engines/scumm/player_v3m.cpp index db532a9f6e0d..ad812a53ca7a 100644 --- a/engines/scumm/player_v3m.cpp +++ b/engines/scumm/player_v3m.cpp @@ -168,8 +168,13 @@ bool Player_V3M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte & uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]); byte note = _channel[ch]._data[_channel[ch]._pos + 2]; samples = durationToSamples(duration); - pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument); - velocity = 127; + if (note > 0) { + pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument); + velocity = 127; + } else { + pitchModifier = 0; + velocity = 0; + } _channel[ch]._pos += 3; return true; } diff --git a/engines/scumm/player_v5m.cpp b/engines/scumm/player_v5m.cpp index d59cf9ade2ae..26cfb0e7c1fd 100644 --- a/engines/scumm/player_v5m.cpp +++ b/engines/scumm/player_v5m.cpp @@ -186,8 +186,15 @@ bool Player_V5M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte & uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]); byte note = _channel[ch]._data[_channel[ch]._pos + 2]; samples = durationToSamples(duration); - pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument); - velocity = _channel[ch]._data[_channel[ch]._pos + 3]; + + if (note > 1) { + pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument); + velocity = _channel[ch]._data[_channel[ch]._pos + 3]; + } else { + pitchModifier = 0; + velocity = 0; + } + _channel[ch]._pos += 4; if (_channel[ch]._pos >= _channel[ch]._length) { From 94b0881427c9120e5e638872840e24297bad2a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Sun, 18 Nov 2012 19:18:21 +0100 Subject: [PATCH 19/26] SCUMM: Lock the sound resource while the music is playing After some discussion on #scummvm, the player now locks the sound resource while the music is playing. This prevents the resource manager from expiring the resource, which at best could cause music to restart where it shouldn't.. At worst, I guess it could have crashed, but I never saw that happen. --- engines/scumm/player_mac.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp index 2286a158ab13..ef97c2d45227 100644 --- a/engines/scumm/player_mac.cpp +++ b/engines/scumm/player_mac.cpp @@ -25,6 +25,7 @@ #include "engines/engine.h" #include "gui/message.h" #include "scumm/player_mac.h" +#include "scumm/resource.h" #include "scumm/scumm.h" #include "scumm/imuse/imuse.h" @@ -163,6 +164,9 @@ void Player_Mac::setMusicVolume(int vol) { } void Player_Mac::stopAllSounds_Internal() { + if (_soundPlaying != -1) { + _vm->_res->unlock(rtSound, _soundPlaying); + } _soundPlaying = -1; for (int i = 0; i < _numberOfChannels; i++) { // The channel data is managed by the resource manager, so @@ -194,6 +198,8 @@ void Player_Mac::startSound(int nr) { Common::StackLock lock(_mutex); debug(5, "Player_Mac::startSound(%d)", nr); + stopAllSounds_Internal(); + const byte *ptr = _vm->getResourceAddress(rtSound, nr); assert(ptr); @@ -201,11 +207,11 @@ void Player_Mac::startSound(int nr) { return; } + _vm->_res->lock(rtSound, nr); _soundPlaying = nr; } bool Player_Mac::Channel::loadInstrument(Common::SeekableReadStream *stream) { - // Load the sound uint16 soundType = stream->readUint16BE(); if (soundType != 1) { warning("Player_Mac::loadInstrument: Unsupported sound type %d", soundType); From cb21d7309e1f559ce5e99ad7fe826f9ecd00ac37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Mon, 19 Nov 2012 07:16:42 +0100 Subject: [PATCH 20/26] SCUMM: Add Windows encoding of the Mac Loom filename --- engines/scumm/player_v3m.cpp | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/engines/scumm/player_v3m.cpp b/engines/scumm/player_v3m.cpp index ad812a53ca7a..6a675f2ca8ad 100644 --- a/engines/scumm/player_v3m.cpp +++ b/engines/scumm/player_v3m.cpp @@ -105,12 +105,29 @@ Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer) // not sure if stream 4 is ever used, but let's use it just in case. } +// \xAA is a trademark glyph in Mac OS Roman. We try that, but also the Windows +// version, the UTF-8 version, and just plain without in case the file system +// can't handle exotic characters like that. + +static const char *loomFileNames[] = { + "Loom\xAA", + "Loom\x99", + "Loom\xE2\x84\xA2", + "Loom" +}; + bool Player_V3M::checkMusicAvailable() { Common::MacResManager resource; - // \xAA is a trademark glyph in Mac OS Roman. We try that, but also the - // UTF-8 version, and just plain without in case the file system can't - // handle exotic characters like that. - if (!resource.exists("Loom\xAA") && !resource.exists("Loom\xE2\x84\xA2") && !resource.exists("Loom")) { + bool found = false; + + for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) { + if (resource.exists(loomFileNames[i])) { + found = true; + break; + } + } + + if (!found) { GUI::MessageDialog dialog(_( "Could not find the 'Loom' Macintosh executable to read the\n" "instruments from. Music will be disabled."), _("OK")); @@ -123,7 +140,16 @@ bool Player_V3M::checkMusicAvailable() { bool Player_V3M::loadMusic(const byte *ptr) { Common::MacResManager resource; - if (!resource.open("Loom\xAA") && !resource.open("Loom\xE2\x84\xA2") && !resource.open("Loom")) { + bool found = false; + + for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) { + if (resource.open(loomFileNames[i])) { + found = true; + break; + } + } + + if (!found) { return false; } From 9e995991e7598f235392700f808ed8aebdc64b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Mon, 19 Nov 2012 07:18:05 +0100 Subject: [PATCH 21/26] SCUMM: Fix crash when Macintosh instruments aren't available Initialise _channel[] even when the instruments aren't available. Otherwise, ScummVM will crash in a number of places including, but not limited to, when loading savegames. --- engines/scumm/player_mac.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp index ef97c2d45227..6cfbec398e8d 100644 --- a/engines/scumm/player_mac.cpp +++ b/engines/scumm/player_mac.cpp @@ -43,11 +43,6 @@ Player_Mac::Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChan } void Player_Mac::init() { - if (!checkMusicAvailable()) { - _channel = NULL; - return; - } - _channel = new Player_Mac::Channel[_numberOfChannels]; int i; @@ -89,6 +84,10 @@ void Player_Mac::init() { setMusicVolume(255); + if (!checkMusicAvailable()) { + return; + } + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); } From fc0288e9d5928597b2c9f3d21ebc467b9255239d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Mon, 19 Nov 2012 07:25:42 +0100 Subject: [PATCH 22/26] SCUMM: Simplify checkMusicAvailable() a bit --- engines/scumm/player_v3m.cpp | 18 ++++++------------ engines/scumm/player_v5m.cpp | 15 ++++++++------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/engines/scumm/player_v3m.cpp b/engines/scumm/player_v3m.cpp index 6a675f2ca8ad..35d2aaac8949 100644 --- a/engines/scumm/player_v3m.cpp +++ b/engines/scumm/player_v3m.cpp @@ -118,24 +118,18 @@ static const char *loomFileNames[] = { bool Player_V3M::checkMusicAvailable() { Common::MacResManager resource; - bool found = false; for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) { if (resource.exists(loomFileNames[i])) { - found = true; - break; + return true; } } - if (!found) { - GUI::MessageDialog dialog(_( - "Could not find the 'Loom' Macintosh executable to read the\n" - "instruments from. Music will be disabled."), _("OK")); - dialog.runModal(); - return false; - } - - return true; + GUI::MessageDialog dialog(_( + "Could not find the 'Loom' Macintosh executable to read the\n" + "instruments from. Music will be disabled."), _("OK")); + dialog.runModal(); + return false; } bool Player_V3M::loadMusic(const byte *ptr) { diff --git a/engines/scumm/player_v5m.cpp b/engines/scumm/player_v5m.cpp index 26cfb0e7c1fd..254b2c73178c 100644 --- a/engines/scumm/player_v5m.cpp +++ b/engines/scumm/player_v5m.cpp @@ -88,15 +88,16 @@ Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer) bool Player_V5M::checkMusicAvailable() { Common::MacResManager resource; - if (!resource.exists("Monkey Island")) { - GUI::MessageDialog dialog(_( - "Could not find the 'Monkey Island' Macintosh executable to read the\n" - "instruments from. Music will be disabled."), _("OK")); - dialog.runModal(); - return false; + + if (resource.exists("Monkey Island")) { + return true; } - return true; + GUI::MessageDialog dialog(_( + "Could not find the 'Monkey Island' Macintosh executable to read the\n" + "instruments from. Music will be disabled."), _("OK")); + dialog.runModal(); + return false; } bool Player_V5M::loadMusic(const byte *ptr) { From 2aa8d0d65d784f0f4d9f5a8471626b5ffa046b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Mon, 19 Nov 2012 08:40:51 +0100 Subject: [PATCH 23/26] SCUMM: Try harder to find Mac Monkey Island instruments At least for me, hfsutils turns spaces into underscores so try both "Monkey Island" and "Monkey_Island". --- engines/scumm/player_v5m.cpp | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/engines/scumm/player_v5m.cpp b/engines/scumm/player_v5m.cpp index 254b2c73178c..1cb9831621b5 100644 --- a/engines/scumm/player_v5m.cpp +++ b/engines/scumm/player_v5m.cpp @@ -86,11 +86,21 @@ Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer) assert(_vm->_game.id == GID_MONKEY); } +// Try both with and without underscore in the filename, because hfsutils may +// turn the space into an underscore. At least, it did for me. + +static const char *monkeyIslandFileNames[] = { + "Monkey Island", + "Monkey_Island" +}; + bool Player_V5M::checkMusicAvailable() { Common::MacResManager resource; - if (resource.exists("Monkey Island")) { - return true; + for (int i = 0; i < ARRAYSIZE(monkeyIslandFileNames); i++) { + if (resource.exists(monkeyIslandFileNames[i])) { + return true; + } } GUI::MessageDialog dialog(_( @@ -102,11 +112,19 @@ bool Player_V5M::checkMusicAvailable() { bool Player_V5M::loadMusic(const byte *ptr) { Common::MacResManager resource; - if (!resource.open("Monkey Island")) { - return false; + bool found = false; + uint i; + + for (i = 0; i < ARRAYSIZE(monkeyIslandFileNames); i++) { + if (resource.open(monkeyIslandFileNames[i])) { + found = true; + break; + } } - uint i; + if (!found) { + return false; + } ptr += 8; // TODO: Decipher the unknown bytes in the header. For now, skip 'em From d06f69f94b6cb2fcf51c3d0018a960d5d0c50051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Fri, 23 Nov 2012 06:01:30 +0100 Subject: [PATCH 24/26] SCUMM: Fix memory leak when loading old MI1 Mac savegames I completely forgot to delete the dummy iMUSE object after using it to skip over the old music save information. Thanks to Lordhoto for pointing this out. --- engines/scumm/player_mac.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp index 6cfbec398e8d..fa0dd9d052aa 100644 --- a/engines/scumm/player_mac.cpp +++ b/engines/scumm/player_mac.cpp @@ -104,6 +104,7 @@ void Player_Mac::saveLoadWithSerializer(Serializer *ser) { if (_vm->_game.id == GID_MONKEY && ser->isLoading()) { IMuse *dummyImuse = IMuse::create(_vm->_system, NULL, NULL); dummyImuse->save_or_load(ser, _vm, false); + delete dummyImuse; } } else { static const SaveLoadEntry musicEntries[] = { From 076bcbc5c6b4c0f34e919dccfb0d407e88138560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Fri, 23 Nov 2012 06:54:25 +0100 Subject: [PATCH 25/26] SCUMM: Added TODO comment about Mac MI1 music Some notes in the main theme are very staccato, and this could possibly explain why. --- engines/scumm/player_mac.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp index fa0dd9d052aa..a91050e88957 100644 --- a/engines/scumm/player_mac.cpp +++ b/engines/scumm/player_mac.cpp @@ -298,6 +298,15 @@ uint32 Player_Mac::durationToSamples(uint16 duration) { } int Player_Mac::noteToPitchModifier(byte note, Instrument *instrument) { + // TODO: Monkey Island 1 uses both note values 0 and 1 as rests. + // Perhaps 0 means an abrupt end of the current note, while 1 means it + // should drop off gradually? One of the voices in the main theme + // sounds a lot more staccato than what I hear in a Mac emulator. (But + // it's hard to tell since that emulator has problems with the music.) + // Also, some instruments (though not this particular one) have data + // after the loop end point, which could possible be used to fade out + // the instrument. + if (note > 1) { const int pitchIdx = note + 60 - instrument->_baseFreq; // I don't want to use floating-point arithmetics here, but I From d3cf4d10f27a6349f08bee66713e9032d81cb8f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Andersson?= Date: Sat, 24 Nov 2012 01:39:16 +0100 Subject: [PATCH 26/26] SCUMM: Handle note value 1 as "hold current note" in MI1 Mac After listening to the original music in a Mac emulator (which unfortunately doesn't handle the music very well), I can only conclude that note value 1 means the note should continue playing. At first I thought maybe it was supposed to fade the current note, or perhaps change its volume, but I can't hear any traces of either. So I'm going to assume it just means "hold the current note", though for the life of me I cannot think of any valid reason for such a command. So it may be wrong, but it sounds closer to the emulator than it did before. --- engines/scumm/player_mac.cpp | 47 ++++++++++++++++++------------------ engines/scumm/player_mac.h | 5 ++-- engines/scumm/player_v3m.cpp | 2 +- engines/scumm/player_v5m.cpp | 25 +++++++++++++++++-- 4 files changed, 50 insertions(+), 29 deletions(-) diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp index a91050e88957..bbb97d360ad8 100644 --- a/engines/scumm/player_mac.cpp +++ b/engines/scumm/player_mac.cpp @@ -31,13 +31,14 @@ namespace Scumm { -Player_Mac::Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask) +Player_Mac::Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask, bool fadeNoteEnds) : _vm(scumm), _mixer(mixer), _sampleRate(_mixer->getOutputRate()), _soundPlaying(-1), _numberOfChannels(numberOfChannels), - _channelMask(channelMask) { + _channelMask(channelMask), + _fadeNoteEnds(fadeNoteEnds) { assert(scumm); assert(mixer); } @@ -298,16 +299,7 @@ uint32 Player_Mac::durationToSamples(uint16 duration) { } int Player_Mac::noteToPitchModifier(byte note, Instrument *instrument) { - // TODO: Monkey Island 1 uses both note values 0 and 1 as rests. - // Perhaps 0 means an abrupt end of the current note, while 1 means it - // should drop off gradually? One of the voices in the main theme - // sounds a lot more staccato than what I hear in a Mac emulator. (But - // it's hard to tell since that emulator has problems with the music.) - // Also, some instruments (though not this particular one) have data - // after the loop end point, which could possible be used to fade out - // the instrument. - - if (note > 1) { + if (note > 0) { const int pitchIdx = note + 60 - instrument->_baseFreq; // I don't want to use floating-point arithmetics here, but I // ran into overflow problems with the church music in Monkey @@ -356,7 +348,7 @@ int Player_Mac::readBuffer(int16 *data, const int numSamples) { } generated = MIN(_channel[i]._remaining, samplesLeft); if (_channel[i]._velocity != 0) { - _channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated, _channel[i]._remaining); + _channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated, _channel[i]._remaining, _fadeNoteEnds); } ptr += generated; samplesLeft -= generated; @@ -375,7 +367,7 @@ int Player_Mac::readBuffer(int16 *data, const int numSamples) { return numSamples; } -void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote) { +void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote, bool fadeNoteEnds) { int samplesLeft = numSamples; while (samplesLeft) { _subPos += pitchModifier; @@ -389,16 +381,23 @@ void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int int newSample = (((int16)((_data[_pos] << 8) ^ 0x8000)) * volume) / 255; - // Fade out the last 100 samples on each note. Even at low - // output sample rates this is just a fraction of a second, - // but it gets rid of distracting "pops" at the end when the - // sample would otherwise go abruptly from something to - // nothing. This was particularly noticeable on the distaff - // notes in Loom. - - remainingSamplesOnNote--; - if (remainingSamplesOnNote < 100) { - newSample = (newSample * remainingSamplesOnNote) / 100; + if (fadeNoteEnds) { + // Fade out the last 100 samples on each note. Even at + // low output sample rates this is just a fraction of a + // second, but it gets rid of distracting "pops" at the + // end when the sample would otherwise go abruptly from + // something to nothing. This was particularly + // noticeable on the distaff notes in Loom. + // + // The reason it's conditional is that Monkey Island + // appears to have a "hold current note" command, and + // if we fade out the current note in that case we + // will actually introduce new "pops". + + remainingSamplesOnNote--; + if (remainingSamplesOnNote < 100) { + newSample = (newSample * remainingSamplesOnNote) / 100; + } } int sample = *data + newSample; diff --git a/engines/scumm/player_mac.h b/engines/scumm/player_mac.h index c46495c33373..09307b4e572d 100644 --- a/engines/scumm/player_mac.h +++ b/engines/scumm/player_mac.h @@ -44,7 +44,7 @@ class ScummEngine; */ class Player_Mac : public Audio::AudioStream, public MusicEngine { public: - Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask); + Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask, bool fadeNoteEnds); virtual ~Player_Mac(); void init(); @@ -90,12 +90,13 @@ class Player_Mac : public Audio::AudioStream, public MusicEngine { _subPos = 0; } - void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote); + void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote, bool fadeNoteEnds); }; int _pitchTable[128]; int _numberOfChannels; int _channelMask; + bool _fadeNoteEnds; virtual bool checkMusicAvailable() { return false; } virtual bool loadMusic(const byte *ptr) { return false; } diff --git a/engines/scumm/player_v3m.cpp b/engines/scumm/player_v3m.cpp index 35d2aaac8949..e61463128a08 100644 --- a/engines/scumm/player_v3m.cpp +++ b/engines/scumm/player_v3m.cpp @@ -97,7 +97,7 @@ namespace Scumm { Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer) - : Player_Mac(scumm, mixer, 5, 0x1E) { + : Player_Mac(scumm, mixer, 5, 0x1E, true) { assert(_vm->_game.id == GID_LOOM); // Channel 0 seems to be what was played on low-end macs, that couldn't diff --git a/engines/scumm/player_v5m.cpp b/engines/scumm/player_v5m.cpp index 1cb9831621b5..500f3bbc4045 100644 --- a/engines/scumm/player_v5m.cpp +++ b/engines/scumm/player_v5m.cpp @@ -82,7 +82,7 @@ namespace Scumm { Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer) - : Player_Mac(scumm, mixer, 3, 0x07) { + : Player_Mac(scumm, mixer, 3, 0x07, false) { assert(_vm->_game.id == GID_MONKEY); } @@ -190,7 +190,6 @@ bool Player_V5M::loadMusic(const byte *ptr) { } bool Player_V5M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) { - _channel[ch]._instrument.newNote(); if (_channel[ch]._pos >= _channel[ch]._length) { if (!_channel[ch]._looped) { _channel[ch]._notesLeft = false; @@ -206,9 +205,31 @@ bool Player_V5M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte & byte note = _channel[ch]._data[_channel[ch]._pos + 2]; samples = durationToSamples(duration); + if (note != 1) { + _channel[ch]._instrument.newNote(); + } + if (note > 1) { pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument); velocity = _channel[ch]._data[_channel[ch]._pos + 3]; + } else if (note == 1) { + // This is guesswork, but Monkey Island uses two different + // "special" note values: 0, which is clearly a rest, and 1 + // which is... I thought at first it was a "soft" key off, to + // fade out the note, but listening to the music in a Mac + // emulator (which unfortunately doesn't work all that well), + // I hear no trace of fading out. + // + // It could mean "change the volume on the current note", but + // I can't hear that either, and it always seems to use the + // exact same velocity on this note. + // + // So it appears it really just is a "hold the current note", + // but why? Couldn't they just have made the original note + // longer? + + pitchModifier = _channel[ch]._pitchModifier; + velocity = _channel[ch]._velocity; } else { pitchModifier = 0; velocity = 0;