diff options
-rw-r--r-- | engines/scumm/imuse/imuse.cpp | 41 | ||||
-rw-r--r-- | engines/scumm/imuse/imuse_internal.h | 2 | ||||
-rw-r--r-- | engines/scumm/imuse/imuse_part.cpp | 13 | ||||
-rw-r--r-- | engines/scumm/imuse/imuse_player.cpp | 5 | ||||
-rw-r--r-- | engines/scumm/imuse/instrument.cpp | 60 | ||||
-rw-r--r-- | engines/scumm/imuse/instrument.h | 4 | ||||
-rw-r--r-- | engines/scumm/imuse/mac_m68k.cpp | 510 | ||||
-rw-r--r-- | engines/scumm/imuse/mac_m68k.h | 177 | ||||
-rw-r--r-- | engines/scumm/imuse/sysex_scumm.cpp | 2 | ||||
-rw-r--r-- | engines/scumm/module.mk | 1 | ||||
-rw-r--r-- | engines/scumm/saveload.h | 2 | ||||
-rw-r--r-- | engines/scumm/scumm.cpp | 31 | ||||
-rw-r--r-- | engines/scumm/sound.cpp | 5 |
13 files changed, 839 insertions, 14 deletions
diff --git a/engines/scumm/imuse/imuse.cpp b/engines/scumm/imuse/imuse.cpp index 27a72c2afe..016ba89e7b 100644 --- a/engines/scumm/imuse/imuse.cpp +++ b/engines/scumm/imuse/imuse.cpp @@ -164,7 +164,7 @@ bool IMuseInternal::isMT32(int sound) { return true; case MKTAG('M', 'A', 'C', ' '): // Occurs in the Mac version of FOA and MI2 - return true; + return false; case MKTAG('G', 'M', 'D', ' '): return false; @@ -226,6 +226,45 @@ bool IMuseInternal::isMIDI(int sound) { return false; } +bool IMuseInternal::supportsPercussion(int sound) { + byte *ptr = g_scumm->_res->_types[rtSound][sound]._address; + if (ptr == NULL) + return false; + + uint32 tag = READ_BE_UINT32(ptr); + switch (tag) { + case MKTAG('A', 'D', 'L', ' '): + case MKTAG('A', 'S', 'F', 'X'): // Special AD class for old AdLib sound effects + case MKTAG('S', 'P', 'K', ' '): + return false; + + case MKTAG('A', 'M', 'I', ' '): + case MKTAG('R', 'O', 'L', ' '): + return true; + + case MKTAG('M', 'A', 'C', ' '): // Occurs in the Mac version of FOA and MI2 + // This is MIDI, i.e. uses MIDI style program changes, but without a + // special percussion channel. + return false; + + case MKTAG('G', 'M', 'D', ' '): + case MKTAG('M', 'I', 'D', 'I'): // Occurs in Sam & Max + return true; + } + + // Old style 'RO' has equivalent properties to 'ROL' + if (ptr[0] == 'R' && ptr[1] == 'O') + return true; + // Euphony tracks show as 'SO' and have equivalent properties to 'ADL' + // FIXME: Right now we're pretending it's GM. + if (ptr[4] == 'S' && ptr[5] == 'O') + return true; + + error("Unknown music type: '%c%c%c%c'", (char)tag >> 24, (char)tag >> 16, (char)tag >> 8, (char)tag); + + return false; +} + MidiDriver *IMuseInternal::getBestMidiDriver(int sound) { MidiDriver *driver = NULL; diff --git a/engines/scumm/imuse/imuse_internal.h b/engines/scumm/imuse/imuse_internal.h index 3b0d36e119..846e2d7545 100644 --- a/engines/scumm/imuse/imuse_internal.h +++ b/engines/scumm/imuse/imuse_internal.h @@ -204,6 +204,7 @@ protected: bool _isMT32; bool _isMIDI; + bool _supportsPercussion; protected: // Player part @@ -458,6 +459,7 @@ protected: byte *findStartOfSound(int sound, int ct = (kMThd | kFORM)); bool isMT32(int sound); bool isMIDI(int sound); + bool supportsPercussion(int sound); int get_queue_sound_status(int sound) const; void handle_marker(uint id, byte data); int get_channel_volume(uint a); diff --git a/engines/scumm/imuse/imuse_part.cpp b/engines/scumm/imuse/imuse_part.cpp index 73e7704469..d4e4740451 100644 --- a/engines/scumm/imuse/imuse_part.cpp +++ b/engines/scumm/imuse/imuse_part.cpp @@ -27,6 +27,7 @@ #include "common/util.h" #include "scumm/imuse/imuse_internal.h" #include "scumm/saveload.h" +#include "scumm/scumm.h" namespace Scumm { @@ -365,7 +366,17 @@ void Part::set_instrument(uint b) { _bank = (byte)(b >> 8); if (_bank) error("Non-zero instrument bank selection. Please report this"); - _instrument.program((byte)b, _player->isMT32()); + // HACK: Horrible hack to allow tracing of program change source. + // The Mac version of Monkey Island 2 uses a different program "bank" + // when it gets program change events through the iMuse SysEx handler. + // We emulate this by introducing a special instrument, which sets + // the instrument via sysEx_customInstrument. This seems to be + // exclusively used for special sound effects like the "spit" sound. + if (g_scumm->_game.id == GID_MONKEY2 && g_scumm->_game.platform == Common::kPlatformMacintosh) { + _instrument.macSfx(b); + } else { + _instrument.program((byte)b, _player->isMT32()); + } if (clearToTransmit()) _instrument.send(_mc); } diff --git a/engines/scumm/imuse/imuse_player.cpp b/engines/scumm/imuse/imuse_player.cpp index 53ccfb3734..3a9c42a920 100644 --- a/engines/scumm/imuse/imuse_player.cpp +++ b/engines/scumm/imuse/imuse_player.cpp @@ -78,6 +78,7 @@ Player::Player() : _speed(128), _isMT32(false), _isMIDI(false), + _supportsPercussion(false), _se(0), _vol_chan(0) { } @@ -103,6 +104,7 @@ bool Player::startSound(int sound, MidiDriver *midi) { _isMT32 = _se->isMT32(sound); _isMIDI = _se->isMIDI(sound); + _supportsPercussion = _se->supportsPercussion(sound); _parts = NULL; _active = true; @@ -386,6 +388,8 @@ void Player::sysEx(const byte *p, uint16 len) { // SysEx manufacturer 0x97 has been spotted in the // Monkey Island 2 AdLib music, so don't make this a // fatal error. See bug #1481383. + // The Macintosh version of Monkey Island 2 simply + // ignores these SysEx events too. if (a == 0) warning("Unknown SysEx manufacturer 0x00 0x%02X 0x%02X", p[0], p[1]); else @@ -1009,6 +1013,7 @@ void Player::fixAfterLoad() { _parser->jumpToTick(_music_tick); // start_seq_sound already switched tracks _isMT32 = _se->isMT32(_id); _isMIDI = _se->isMIDI(_id); + _supportsPercussion = _se->supportsPercussion(_id); } } diff --git a/engines/scumm/imuse/instrument.cpp b/engines/scumm/imuse/instrument.cpp index 11bb4e7605..61c73b1e2d 100644 --- a/engines/scumm/imuse/instrument.cpp +++ b/engines/scumm/imuse/instrument.cpp @@ -278,6 +278,21 @@ private: byte _instrument[23]; }; +class Instrument_MacSfx : public InstrumentInternal { +private: + byte _program; + +public: + Instrument_MacSfx(byte program); + Instrument_MacSfx(Serializer *s); + void saveOrLoad(Serializer *s); + void send(MidiChannel *mc); + void copy_to(Instrument *dest) { dest->macSfx(_program); } + bool is_valid() { + return (_program < 128); + } +}; + //////////////////////////////////////// // // Instrument class members @@ -326,6 +341,14 @@ void Instrument::pcspk(const byte *instrument) { _instrument = new Instrument_PcSpk(instrument); } +void Instrument::macSfx(byte prog) { + clear(); + if (prog > 127) + return; + _type = itMacSfx; + _instrument = new Instrument_MacSfx(prog); +} + void Instrument::saveOrLoad(Serializer *s) { if (s->isSaving()) { s->saveByte(_type); @@ -349,6 +372,9 @@ void Instrument::saveOrLoad(Serializer *s) { case itPcSpk: _instrument = new Instrument_PcSpk(s); break; + case itMacSfx: + _instrument = new Instrument_MacSfx(s); + break; default: warning("No known instrument classification #%d", (int)_type); _type = itNone; @@ -528,4 +554,38 @@ void Instrument_PcSpk::send(MidiChannel *mc) { mc->sysEx_customInstrument('SPK ', (byte *)&_instrument); } +//////////////////////////////////////// +// +// Instrument_MacSfx class members +// +//////////////////////////////////////// + +Instrument_MacSfx::Instrument_MacSfx(byte program) : + _program(program) { + if (program > 127) { + _program = 255; + } +} + +Instrument_MacSfx::Instrument_MacSfx(Serializer *s) { + _program = 255; + if (!s->isSaving()) { + saveOrLoad(s); + } +} + +void Instrument_MacSfx::saveOrLoad(Serializer *s) { + if (s->isSaving()) { + s->saveByte(_program); + } else { + _program = s->loadByte(); + } +} + +void Instrument_MacSfx::send(MidiChannel *mc) { + if (_program > 127) { + return; + } + mc->sysEx_customInstrument('MAC ', &_program); +} } // End of namespace Scumm diff --git a/engines/scumm/imuse/instrument.h b/engines/scumm/imuse/instrument.h index a855c64155..7e09e86fa5 100644 --- a/engines/scumm/imuse/instrument.h +++ b/engines/scumm/imuse/instrument.h @@ -52,7 +52,8 @@ public: itProgram = 1, itAdLib = 2, itRoland = 3, - itPcSpk = 4 + itPcSpk = 4, + itMacSfx = 5 }; Instrument() : _type(0), _instrument(0) { } @@ -72,6 +73,7 @@ public: void adlib(const byte *instrument); void roland(const byte *instrument); void pcspk(const byte *instrument); + void macSfx(byte program); byte getType() { return _type; } bool isValid() { return (_instrument ? _instrument->is_valid() : false); } diff --git a/engines/scumm/imuse/mac_m68k.cpp b/engines/scumm/imuse/mac_m68k.cpp new file mode 100644 index 0000000000..4d7a6a64c0 --- /dev/null +++ b/engines/scumm/imuse/mac_m68k.cpp @@ -0,0 +1,510 @@ +/* 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 "scumm/imuse/mac_m68k.h" + +#include "common/util.h" +#include "common/macresman.h" +#include "common/stream.h" + +namespace Scumm { + +MacM68kDriver::MacM68kDriver(Audio::Mixer *mixer) + : MidiDriver_Emulated(mixer) { +} + +MacM68kDriver::~MacM68kDriver() { +} + +int MacM68kDriver::open() { + if (_isOpen) { + return MERR_ALREADY_OPEN; + } + + const int error = MidiDriver_Emulated::open(); + if (error) { + return error; + } + + for (uint i = 0; i < ARRAYSIZE(_channels); ++i) { + _channels[i].init(this, i); + } + + memset(_voiceChannels, 0, sizeof(_voiceChannels)); + _lastUsedVoiceChannel = 0; + + loadAllInstruments(); + + _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 (int i = 115; i >= 0; --i) { + _pitchTable[i] = _pitchTable[i + 12] / 2; + } + + _volumeTable = new byte[8192]; + for (int i = 0; i < 32; ++i) { + for (int j = 0; j < 256; ++j) { + _volumeTable[i * 256 + j] = ((-128 + j) * _volumeBaseTable[i]) / 127 - 128; + } + } + + _mixBuffer = 0; + _mixBufferLength = 0; + + // We set the output sound type to music here to allow sound volume + // adjustment. The drawback here is that we can not control the music and + // sfx separately here. But the AdLib output has the same issue so it + // should not be that bad. + _mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + + return 0; +} + +void MacM68kDriver::close() { + if (!_isOpen) { + return; + } + + _mixer->stopHandle(_mixerSoundHandle); + _isOpen = false; + for (InstrumentMap::iterator i = _instruments.begin(); i != _instruments.end(); ++i) { + delete[] i->_value.data; + } + _instruments.clear(); + delete[] _volumeTable; + _volumeTable = 0; + delete[] _mixBuffer; + _mixBuffer = 0; + _mixBufferLength = 0; +} + +void MacM68kDriver::send(uint32 d) { + assert(false); +} + +void MacM68kDriver::sysEx_customInstrument(byte channel, uint32 type, const byte *instr) { + assert(false); +} + +MidiChannel *MacM68kDriver::allocateChannel() { + for (uint i = 0; i < ARRAYSIZE(_channels); ++i) { + if (_channels[i].allocate()) { + return &_channels[i]; + } + } + + return 0; +} + +MacM68kDriver::Instrument MacM68kDriver::getInstrument(int idx) const { + InstrumentMap::const_iterator i = _instruments.find(idx); + if (i != _instruments.end()) { + return i->_value; + } else { + return _defaultInstrument; + } +} + +void MacM68kDriver::generateSamples(int16 *buf, int len) { + int silentChannels = 0; + + if (_mixBufferLength < len) { + delete[] _mixBuffer; + + _mixBufferLength = len; + _mixBuffer = new int[_mixBufferLength]; + assert(_mixBuffer); + } + memset(_mixBuffer, 0, sizeof(int) * _mixBufferLength); + + for (int i = 0; i < kChannelCount; ++i) { + OutputChannel &out = _voiceChannels[i].out; + if (out.isFinished) { + ++silentChannels; + continue; + } + + byte *volumeTable = &_volumeTable[(out.volume / 4) * 256]; + int *buffer = _mixBuffer; + + int samplesLeft = len; + while (samplesLeft) { + out.subPos += out.pitchModifier; + while (out.subPos >= 0x10000) { + out.subPos -= 0x10000; + out.instrument++; + } + + if (out.instrument >= out.end) { + if (!out.start) { + break; + } + + out.instrument = out.start; + out.subPos = 0; + } + + *buffer++ += volumeTable[*out.instrument]; + --samplesLeft; + } + + if (samplesLeft) { + out.isFinished = true; + while (samplesLeft--) { + *buffer++ += 0x80; + } + } + } + + const int *buffer = _mixBuffer; + const int silenceAdd = silentChannels << 7; + while (len--) { + *buf++ = (((*buffer++ + silenceAdd) >> 3) << 8) ^ 0x8000; + } +} + +void MacM68kDriver::loadAllInstruments() { + Common::MacResManager resource; + if (resource.open("iMUSE Setups")) { + for (int i = 0x3E7; i < 0x468; ++i) { + Common::SeekableReadStream *stream = resource.getResource(MKTAG('s', 'n', 'd', ' '), i); + if (stream) { + addInstrument(i, stream); + delete stream; + } + } + + for (int i = 0x7D0; i < 0x8D0; ++i) { + Common::SeekableReadStream *stream = resource.getResource(MKTAG('s', 'n', 'd', ' '), i); + if (stream) { + addInstrument(i, stream); + delete stream; + } + } + + InstrumentMap::iterator inst = _instruments.find(kDefaultInstrument); + if (inst != _instruments.end()) { + _defaultInstrument = inst->_value; + } else { + error("MacM68kDriver::loadAllInstruments: Could not load default instrument"); + } + } else { + error("MacM68kDriver::loadAllInstruments: Could not load \"iMUSE Setups\""); + } +} + +void MacM68kDriver::addInstrument(int idx, Common::SeekableReadStream *data) { + // We parse the "SND" files manually here, since we need special data + // from their header and need to work on them raw while mixing. + data->skip(2); + int count = data->readUint16BE(); + data->skip(2 * (3 * count)); + count = data->readUint16BE(); + data->skip(2 * (4 * count)); + + Instrument inst; + // Skip (optional) pointer to data + data->skip(4); + inst.length = data->readUint32BE(); + inst.sampleRate = data->readUint32BE(); + inst.loopStart = data->readUint32BE(); + inst.loopEnd = data->readUint32BE(); + // Skip encoding + data->skip(1); + inst.baseFrequency = data->readByte(); + + inst.data = new byte[inst.length]; + assert(inst.data); + data->read(inst.data, inst.length); + _instruments[idx] = inst; +} + +void MacM68kDriver::setPitch(OutputChannel *out, int frequency) { + out->frequency = frequency; + out->isFinished = false; + + const int pitchIdx = (frequency >> 7) + 60 - out->baseFrequency; + assert(pitchIdx >= 0); + + const int low7Bits = frequency & 0x7F; + if (low7Bits) { + out->pitchModifier = _pitchTable[pitchIdx] + (((_pitchTable[pitchIdx + 1] - _pitchTable[pitchIdx]) * low7Bits) >> 7); + } else { + out->pitchModifier = _pitchTable[pitchIdx]; + } +} + +void MacM68kDriver::VoiceChannel::off() { + if (out.start) { + out.isFinished = true; + } + + part->removeVoice(this); + part = 0; +} + +void MacM68kDriver::MidiChannel_MacM68k::release() { + _allocated = false; + while (_voice) { + _voice->off(); + } +} + +void MacM68kDriver::MidiChannel_MacM68k::send(uint32 b) { + uint8 type = b & 0xF0; + uint8 p1 = (b >> 8) & 0xFF; + uint8 p2 = (b >> 16) & 0xFF; + + switch (type) { + case 0x80: + noteOff(p1); + break; + + case 0x90: + if (p2) { + noteOn(p1, p2); + } else { + noteOff(p1); + } + break; + + case 0xB0: + controlChange(p1, p2); + break; + + case 0xE0: + pitchBend((p1 | (p2 << 7)) - 0x2000); + break; + + default: + break; + } +} + +void MacM68kDriver::MidiChannel_MacM68k::noteOff(byte note) { + for (VoiceChannel *i = _voice; i; i = i->next) { + if (i->note == note) { + if (_sustain) { + i->sustainNoteOff = true; + } else { + i->off(); + } + } + } +} + +void MacM68kDriver::MidiChannel_MacM68k::noteOn(byte note, byte velocity) { + // Do not start a not unless there is an instrument set up + if (!_instrument.data) { + return; + } + + // Allocate a voice channel + VoiceChannel *voice = _owner->allocateVoice(_priority); + if (!voice) { + return; + } + addVoice(voice); + + voice->note = note; + // This completly ignores the note's volume, but is in accordance + // to the original. + voice->out.volume = _volume; + + // Set up the instrument data + voice->out.baseFrequency = _instrument.baseFrequency; + voice->out.soundStart = _instrument.data; + voice->out.soundEnd = _instrument.data + _instrument.length; + if (_instrument.loopEnd && _instrument.loopEnd - 12 > _instrument.loopStart) { + voice->out.loopStart = _instrument.data + _instrument.loopStart; + voice->out.loopEnd = _instrument.data + _instrument.loopEnd; + } else { + voice->out.loopStart = 0; + voice->out.loopEnd = voice->out.soundEnd; + } + + voice->out.start = voice->out.loopStart; + voice->out.end = voice->out.loopEnd; + + // Set up the pitch + _owner->setPitch(&voice->out, (note << 7) + _pitchBend); + + // Set up the sample position + voice->out.instrument = voice->out.soundStart; + voice->out.subPos = 0; +} + +void MacM68kDriver::MidiChannel_MacM68k::programChange(byte program) { + _instrument = _owner->getInstrument(program + kProgramChangeBase); +} + +void MacM68kDriver::MidiChannel_MacM68k::pitchBend(int16 bend) { + _pitchBend = (bend * _pitchBendFactor) >> 6; + for (VoiceChannel *i = _voice; i; i = i->next) { + _owner->setPitch(&i->out, (i->note << 7) + _pitchBend); + } +} + +void MacM68kDriver::MidiChannel_MacM68k::controlChange(byte control, byte value) { + switch (control) { + // volume change + case 7: + _volume = value; + for (VoiceChannel *i = _voice; i; i = i->next) { + i->out.volume = value; + i->out.isFinished = false; + } + break; + + // sustain + case 64: + _sustain = value; + if (!_sustain) { + for (VoiceChannel *i = _voice; i; i = i->next) { + if (i->sustainNoteOff) { + i->off(); + } + } + } + break; + + // all notes off + case 123: + for (VoiceChannel *i = _voice; i; i = i->next) { + i->off(); + } + break; + + default: + break; + } +} + +void MacM68kDriver::MidiChannel_MacM68k::pitchBendFactor(byte value) { + _pitchBendFactor = value; +} + +void MacM68kDriver::MidiChannel_MacM68k::priority(byte value) { + _priority = value; +} + +void MacM68kDriver::MidiChannel_MacM68k::sysEx_customInstrument(uint32 type, const byte *instr) { + assert(instr); + if (type == 'MAC ') { + _instrument = _owner->getInstrument(*instr + kSysExBase); + } +} + +void MacM68kDriver::MidiChannel_MacM68k::init(MacM68kDriver *owner, byte channel) { + _owner = owner; + _number = channel; + _allocated = false; +} + +bool MacM68kDriver::MidiChannel_MacM68k::allocate() { + if (_allocated) { + return false; + } + + _allocated = true; + _voice = 0; + _priority = 0; + memset(&_instrument, 0, sizeof(_instrument)); + _pitchBend = 0; + _pitchBendFactor = 0; + _volume = 0; + return true; +} + +void MacM68kDriver::MidiChannel_MacM68k::addVoice(VoiceChannel *voice) { + voice->next = _voice; + voice->prev = 0; + voice->part = this; + if (_voice) { + _voice->prev = voice; + } + _voice = voice; +} + +void MacM68kDriver::MidiChannel_MacM68k::removeVoice(VoiceChannel *voice) { + VoiceChannel *i = _voice; + while (i && i != voice) { + i = i->next; + } + + if (i) { + if (i->next) { + i->next->prev = i->prev; + } + + if (i->prev) { + i->prev->next = i->next; + } else { + _voice = i->next; + } + } +} + +MacM68kDriver::VoiceChannel *MacM68kDriver::allocateVoice(int priority) { + VoiceChannel *channel = 0; + for (int i = 0; i < kChannelCount; ++i) { + if (++_lastUsedVoiceChannel == kChannelCount) { + _lastUsedVoiceChannel = 0; + } + + VoiceChannel *cur = &_voiceChannels[_lastUsedVoiceChannel]; + if (!cur->part) { + memset(cur, 0, sizeof(*cur)); + return cur; + } else if (!cur->next) { + if (cur->part->_priority <= priority) { + priority = cur->part->_priority; + channel = cur; + } + } + } + + if (channel) { + channel->off(); + memset(channel, 0, sizeof(*channel)); + } + + return channel; +} + +const int MacM68kDriver::_volumeBaseTable[32] = { + 0, 0, 1, 1, 2, 3, 5, 6, + 8, 11, 13, 16, 19, 22, 26, 30, + 34, 38, 43, 48, 53, 58, 64, 70, + 76, 83, 89, 96, 104, 111, 119, 127 +}; + +} // End of namespace Scumm diff --git a/engines/scumm/imuse/mac_m68k.h b/engines/scumm/imuse/mac_m68k.h new file mode 100644 index 0000000000..59e2f68b9b --- /dev/null +++ b/engines/scumm/imuse/mac_m68k.h @@ -0,0 +1,177 @@ +/* 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_IMUSE_MAC_M68K_H +#define SCUMM_IMUSE_MAC_M68K_H + +#include "audio/softsynth/emumidi.h" + +#include "common/hashmap.h" + +namespace Common { +class SeekableReadStream; +} + +namespace Scumm { + +class MacM68kDriver : public MidiDriver_Emulated { + friend class MidiChannel_MacM68k; +public: + MacM68kDriver(Audio::Mixer *mixer); + ~MacM68kDriver(); + + virtual int open(); + virtual void close(); + + virtual void send(uint32 d); + virtual void sysEx_customInstrument(byte channel, uint32 type, const byte *instr); + + virtual MidiChannel *allocateChannel(); + virtual MidiChannel *getPercussionChannel() { return 0; } + + virtual bool isStereo() const { return false; } + virtual int getRate() const { + // The original is using a frequency of approx. 22254.54546 here. + // To be precise it uses the 16.16 fixed point value 0x56EE8BA3. + return 22254; + } + +protected: + virtual void generateSamples(int16 *buf, int len); + virtual void onTimer() {} + +private: + int *_mixBuffer; + int _mixBufferLength; + + struct Instrument { + uint length; + uint sampleRate; + uint loopStart; + uint loopEnd; + int baseFrequency; + + byte *data; + }; + + enum { + kDefaultInstrument = 0x3E7, + kProgramChangeBase = 0x3E8, + kSysExBase = 0x7D0 + }; + + Instrument getInstrument(int idx) const; + typedef Common::HashMap<int, Instrument> InstrumentMap; + InstrumentMap _instruments; + Instrument _defaultInstrument; + void loadAllInstruments(); + void addInstrument(int idx, Common::SeekableReadStream *data); + + struct OutputChannel { + int pitchModifier; + + const byte *instrument; + uint subPos; + + const byte *start; + const byte *end; + + const byte *soundStart; + const byte *soundEnd; + const byte *loopStart; + const byte *loopEnd; + + int frequency; + int volume; + + bool isFinished; + + int baseFrequency; + }; + + void setPitch(OutputChannel *out, int frequency); + int _pitchTable[128]; + + byte *_volumeTable; + static const int _volumeBaseTable[32]; + + class MidiChannel_MacM68k; + + struct VoiceChannel { + MidiChannel_MacM68k *part; + VoiceChannel *prev, *next; + int channel; + int note; + bool sustainNoteOff; + OutputChannel out; + + void off(); + }; + + class MidiChannel_MacM68k : public MidiChannel { + friend class MacM68kDriver; + public: + virtual MidiDriver *device() { return _owner; } + virtual byte getNumber() { return _number; } + virtual void release(); + + virtual void send(uint32 b); + virtual void noteOff(byte note); + virtual void noteOn(byte note, byte velocity); + virtual void programChange(byte program); + virtual void pitchBend(int16 bend); + virtual void controlChange(byte control, byte value); + virtual void pitchBendFactor(byte value); + virtual void priority(byte value); + virtual void sysEx_customInstrument(uint32 type, const byte *instr); + + void init(MacM68kDriver *owner, byte channel); + bool allocate(); + + void addVoice(VoiceChannel *voice); + void removeVoice(VoiceChannel *voice); + private: + MacM68kDriver *_owner; + bool _allocated; + int _number; + + VoiceChannel *_voice; + int _priority; + int _sustain; + Instrument _instrument; + int _pitchBend; + int _pitchBendFactor; + int _volume; + }; + + MidiChannel_MacM68k _channels[32]; + + enum { + kChannelCount = 8 + }; + VoiceChannel _voiceChannels[kChannelCount]; + int _lastUsedVoiceChannel; + VoiceChannel *allocateVoice(int priority); +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/imuse/sysex_scumm.cpp b/engines/scumm/imuse/sysex_scumm.cpp index 85ffc86f47..8f230ebac3 100644 --- a/engines/scumm/imuse/sysex_scumm.cpp +++ b/engines/scumm/imuse/sysex_scumm.cpp @@ -71,7 +71,7 @@ void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) { part->set_pri(buf[2]); part->volume(buf[3]); part->set_pan(buf[4]); - part->_percussion = player->_isMIDI ? ((buf[5] & 0x80) > 0) : false; + part->_percussion = player->_supportsPercussion ? ((buf[5] & 0x80) > 0) : false; part->set_transpose(buf[5]); part->set_detune(buf[6]); part->pitchBendFactor(buf[7]); diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk index 1f219f5187..8499c9bad3 100644 --- a/engines/scumm/module.mk +++ b/engines/scumm/module.mk @@ -27,6 +27,7 @@ MODULE_OBJS := \ imuse/imuse_part.o \ imuse/imuse_player.o \ imuse/instrument.o \ + imuse/mac_m68k.o \ imuse/pcspk.o \ imuse/sysex_samnmax.o \ imuse/sysex_scumm.o \ diff --git a/engines/scumm/saveload.h b/engines/scumm/saveload.h index d5f7ea526e..a640bc1e17 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 92 +#define CURRENT_VER 93 /** * An auxillary macro, used to specify savegame versions. We use this instead diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index d0f46f3e56..7b37517ac0 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -73,6 +73,7 @@ #include "scumm/util.h" #include "scumm/verbs.h" #include "scumm/imuse/pcspk.h" +#include "scumm/imuse/mac_m68k.h" #include "backends/audiocd/audiocd.h" @@ -1835,17 +1836,31 @@ void ScummEngine::setupMusic(int midi) { } else if (_game.version >= 3 && _game.heversion <= 62) { MidiDriver *nativeMidiDriver = 0; MidiDriver *adlibMidiDriver = 0; - - if (_sound->_musicType != MDT_ADLIB && _sound->_musicType != MDT_TOWNS && _sound->_musicType != MDT_PCSPK) + bool multi_midi = ConfMan.getBool("multi_midi") && _sound->_musicType != MDT_NONE && _sound->_musicType != MDT_PCSPK && (midi & MDT_ADLIB); + bool useOnlyNative = false; + + if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_MONKEY2) { + // We setup this driver as native MIDI driver to avoid playback + // of the Mac music via a selected MIDI device. + nativeMidiDriver = new MacM68kDriver(_mixer); + // The Mac driver is never MT-32. + _native_mt32 = false; + // Ignore non-native drivers. This also ignores the multi MIDI setting. + useOnlyNative = true; + } else if (_sound->_musicType != MDT_ADLIB && _sound->_musicType != MDT_TOWNS && _sound->_musicType != MDT_PCSPK) { nativeMidiDriver = MidiDriver::createMidi(dev); + } + if (nativeMidiDriver != NULL && _native_mt32) nativeMidiDriver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); - bool multi_midi = ConfMan.getBool("multi_midi") && _sound->_musicType != MDT_NONE && _sound->_musicType != MDT_PCSPK && (midi & MDT_ADLIB); - if (_sound->_musicType == MDT_ADLIB || _sound->_musicType == MDT_TOWNS || multi_midi) { - adlibMidiDriver = MidiDriver::createMidi(MidiDriver::detectDevice(_sound->_musicType == MDT_TOWNS ? MDT_TOWNS : MDT_ADLIB)); - adlibMidiDriver->property(MidiDriver::PROP_OLD_ADLIB, (_game.features & GF_SMALL_HEADER) ? 1 : 0); - } else if (_sound->_musicType == MDT_PCSPK) { - adlibMidiDriver = new PcSpkDriver(_mixer); + + if (!useOnlyNative) { + if (_sound->_musicType == MDT_ADLIB || _sound->_musicType == MDT_TOWNS || multi_midi) { + adlibMidiDriver = MidiDriver::createMidi(MidiDriver::detectDevice(_sound->_musicType == MDT_TOWNS ? MDT_TOWNS : MDT_ADLIB)); + adlibMidiDriver->property(MidiDriver::PROP_OLD_ADLIB, (_game.features & GF_SMALL_HEADER) ? 1 : 0); + } else if (_sound->_musicType == MDT_PCSPK) { + adlibMidiDriver = new PcSpkDriver(_mixer); + } } _imuse = IMuse::create(_system, nativeMidiDriver, adlibMidiDriver); diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index 1dc026ad52..15701f166d 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -248,7 +248,10 @@ void Sound::playSound(int soundID) { _mixer->playStream(Audio::Mixer::kSFXSoundType, NULL, stream, soundID); } // Support for sampled sound effects in Monkey Island 1 and 2 - else if (_vm->_game.platform != Common::kPlatformFMTowns && READ_BE_UINT32(ptr) == MKTAG('S','B','L',' ')) { + else if (_vm->_game.platform != Common::kPlatformFMTowns + // The Macintosh version of MI2 just ignores SBL effects. + && (_vm->_game.platform != Common::kPlatformMacintosh && _vm->_game.id != GID_MONKEY2) + && READ_BE_UINT32(ptr) == MKTAG('S','B','L',' ')) { debugC(DEBUG_SOUND, "Using SBL sound effect"); // SBL resources essentially contain VOC sound data. |