aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Schickel2011-07-08 04:43:45 +0200
committerJohannes Schickel2011-07-08 04:45:01 +0200
commited993e6350bd4926b4b9c94a014aa0f96a628ce7 (patch)
treeb6f0b19a5d7d8da8b61b98376e85b36867a13acd
parent3f04ad72a998dead1b6b43a8facd4d7a62d2119a (diff)
downloadscummvm-rg350-ed993e6350bd4926b4b9c94a014aa0f96a628ce7.tar.gz
scummvm-rg350-ed993e6350bd4926b4b9c94a014aa0f96a628ce7.tar.bz2
scummvm-rg350-ed993e6350bd4926b4b9c94a014aa0f96a628ce7.zip
SCUMM: Initial PC Speaker output implementation for SCUMM v5.
This is *not* complete yet.
-rw-r--r--engines/scumm/detection_tables.h8
-rw-r--r--engines/scumm/imuse/imuse.cpp6
-rw-r--r--engines/scumm/imuse/imuse_internal.h2
-rw-r--r--engines/scumm/imuse/imuse_part.cpp6
-rw-r--r--engines/scumm/imuse/instrument.cpp52
-rw-r--r--engines/scumm/imuse/instrument.h4
-rw-r--r--engines/scumm/imuse/pcspk.cpp408
-rw-r--r--engines/scumm/imuse/pcspk.h147
-rw-r--r--engines/scumm/imuse/sysex_scumm.cpp21
-rw-r--r--engines/scumm/module.mk1
-rw-r--r--engines/scumm/scumm.cpp6
-rw-r--r--engines/scumm/sound.cpp4
12 files changed, 653 insertions, 12 deletions
diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h
index e510c46cf2..11901f7565 100644
--- a/engines/scumm/detection_tables.h
+++ b/engines/scumm/detection_tables.h
@@ -243,11 +243,11 @@ static const GameSettings gameVariantsTable[] = {
{"monkey", "FM-TOWNS", 0, GID_MONKEY, 5, 0, MDT_TOWNS, GF_AUDIOTRACKS, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_NOMIDI | GUIO_MIDITOWNS},
{"monkey", "SEGA", 0, GID_MONKEY, 5, 0, MDT_NONE, GF_AUDIOTRACKS, Common::kPlatformSegaCD, GUIO_NOSPEECH | GUIO_NOMIDI},
- {"monkey2", "", 0, GID_MONKEY2, 5, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NOSPEECH},
- {"monkey2", "FM-TOWNS", 0, GID_MONKEY2, 5, 0, MDT_TOWNS | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_MIDITOWNS | GUIO_MIDIADLIB | GUIO_MIDIMT32},
+ {"monkey2", "", 0, GID_MONKEY2, 5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NOSPEECH},
+ {"monkey2", "FM-TOWNS", 0, GID_MONKEY2, 5, 0, MDT_PCSPK | MDT_TOWNS | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_MIDITOWNS | GUIO_MIDIADLIB | GUIO_MIDIMT32},
- {"atlantis", "", 0, GID_INDY4, 5, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NONE},
- {"atlantis", "Floppy", 0, GID_INDY4, 5, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NOSPEECH},
+ {"atlantis", "", 0, GID_INDY4, 5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NONE},
+ {"atlantis", "Floppy", 0, GID_INDY4, 5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NOSPEECH},
{"atlantis", "FM-TOWNS", 0, GID_INDY4, 5, 0, MDT_TOWNS | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, Common::kPlatformFMTowns, GUIO_MIDITOWNS | GUIO_MIDIADLIB | GUIO_MIDIMT32},
{"tentacle", "", 0, GID_TENTACLE, 6, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_GM, GF_USE_KEY, UNK, GUIO_NONE},
diff --git a/engines/scumm/imuse/imuse.cpp b/engines/scumm/imuse/imuse.cpp
index 317ef36cb9..961d0690d0 100644
--- a/engines/scumm/imuse/imuse.cpp
+++ b/engines/scumm/imuse/imuse.cpp
@@ -1674,6 +1674,12 @@ void IMuseInternal::setGlobalAdLibInstrument(byte slot, byte *data) {
}
}
+void IMuseInternal::setGlobalPcSpkInstrument(byte slot, byte *data) {
+ if (slot < 32) {
+ _global_adlib_instruments[slot].pcspk(data);
+ }
+}
+
void IMuseInternal::copyGlobalAdLibInstrument(byte slot, Instrument *dest) {
if (slot >= 32)
return;
diff --git a/engines/scumm/imuse/imuse_internal.h b/engines/scumm/imuse/imuse_internal.h
index 6a7b9fc7d9..76a8c76683 100644
--- a/engines/scumm/imuse/imuse_internal.h
+++ b/engines/scumm/imuse/imuse_internal.h
@@ -342,6 +342,7 @@ struct Part : public Serializable {
void off();
void set_instrument(uint b);
void set_instrument(byte *data);
+ void set_instrument_pcspk(byte *data);
void load_global_instrument(byte b);
void set_transpose(int8 transpose);
@@ -499,6 +500,7 @@ protected:
void reallocateMidiChannels(MidiDriver *midi);
void setGlobalAdLibInstrument(byte slot, byte *data);
+ void setGlobalPcSpkInstrument(byte slot, byte *data);
void copyGlobalAdLibInstrument(byte slot, Instrument *dest);
bool isNativeMT32() { return _native_mt32; }
diff --git a/engines/scumm/imuse/imuse_part.cpp b/engines/scumm/imuse/imuse_part.cpp
index 5df8407a96..82ad9b70d2 100644
--- a/engines/scumm/imuse/imuse_part.cpp
+++ b/engines/scumm/imuse/imuse_part.cpp
@@ -199,6 +199,12 @@ void Part::set_instrument(byte * data) {
_instrument.send(_mc);
}
+void Part::set_instrument_pcspk(byte *data) {
+ _instrument.pcspk(data);
+ if (clearToTransmit())
+ _instrument.send(_mc);
+}
+
void Part::load_global_instrument(byte slot) {
_player->_se->copyGlobalAdLibInstrument(slot, &_instrument);
if (clearToTransmit())
diff --git a/engines/scumm/imuse/instrument.cpp b/engines/scumm/imuse/instrument.cpp
index 955700fc2b..581f378655 100644
--- a/engines/scumm/imuse/instrument.cpp
+++ b/engines/scumm/imuse/instrument.cpp
@@ -259,6 +259,19 @@ public:
bool is_valid() { return (_native_mt32 ? true : (_instrument_name[0] != '\0')); }
};
+class Instrument_PcSpk : public InstrumentInternal {
+public:
+ Instrument_PcSpk(const byte *data);
+ Instrument_PcSpk(Serializer *s);
+ void saveOrLoad(Serializer *s);
+ void send(MidiChannel *mc);
+ void copy_to(Instrument *dest) { dest->pcspk((byte *)&_instrument); }
+ bool is_valid() { return true; }
+
+private:
+ byte _instrument[23];
+};
+
////////////////////////////////////////
//
// Instrument class members
@@ -299,6 +312,14 @@ void Instrument::roland(const byte *instrument) {
_instrument = new Instrument_Roland(instrument);
}
+void Instrument::pcspk(const byte *instrument) {
+ clear();
+ if (!instrument)
+ return;
+ _type = itPcSpk;
+ _instrument = new Instrument_PcSpk(instrument);
+}
+
void Instrument::saveOrLoad (Serializer *s) {
if (s->isSaving()) {
s->saveByte(_type);
@@ -319,6 +340,9 @@ void Instrument::saveOrLoad (Serializer *s) {
case itRoland:
_instrument = new Instrument_Roland(s);
break;
+ case itPcSpk:
+ _instrument = new Instrument_PcSpk(s);
+ break;
default:
warning("No known instrument classification #%d", (int)_type);
_type = itNone;
@@ -470,4 +494,32 @@ uint8 Instrument_Roland::getEquivalentGM() {
return 255;
}
+////////////////////////////////////////
+//
+// Instrument_PcSpk class members
+//
+////////////////////////////////////////
+
+Instrument_PcSpk::Instrument_PcSpk(const byte *data) {
+ memcpy(_instrument, data, sizeof(_instrument));
+}
+
+Instrument_PcSpk::Instrument_PcSpk(Serializer *s) {
+ if (!s->isSaving())
+ saveOrLoad(s);
+ else
+ memset(_instrument, 0, sizeof(_instrument));
+}
+
+void Instrument_PcSpk::saveOrLoad(Serializer *s) {
+ if (s->isSaving())
+ s->saveBytes(_instrument, sizeof(_instrument));
+ else
+ s->loadBytes(_instrument, sizeof(_instrument));
+}
+
+void Instrument_PcSpk::send(MidiChannel *mc) {
+ mc->sysEx_customInstrument('SPK ', (byte *)&_instrument);
+}
+
} // End of namespace Scumm
diff --git a/engines/scumm/imuse/instrument.h b/engines/scumm/imuse/instrument.h
index 79cbd49032..34f955518e 100644
--- a/engines/scumm/imuse/instrument.h
+++ b/engines/scumm/imuse/instrument.h
@@ -51,7 +51,8 @@ public:
itNone = 0,
itProgram = 1,
itAdLib = 2,
- itRoland = 3
+ itRoland = 3,
+ itPcSpk = 4
};
Instrument() : _type (0), _instrument (0) { }
@@ -70,6 +71,7 @@ public:
void program(byte program, bool mt32);
void adlib(const byte *instrument);
void roland(const byte *instrument);
+ void pcspk(const byte *instrument);
byte getType() { return _type; }
bool isValid() { return (_instrument ? _instrument->is_valid() : false); }
diff --git a/engines/scumm/imuse/pcspk.cpp b/engines/scumm/imuse/pcspk.cpp
new file mode 100644
index 0000000000..8c6e2a80c5
--- /dev/null
+++ b/engines/scumm/imuse/pcspk.cpp
@@ -0,0 +1,408 @@
+/* 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/pcspk.h"
+
+#include "common/debug.h"
+#include "common/textconsole.h"
+
+namespace Scumm {
+
+PcSpkDriver::PcSpkDriver(Audio::Mixer *mixer)
+ : MidiDriver_Emulated(mixer), _pcSpk(mixer->getOutputRate()) {
+}
+
+PcSpkDriver::~PcSpkDriver() {
+ close();
+}
+
+int PcSpkDriver::open() {
+ if (_isOpen)
+ return MERR_ALREADY_OPEN;
+
+ MidiDriver_Emulated::open();
+
+ for (uint i = 0; i < 6; ++i)
+ _channels[i].init(this, i);
+ _activeChannel = 0;
+
+ _mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+ return 0;
+}
+
+void PcSpkDriver::close() {
+ if (!_isOpen)
+ return;
+ _isOpen = false;
+
+ _mixer->stopHandle(_mixerSoundHandle);
+}
+
+void PcSpkDriver::send(uint32 d) {
+ assert((d & 0x0F) < 6);
+ debug("send");
+ _channels[(d & 0x0F)].send(d);
+}
+
+void PcSpkDriver::sysEx_customInstrument(byte channel, uint32 type, const byte *instr) {
+ assert(channel < 6);
+ if (type == 'SPK ')
+ _channels[channel].sysEx_customInstrument(type, instr);
+}
+
+MidiChannel *PcSpkDriver::allocateChannel() {
+ for (uint i = 0; i < 6; ++i) {
+ if (_channels[i].allocate())
+ return &_channels[i];
+ }
+
+ return 0;
+}
+
+void PcSpkDriver::generateSamples(int16 *buf, int len) {
+ _pcSpk.readBuffer(buf, len);
+}
+
+void PcSpkDriver::onTimer() {
+ if (!_activeChannel)
+ return;
+
+ for (uint i = 0; i < 6; ++i) {
+ OutputChannel &out = _channels[i]._out;
+
+ if (!out.active)
+ continue;
+
+ if (out.length == 0 || --out.length != 0) {
+ if (out.unkB && out.unkC) {
+ // TODO
+ }
+ } else {
+ out.active = 0;
+ updateNote();
+ return;
+ }
+ }
+
+ if (_activeChannel->_tl) {
+ //output((_activeChannel->_out.note << 7) + _activeChannel->_pitchBend + _activeChannel->_out.unk60 + _activeChannel->_out.unkE);
+ } else {
+ _pcSpk.stop();
+ }
+}
+
+void PcSpkDriver::updateNote() {
+ uint8 priority = 0;
+ _activeChannel = 0;
+ for (uint i = 0; i < 6; ++i) {
+ if (_channels[i]._allocated && _channels[i]._out.active && _channels[i]._priority >= priority) {
+ priority = _channels[i]._priority;
+ _activeChannel = &_channels[i];
+ }
+ }
+
+ if (_activeChannel == 0 || _activeChannel->_tl == 0) {
+ _pcSpk.stop();
+ } else {
+ output(_activeChannel->_pitchBend + (_activeChannel->_out.note << 7));
+ }
+}
+
+void PcSpkDriver::output(uint16 out) {
+ byte v1 = (out >> 7) & 0xFF;
+ byte v2 = (out >> 2) & 0x1E;
+
+ byte shift = _outputTable1[v1];
+ uint16 indexBase = _outputTable2[v1] << 5;
+ uint16 frequency = _frequencyTable[(indexBase + v2) / 2] >> shift;
+ _pcSpk.play(Audio::PCSpeaker::kWaveFormSquare, 1193180 / frequency, -1);
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::init(PcSpkDriver *owner, byte channel) {
+ _owner = owner;
+ _channel = channel;
+ _allocated = false;
+ memset(&_out, 0, sizeof(_out));
+}
+
+bool PcSpkDriver::MidiChannel_PcSpk::allocate() {
+ if (_allocated)
+ return false;
+
+ memset(&_out, 0, sizeof(_out));
+ memset(_instrument, 0, sizeof(_instrument));
+ _out.effectDefA.envelope = &_out.effectEnvelopeA;
+ _out.effectDefB.envelope = &_out.effectEnvelopeB;
+
+ _allocated = true;
+ return true;
+}
+
+MidiDriver *PcSpkDriver::MidiChannel_PcSpk::device() {
+ return _owner;
+}
+
+byte PcSpkDriver::MidiChannel_PcSpk::getNumber() {
+ return _channel;
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::release() {
+ _out.active = 0;
+ _allocated = false;
+ _owner->updateNote();
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::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 PcSpkDriver::MidiChannel_PcSpk::noteOff(byte note) {
+ if (!_allocated)
+ return;
+
+ if (_sustain) {
+ if (_out.note == note)
+ _out.sustainNoteOff = 1;
+ } else {
+ if (_out.note == note) {
+ _out.active = 0;
+ _owner->updateNote();
+ }
+ }
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::noteOn(byte note, byte velocity) {
+ if (!_allocated)
+ return;
+
+ _out.note = note;
+ _out.sustainNoteOff = 0;
+ _out.length = _instrument[0];
+ //_out.instrument = _owner->_outInstrumentData + note;
+ _out.unkA = 0;
+ _out.unkB = _instrument[1];
+ _out.unkC = _instrument[2];
+ _out.unkE = 0;
+ _out.unk60 = 0;
+ _out.active = 1;
+
+ _owner->updateNote();
+
+ if ((_instrument[5] & 0x80) != 0) {
+ warning("Effect envelope 1 used");
+ }
+
+ if ((_instrument[14] & 0x80) != 0) {
+ warning("Effect envelope 2 used");
+ }
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::programChange(byte program) {
+ // Nothing to implement here, the iMuse code takes care of passing us the
+ // instrument data.
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::pitchBend(int16 bend) {
+ _pitchBend = (bend * _pitchBendFactor) >> 6;
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::controlChange(byte control, byte value) {
+ switch (control) {
+ case 1:
+ if (_out.effectEnvelopeA.state && _out.effectDefA.enabled)
+ _out.effectEnvelopeA.modWheelState = (value >> 2);
+ if (_out.effectEnvelopeB.state && _out.effectDefB.enabled)
+ _out.effectEnvelopeB.modWheelState = (value >> 2);
+ break;
+
+ case 7:
+ _tl = value;
+ // TODO: Properly implement this
+ _owner->updateNote();
+ break;
+
+ case 64:
+ _sustain = value;
+ if (!value && _out.sustainNoteOff) {
+ _out.active = 0;
+ _owner->updateNote();
+ }
+ break;
+
+ case 123:
+ _out.active = 0;
+ _owner->updateNote();
+ break;
+
+ default:
+ break;
+ }
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::pitchBendFactor(byte value) {
+ _pitchBendFactor = value;
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::priority(byte value) {
+ _priority = value;
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::sysEx_customInstrument(uint32 type, const byte *instr) {
+ memcpy(_instrument, instr, sizeof(_instrument));
+}
+
+/*const byte PcSpkDriver::_outInstrumentData[] = {
+ 0
+};*/
+
+const byte PcSpkDriver::_outputTable1[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3,
+ 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5,
+ 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7
+};
+
+const byte PcSpkDriver::_outputTable2[] = {
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7
+};
+
+const uint16 PcSpkDriver::_frequencyTable[] = {
+ 0x8E84, 0x8E00, 0x8D7D, 0x8CFA,
+ 0x8C78, 0x8BF7, 0x8B76, 0x8AF5,
+ 0x8A75, 0x89F5, 0x8976, 0x88F7,
+ 0x8879, 0x87FB, 0x877D, 0x8700,
+ 0x8684, 0x8608, 0x858C, 0x8511,
+ 0x8496, 0x841C, 0x83A2, 0x8328,
+ 0x82AF, 0x8237, 0x81BF, 0x8147,
+ 0x80D0, 0x8059, 0x7FE3, 0x7F6D,
+ 0x7EF7, 0x7E82, 0x7E0D, 0x7D99,
+ 0x7D25, 0x7CB2, 0x7C3F, 0x7BCC,
+ 0x7B5A, 0x7AE8, 0x7A77, 0x7A06,
+ 0x7995, 0x7925, 0x78B5, 0x7846,
+ 0x77D7, 0x7768, 0x76FA, 0x768C,
+ 0x761F, 0x75B2, 0x7545, 0x74D9,
+ 0x746D, 0x7402, 0x7397, 0x732C,
+ 0x72C2, 0x7258, 0x71EF, 0x7186,
+ 0x711D, 0x70B5, 0x704D, 0x6FE5,
+ 0x6F7E, 0x6F17, 0x6EB0, 0x6E4A,
+ 0x6DE5, 0x6D7F, 0x6D1A, 0x6CB5,
+ 0x6C51, 0x6BED, 0x6B8A, 0x6B26,
+ 0x6AC4, 0x6A61, 0x69FF, 0x699D,
+ 0x693C, 0x68DB, 0x687A, 0x681A,
+ 0x67BA, 0x675A, 0x66FA, 0x669B,
+ 0x663D, 0x65DF, 0x6581, 0x6523,
+ 0x64C6, 0x6469, 0x640C, 0x63B0,
+ 0x6354, 0x62F8, 0x629D, 0x6242,
+ 0x61E7, 0x618D, 0x6133, 0x60D9,
+ 0x6080, 0x6027, 0x5FCE, 0x5F76,
+ 0x5F1E, 0x5EC6, 0x5E6E, 0x5E17,
+ 0x5DC1, 0x5D6A, 0x5D14, 0x5CBE,
+ 0x5C68, 0x5C13, 0x5BBE, 0x5B6A,
+ 0x5B15, 0x5AC1, 0x5A6E, 0x5A1A,
+ 0x59C7, 0x5974, 0x5922, 0x58CF,
+ 0x587D, 0x582C, 0x57DA, 0x5789,
+ 0x5739, 0x56E8, 0x5698, 0x5648,
+ 0x55F9, 0x55A9, 0x555A, 0x550B,
+ 0x54BD, 0x546F, 0x5421, 0x53D3,
+ 0x5386, 0x5339, 0x52EC, 0x52A0,
+ 0x5253, 0x5207, 0x51BC, 0x5170,
+ 0x5125, 0x50DA, 0x5090, 0x5046,
+ 0x4FFB, 0x4FB2, 0x4F68, 0x4F1F,
+ 0x4ED6, 0x4E8D, 0x4E45, 0x4DFC,
+ 0x4DB5, 0x4D6D, 0x4D25, 0x4CDE,
+ 0x4C97, 0x4C51, 0x4C0A, 0x4BC4,
+ 0x4B7E, 0x4B39, 0x4AF3, 0x4AAE,
+ 0x4A69, 0x4A24, 0x49E0, 0x499C,
+ 0x4958, 0x4914, 0x48D1, 0x488E,
+ 0x484B, 0x4808, 0x47C6, 0x4783
+};
+
+} // End of namespace Scumm
+
diff --git a/engines/scumm/imuse/pcspk.h b/engines/scumm/imuse/pcspk.h
new file mode 100644
index 0000000000..8edf86e927
--- /dev/null
+++ b/engines/scumm/imuse/pcspk.h
@@ -0,0 +1,147 @@
+/* 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_PCSPK_H
+#define SCUMM_IMUSE_PCSPK_H
+
+#include "audio/softsynth/emumidi.h"
+#include "audio/softsynth/pcspk.h"
+
+namespace Scumm {
+
+class PcSpkDriver : public MidiDriver_Emulated {
+public:
+ PcSpkDriver(Audio::Mixer *mixer);
+ ~PcSpkDriver();
+
+ 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; }
+
+ bool isStereo() const { return _pcSpk.isStereo(); }
+ int getRate() const { return _pcSpk.getRate(); }
+protected:
+ void generateSamples(int16 *buf, int len);
+ void onTimer();
+
+private:
+ Audio::PCSpeaker _pcSpk;
+
+ void updateNote();
+ void output(uint16 out);
+
+ struct EffectEnvelope {
+ uint8 state;
+ uint16 currentLevel;
+ uint16 duration;
+ uint16 maxLevel;
+ uint16 startLevel;
+ uint8 loop;
+ uint8 stateTargetLevels[4];
+ uint8 stateModWheelLevels[4];
+ uint8 modWheelSensitivity;
+ uint8 modWheelState;
+ uint8 modWheelLast;
+ uint16 stateNumSteps;
+ uint16 stateStepCounter;
+ uint16 changePerStep;
+ uint16 dir;
+ uint16 changePerStepRem;
+ uint16 changeCountRem;
+ };
+
+ struct EffectDefinition {
+ uint16 phase;
+ uint8 type;
+ uint8 enabled;
+ EffectEnvelope *envelope;
+ };
+
+ struct MidiChannel_PcSpk;
+
+ struct OutputChannel {
+ uint8 active;
+ uint8 note;
+ uint8 sustainNoteOff;
+ uint8 length;
+ //const uint8 *instrument;
+ uint8 unkA;
+ uint8 unkB;
+ uint8 unkC;
+ uint16 unkE;
+ EffectEnvelope effectEnvelopeA;
+ EffectDefinition effectDefA;
+ EffectEnvelope effectEnvelopeB;
+ EffectDefinition effectDefB;
+ uint16 unk60;
+ };
+
+ struct MidiChannel_PcSpk : public MidiChannel {
+ virtual MidiDriver *device();
+ virtual byte getNumber();
+ 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(PcSpkDriver *owner, byte channel);
+ bool allocate();
+
+ PcSpkDriver *_owner;
+ bool _allocated;
+ byte _channel;
+
+ OutputChannel _out;
+ uint8 _instrument[23];
+ uint8 _programNr;
+ uint8 _priority;
+ uint8 _tl;
+ uint8 _modWheel;
+ uint8 _sustain;
+ uint8 _pitchBendFactor;
+ int16 _pitchBend;
+ };
+
+ MidiChannel_PcSpk _channels[6];
+ MidiChannel_PcSpk *_activeChannel;
+
+ //static const byte _outInstrumentData[];
+ static const byte _outputTable1[];
+ static const byte _outputTable2[];
+ static const uint16 _frequencyTable[];
+};
+
+} // End of namespace Scumm
+
+#endif
+
diff --git a/engines/scumm/imuse/sysex_scumm.cpp b/engines/scumm/imuse/sysex_scumm.cpp
index c3bec93a60..160cdf500a 100644
--- a/engines/scumm/imuse/sysex_scumm.cpp
+++ b/engines/scumm/imuse/sysex_scumm.cpp
@@ -91,8 +91,16 @@ void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) {
// 0 is a valid program number. MI2 tests show that in such
// cases, a regular program change message always seems to follow
// anyway.
- if (player->_isMIDI)
+ if (player->_isMIDI) {
part->_instrument.program((p[15] & 0x0F) << 4 |(p[16] & 0x0F), player->_isMT32);
+ } else {
+ // FIXME/HACK: This is only needed here, since when we use the following line:
+ // se->copyGlobalAdLibInstrument((p[15] & 0x0F) << 4 |(p[16] & 0x0F), &part->_instrument);
+ // We would not get any instrument for PC Speaker. Because we don't default to an
+ // "empty" instrument in case the global instrument specified is not set up.
+ byte empty[23] = {0};
+ part->_instrument.pcspk(empty);
+ }
part->sendAll();
}
}
@@ -116,8 +124,10 @@ void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) {
if (len == 62) {
player->decode_sysex_bytes(p, buf, len - 2);
part->set_instrument((byte *)buf);
- } else {
- // SPK tracks have len == 48 here, and are not supported
+ } else if (len == 48) {
+ player->decode_sysex_bytes(p, buf, len - 2);
+ part->set_instrument_pcspk((byte *)buf);
+ } else {
part->programChange(254); // Must be invalid, but not 255 (which is reserved)
}
}
@@ -127,7 +137,10 @@ void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) {
p += 2; // Skip hardware type and... whatever came right before it
a = *p++;
player->decode_sysex_bytes(p, buf, len - 3);
- se->setGlobalAdLibInstrument(a, buf);
+ if (len == 63)
+ se->setGlobalAdLibInstrument(a, buf);
+ else if (len == 49)
+ se->setGlobalPcSpkInstrument(a, buf);
break;
case 33: // Parameter adjust
diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index 1a60564a9e..99ffdf7f21 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/pcspk.o \
imuse/sysex_samnmax.o \
imuse/sysex_scumm.o \
input.o \
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 7b136dc36d..ed1c90145d 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -71,6 +71,7 @@
#include "scumm/he/cup_player_he.h"
#include "scumm/util.h"
#include "scumm/verbs.h"
+#include "scumm/imuse/pcspk.h"
#include "backends/audiocd/audiocd.h"
@@ -1857,7 +1858,7 @@ void ScummEngine::setupMusic(int midi) {
MidiDriver *nativeMidiDriver = 0;
MidiDriver *adlibMidiDriver = 0;
- if (_musicType != MDT_ADLIB && _musicType != MDT_TOWNS)
+ if (_musicType != MDT_ADLIB && _musicType != MDT_TOWNS && _musicType != MDT_PCSPK)
nativeMidiDriver = MidiDriver::createMidi(dev);
if (nativeMidiDriver != NULL && _native_mt32)
nativeMidiDriver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
@@ -1865,6 +1866,9 @@ void ScummEngine::setupMusic(int midi) {
if (_musicType == MDT_ADLIB || _musicType == MDT_TOWNS || multi_midi) {
adlibMidiDriver = MidiDriver::createMidi(MidiDriver::detectDevice(_musicType == MDT_TOWNS ? MDT_TOWNS : MDT_ADLIB));
adlibMidiDriver->property(MidiDriver::PROP_OLD_ADLIB, (_game.features & GF_SMALL_HEADER) ? 1 : 0);
+ } else if (_musicType == MDT_PCSPK) {
+ // HACK
+ adlibMidiDriver = new PcSpkDriver(_mixer);
}
_imuse = IMuse::create(_system, nativeMidiDriver, adlibMidiDriver);
diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp
index 27e43b3740..544abe6b1d 100644
--- a/engines/scumm/sound.cpp
+++ b/engines/scumm/sound.cpp
@@ -1137,8 +1137,8 @@ int ScummEngine::readSoundResource(ResId idx) {
break;
case MKTAG('S','P','K',' '):
pri = -1;
-// if (_musicType == MDT_PCSPK || _musicType == MDT_PCJR)
-// pri = 11;
+ if (_musicType == MDT_PCSPK || _musicType == MDT_PCJR)
+ pri = 11;
break;
}