aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Schickel2012-09-20 08:29:03 -0700
committerJohannes Schickel2012-09-20 08:29:03 -0700
commit4522d182d44134d48ed0de86863f21a128f5f593 (patch)
treef9b4c0b1884515108f0ad007009c78e436febaad
parent0677871ea94356e0a4a224840de691c05c05e8c5 (diff)
parentc3f37fb1870fb946692b6d67e6d32d2958be2b3a (diff)
downloadscummvm-rg350-4522d182d44134d48ed0de86863f21a128f5f593.tar.gz
scummvm-rg350-4522d182d44134d48ed0de86863f21a128f5f593.tar.bz2
scummvm-rg350-4522d182d44134d48ed0de86863f21a128f5f593.zip
Merge pull request #279 from lordhoto/mi2-mac-sound
Add support for Monkey Island 2 Mac sound.
-rw-r--r--engines/scumm/imuse/imuse.cpp41
-rw-r--r--engines/scumm/imuse/imuse_internal.h2
-rw-r--r--engines/scumm/imuse/imuse_part.cpp13
-rw-r--r--engines/scumm/imuse/imuse_player.cpp5
-rw-r--r--engines/scumm/imuse/instrument.cpp60
-rw-r--r--engines/scumm/imuse/instrument.h4
-rw-r--r--engines/scumm/imuse/mac_m68k.cpp510
-rw-r--r--engines/scumm/imuse/mac_m68k.h177
-rw-r--r--engines/scumm/imuse/sysex_scumm.cpp2
-rw-r--r--engines/scumm/module.mk1
-rw-r--r--engines/scumm/saveload.h2
-rw-r--r--engines/scumm/scumm.cpp31
-rw-r--r--engines/scumm/sound.cpp5
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.