[Scummvm-git-logs] scummvm master -> 6694cdcd7389f7e5452733c8bfcfda9585223d3d
athrxx
athrxx at scummvm.org
Sun Jul 14 22:11:01 CEST 2019
This automated email contains information about 7 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
9afdde2601 SCUMM: add Amiga iMuse sound driver
0899ecc987 SCUMM: hook up Amiga MI2 + INDY4 to new sound driver
d1b64aab0c SCUMM: (iMuse/Amiga) - improve accuracy
01f99f1a0a SCUMM: imuse driver directory cleanup
4ee4d2d9af SCUMM: (FM-Towns Audio) remove TODO
19643175a8 SCUMM: limit 'Unrecognized base tag' warning to valid cases
6694cdcd73 SCUMM: update news (Amiga iMuse support)
Commit: 9afdde2601b64f724c2e5c4d4b43ba04709a27f1
https://github.com/scummvm/scummvm/commit/9afdde2601b64f724c2e5c4d4b43ba04709a27f1
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-14T20:56:27+02:00
Commit Message:
SCUMM: add Amiga iMuse sound driver
(applies to MI2 and INDY4)
Changed paths:
A engines/scumm/imuse/drivers/amiga.cpp
A engines/scumm/imuse/drivers/amiga.h
audio/mididrv.h
engines/scumm/POTFILES
engines/scumm/imuse/imuse.cpp
engines/scumm/module.mk
diff --git a/audio/mididrv.h b/audio/mididrv.h
index 1eaf415..7fffb99 100644
--- a/audio/mididrv.h
+++ b/audio/mididrv.h
@@ -274,6 +274,7 @@ public:
virtual void volume(byte value) { controlChange(7, value); }
virtual void panPosition(byte value) { controlChange(10, value); }
virtual void pitchBendFactor(byte value) = 0;
+ virtual void transpose(int8 value) {}
virtual void detune(byte value) { controlChange(17, value); }
virtual void priority(byte value) { }
virtual void sustain(bool value) { controlChange(64, value ? 1 : 0); }
diff --git a/engines/scumm/POTFILES b/engines/scumm/POTFILES
index 039aa16..bdd18b6 100644
--- a/engines/scumm/POTFILES
+++ b/engines/scumm/POTFILES
@@ -5,4 +5,4 @@ engines/scumm/input.cpp
engines/scumm/scumm.cpp
engines/scumm/players/player_v3m.cpp
engines/scumm/players/player_v5m.cpp
-
+engines/scumm/imuse/drivers/amiga.cpp
diff --git a/engines/scumm/imuse/drivers/amiga.cpp b/engines/scumm/imuse/drivers/amiga.cpp
new file mode 100644
index 0000000..4942fd5
--- /dev/null
+++ b/engines/scumm/imuse/drivers/amiga.cpp
@@ -0,0 +1,875 @@
+/* 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 "engines/scumm/imuse/drivers/amiga.h"
+#include "audio/mixer.h"
+#include "common/file.h"
+#include "common/translation.h"
+#include "gui/error.h"
+
+namespace Scumm {
+
+struct Instrument_Amiga {
+ struct Samples {
+ uint16 rate;
+ uint16 baseNote;
+ int16 noteRangeMin;
+ int16 noteRangeMax;
+ int16 sustainLevel;
+ uint16 type;
+ uint32 numSamples;
+ uint32 dr_offset;
+ uint32 dr_numSamples;
+ int16 levelFadeDelayAT;
+ int16 levelFadeDelayRL;
+ int16 levelFadeTriggerRL;
+ int16 levelFadeDelayDC;
+ const int8 *data;
+ };
+ Samples samples[8];
+ int numBlocks;
+};
+
+class SoundChannel_Amiga {
+public:
+ SoundChannel_Amiga(IMuseDriver_Amiga *driver, int id, Instrument_Amiga *instruments);
+ ~SoundChannel_Amiga();
+
+ static SoundChannel_Amiga *allocate(int prio);
+ void connect(IMusePart_Amiga *part);
+ void disconnect();
+
+ void noteOn(byte note, byte velocity, byte program, int8 transpose, int16 pitchBend);
+ void ctrl_volume(uint8 volume);
+ void ctrl_sustain(bool sustainToggle);
+ void transposePitchBend(int8 transpose, int16 pitchBend);
+
+ void updateLevel();
+ void updateEnvelope();
+
+ uint8 note() const { return _note; }
+ SoundChannel_Amiga *next() const { return _next; }
+
+private:
+ void keyOn(const int8 *data1, uint16 data1Size, const int8 *data2, uint16 data2Size, uint16 period);
+ void keyOff();
+ void setRepeatData(const int8 *data, uint16 size);
+ void setVelocity(uint8 velo, int delay);
+ void setVolume(uint8 volume);
+
+ uint16 calculatePeriod(int16 tone, uint8 baseNote, uint16 rate);
+
+ void createVolumeTable();
+
+ SoundChannel_Amiga *_prev, *_next;
+ IMusePart_Amiga *_assign;
+
+ uint8 _id;
+ uint8 _note;
+ int8 _transpose;
+ bool _sustain;
+
+ IMuseDriver_Amiga *_driver;
+
+ static uint8 _allocCurPos;
+ static SoundChannel_Amiga *_channels[4];
+
+ enum EnvelopeState {
+ kReady = 0,
+ kRelease = 1,
+ kDecay = 2,
+ kAttack = 3,
+ kRestart = 4
+ };
+
+ struct IOUnit {
+ IOUnit() : program(0), block(0), volume(63), currentLevel(0), fadeTargetLevel(0), fadeLevelDelta(0), fadeLevelMod(0), levelFadeTriggerDC(0), fadeLevelTicks(0),
+ fadeLevelTicker(0), fadeLevelDuration(0), releaseData(0), releaseDataSize(0), repeatData(0), repeatDataSize(0), envelopeState(kReady) {}
+ uint8 program;
+ uint8 block;
+ uint8 volume;
+ uint8 currentLevel;
+ uint8 fadeTargetLevel;
+ uint8 fadeLevelDelta;
+ uint16 fadeLevelTicks;
+ int8 fadeLevelMod;
+ bool levelFadeTriggerDC;
+ uint32 fadeLevelTicker;
+ uint32 fadeLevelDuration;
+ const int8 *releaseData;
+ uint16 releaseDataSize;
+ const int8 *repeatData;
+ uint16 repeatDataSize;
+ uint8 envelopeState;
+ };
+
+ IOUnit _ioUnit;
+
+ const Instrument_Amiga *_instruments;
+
+ static const int8 _muteData[16];
+ static const uint8 *_volTable;
+};
+
+class IMusePart_Amiga : public MidiChannel {
+public:
+ IMusePart_Amiga(IMuseDriver_Amiga *driver, int id);
+ ~IMusePart_Amiga() {}
+
+ MidiDriver *device() { return _driver; }
+ byte getNumber() { return _id; }
+ bool allocate();
+ void release();
+
+ void send(uint32 b);
+
+ void noteOff(byte note);
+ void noteOn(byte note, byte velocity);
+ void controlChange(byte control, byte value);
+ void programChange(byte program);
+ void pitchBend(int16 bend);
+ void pitchBendFactor(byte value);
+ void transpose(int8 value);
+
+ void priority(byte value);
+ void sysEx_customInstrument(uint32 type, const byte *instr) {}
+
+ int getPriority() const { return _priority; }
+ SoundChannel_Amiga *getChannel() const { return _out; }
+ void setChannel(SoundChannel_Amiga *chan) { _out = chan; }
+
+private:
+ void controlModulationWheel(byte value);
+ void controlVolume(byte value);
+ void controlSustain(byte value);
+
+ uint8 _priority;
+ uint8 _program;
+ int8 _modulation;
+ int8 _transpose;
+ int16 _pitchBend;
+ uint8 _pitchBendSensitivity;
+ uint16 _volume;
+ bool _sustain;
+ bool _allocated;
+ const uint8 _id;
+ SoundChannel_Amiga *_out;
+ IMuseDriver_Amiga *_driver;
+};
+
+SoundChannel_Amiga::SoundChannel_Amiga(IMuseDriver_Amiga *driver, int id, Instrument_Amiga *instruments) : _driver(driver), _id(id), _instruments(instruments),
+ _assign(0), _next(0), _prev(0), _sustain(false), _note(0) {
+ assert(id > -1 && id < 4);
+ _channels[id] = this;
+ createVolumeTable();
+}
+
+SoundChannel_Amiga::~SoundChannel_Amiga() {
+ _channels[_id] = 0;
+
+ // delete volume table only if this is the last remaining SoundChannel_Amiga object
+ for (int i = 0; i < 4; ++i) {
+ if (_channels[i])
+ return;
+ }
+
+ delete[] _volTable;
+ _volTable = 0;
+}
+
+SoundChannel_Amiga *SoundChannel_Amiga::allocate(int prio) {
+ SoundChannel_Amiga *res = 0;
+
+ for (int i = 0; i < 4; i++) {
+ if (++_allocCurPos == 4)
+ _allocCurPos = 0;
+
+ SoundChannel_Amiga *temp = _channels[_allocCurPos];
+ if (!temp->_assign)
+ return temp;
+
+ if (temp->_next)
+ continue;
+
+ if (prio >= temp->_assign->getPriority()) {
+ res = temp;
+ prio = temp->_assign->getPriority();
+ }
+ }
+
+ if (res)
+ res->disconnect();
+
+ return res;
+}
+
+void SoundChannel_Amiga::connect(IMusePart_Amiga *part) {
+ if (!part)
+ return;
+
+ _assign = part;
+ _next = part->getChannel();
+ _prev = 0;
+ part->setChannel(this);
+ if (_next)
+ _next->_prev = this;
+}
+
+void SoundChannel_Amiga::disconnect() {
+ keyOff();
+
+ SoundChannel_Amiga *p = _prev;
+ SoundChannel_Amiga *n = _next;
+
+ if (n)
+ n->_prev = p;
+ if (p)
+ p->_next = n;
+ else
+ _assign->setChannel(n);
+ _assign = 0;
+}
+
+void SoundChannel_Amiga::noteOn(byte note, byte volume, byte program, int8 transpose, int16 pitchBend) {
+ if (program > 128)
+ program = 128;
+
+ if (program != 128 && !_instruments[program].samples[0].data)
+ program = 128;
+
+ _note = note;
+ _sustain = false;
+
+ _ioUnit.block = 0;
+ _ioUnit.program = program;
+ const Instrument_Amiga::Samples *s = &_instruments[program].samples[_ioUnit.block];
+ int16 pnote = note + transpose + (pitchBend >> 7);
+
+ if (_instruments[program].numBlocks > 1) {
+ for (int i = 0; i < _instruments[program].numBlocks; ++i) {
+ if (pnote >= _instruments[program].samples[i].noteRangeMin && pnote <= _instruments[program].samples[i].noteRangeMax) {
+ _ioUnit.block = i;
+ s = &_instruments[program].samples[_ioUnit.block];
+ break;
+ }
+ }
+ }
+
+ _driver->disableChannel(_id);
+ setVelocity(0, 0);
+ setVolume(volume);
+
+ if (s->type > 1)
+ return;
+
+ uint16 period = calculatePeriod(pitchBend + ((_note + transpose) << 7), s->baseNote, s->rate);
+
+ if (s->type == 1) {
+ keyOn(s->data, s->numSamples, 0, 0, period);
+ setRepeatData(0, 0);
+ } else {
+ if (s->dr_numSamples) {
+ keyOn(s->data, s->dr_numSamples, s->data + s->dr_offset, s->dr_numSamples - s->dr_offset, period);
+ setRepeatData(s->data + s->dr_numSamples, s->numSamples - s->dr_numSamples);
+ } else {
+ keyOn(s->data, s->numSamples, s->data + s->dr_offset, s->numSamples - s->dr_offset, period);
+ setRepeatData(0, 0);
+ }
+ }
+}
+
+void SoundChannel_Amiga::ctrl_volume(uint8 volume) {
+ setVolume(volume);
+}
+
+void SoundChannel_Amiga::ctrl_sustain(bool sustainToggle) {
+ if (_sustain && !sustainToggle)
+ disconnect();
+ else if (sustainToggle)
+ _sustain = true;
+}
+
+void SoundChannel_Amiga::transposePitchBend(int8 transpose, int16 pitchBend) {
+ //_transpose = transpose;
+ const Instrument_Amiga::Samples *s = &_instruments[_ioUnit.program].samples[_ioUnit.block];
+ _driver->setChannelPeriod(_id, calculatePeriod(((_note + transpose) << 7) + pitchBend, s->baseNote, s->rate));
+}
+
+void SoundChannel_Amiga::updateLevel() {
+ if (!_ioUnit.fadeLevelMod)
+ return;
+
+ _ioUnit.fadeLevelDuration += _ioUnit.fadeLevelDelta;
+ if (_ioUnit.fadeLevelDuration <= _ioUnit.fadeLevelTicker)
+ return;
+
+ while (_ioUnit.fadeLevelDuration > _ioUnit.fadeLevelTicker && _ioUnit.currentLevel != _ioUnit.fadeTargetLevel) {
+ _ioUnit.fadeLevelTicker += _ioUnit.fadeLevelTicks;
+ _ioUnit.currentLevel += _ioUnit.fadeLevelMod;
+ }
+
+ _driver->setChannelVolume(_id, _volTable[(_ioUnit.volume << 5) + _ioUnit.currentLevel]);
+
+ if (_ioUnit.currentLevel != _ioUnit.fadeTargetLevel)
+ return;
+
+ _ioUnit.fadeLevelMod = 0;
+ if (!_ioUnit.levelFadeTriggerDC)
+ return;
+
+ const Instrument_Amiga::Samples *s = &_instruments[_ioUnit.program].samples[_ioUnit.block];
+ setVelocity(s->sustainLevel >> 1, s->levelFadeDelayDC);
+}
+
+void SoundChannel_Amiga::updateEnvelope() {
+ if (_ioUnit.envelopeState == kReady)
+ return;
+
+ uint8 envCur = _ioUnit.envelopeState--;
+ if (envCur == kAttack) {
+ const Instrument_Amiga::Samples *s = &_instruments[_ioUnit.program].samples[_ioUnit.block];
+ _driver->enableChannel(_id);
+ if (s->levelFadeDelayDC) {
+ setVelocity(31, s->levelFadeDelayAT);
+ if (s->levelFadeDelayAT)
+ _ioUnit.levelFadeTriggerDC = true;
+ else
+ setVelocity(s->sustainLevel >> 1, s->levelFadeDelayDC);
+ } else {
+ setVelocity(s->sustainLevel >> 1, s->levelFadeDelayAT);
+ }
+ }
+
+ if (envCur == kRelease) {
+ _driver->setChannelSampleStart(_id, _ioUnit.releaseData);
+ _driver->setChannelSampleLen(_id, _ioUnit.releaseDataSize);
+ }
+}
+
+void SoundChannel_Amiga::keyOn(const int8 *attackData, uint16 attackDataSize, const int8 *releaseData, uint16 releaseDataSize, uint16 period) {
+ _driver->setChannelSampleStart(_id, attackData);
+ _driver->setChannelSampleLen(_id, attackDataSize >> 1);
+ _driver->setChannelPeriod(_id, period);
+
+ if (releaseData) {
+ _ioUnit.releaseData = releaseData;
+ _ioUnit.releaseDataSize = releaseDataSize >> 1;
+ } else {
+ _ioUnit.releaseData = _muteData;
+ _ioUnit.releaseDataSize = ARRAYSIZE(_muteData) >> 1;
+ }
+
+ _ioUnit.envelopeState = kRestart;
+}
+
+void SoundChannel_Amiga::keyOff() {
+ _ioUnit.levelFadeTriggerDC = 0;
+ if (_ioUnit.repeatData) {
+ _driver->setChannelSampleStart(_id, _ioUnit.repeatData);
+ _driver->setChannelSampleLen(_id, _ioUnit.repeatDataSize);
+ _ioUnit.releaseData = _muteData;
+ _ioUnit.releaseDataSize = ARRAYSIZE(_muteData) >> 1;
+ _ioUnit.envelopeState = kDecay;
+ } else {
+ _ioUnit.envelopeState = kReady;
+ }
+
+ if (_instruments[_ioUnit.program].samples[_ioUnit.block].levelFadeTriggerRL)
+ setVelocity(0, _instruments[_ioUnit.program].samples[_ioUnit.block].levelFadeDelayRL);
+}
+
+void SoundChannel_Amiga::setRepeatData(const int8 *data, uint16 size) {
+ _ioUnit.repeatData = data;
+ _ioUnit.repeatDataSize = size >> 1;
+}
+
+void SoundChannel_Amiga::setVelocity(uint8 velo, int delay) {
+ _ioUnit.levelFadeTriggerDC = 0;
+
+ if (delay) {
+ _ioUnit.fadeTargetLevel = velo;
+ _ioUnit.fadeLevelDelta = ABS(_ioUnit.currentLevel - velo);
+ _ioUnit.fadeLevelTicks = (delay << 10) / 5500;
+ _ioUnit.fadeLevelMod = (_ioUnit.currentLevel >= velo) ? -1 : 1;
+ _ioUnit.fadeLevelTicker = _ioUnit.fadeLevelDuration = 0;
+ } else {
+ _driver->setChannelVolume(_id, _volTable[(_ioUnit.volume << 5) + velo]);
+ _ioUnit.currentLevel = _ioUnit.fadeTargetLevel = velo;
+ _ioUnit.fadeLevelMod = 0;
+ }
+}
+
+void SoundChannel_Amiga::setVolume(uint8 volume) {
+ volume >>= 1;
+ _ioUnit.volume = volume;
+ _driver->setChannelVolume(_id, _volTable[(volume << 5) + _ioUnit.currentLevel]);
+}
+
+uint16 SoundChannel_Amiga::calculatePeriod(int16 tone, uint8 baseNote, uint16 rate) {
+ static const uint32 octavePeriods[13] = { 0x4000, 0x43CE, 0x47D7, 0x4C1B, 0x50A2, 0x556D, 0x5A82, 0x5FE4, 0x6598, 0x6BA2, 0x7209, 0x78D0, 0x8000 };
+
+ int16 frq_coarse = tone >> 7;
+ uint8 frq_fine = tone & 0x7F;
+ int16 octTrans = baseNote;
+ rate <<= 3;
+
+ for (int16 octTransHi = baseNote + 12; octTransHi <= frq_coarse; octTransHi += 12) {
+ rate >>= 1;
+ octTrans = octTransHi;
+ }
+
+ while (octTrans > frq_coarse) {
+ rate += rate;
+ octTrans -= 12;
+ }
+
+ uint32 res = (((octavePeriods[11 - (frq_coarse - octTrans)] * rate) >> 18) * frq_fine + ((octavePeriods[12 - (frq_coarse - octTrans)] * rate) >> 18) * (0x80 - frq_fine)) >> 7;
+
+ if (!res)
+ return 124;
+
+ while (res < 124)
+ res += res;
+
+ if (res > 65535)
+ res = 65535;
+
+ return res & 0xFFFF;
+}
+
+void SoundChannel_Amiga::createVolumeTable() {
+ if (_volTable)
+ return;
+
+ uint8 *volTbl = new uint8[2048];
+ for (int a = 0; a < 64; ++a) {
+ volTbl[a << 5] = 0;
+ for (int b = 1; b < 32; ++b)
+ volTbl[(a << 5) + b] = (a * (b + 1)) >> 5;
+ }
+ _volTable = volTbl;
+}
+
+uint8 SoundChannel_Amiga::_allocCurPos = 0;
+
+const uint8 *SoundChannel_Amiga::_volTable = 0;
+
+SoundChannel_Amiga *SoundChannel_Amiga::_channels[4] = { 0, 0, 0, 0 };
+
+const int8 SoundChannel_Amiga::_muteData[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+
+IMusePart_Amiga::IMusePart_Amiga(IMuseDriver_Amiga *driver, int id) : _driver(driver), _id(id), _allocated(false), _out(0), _priority(0), _program(0),
+ _pitchBend(0), _pitchBendSensitivity(2), _volume(0), _modulation(0), _transpose(0), _sustain(false) {
+}
+
+bool IMusePart_Amiga::allocate() {
+ if (_allocated)
+ return false;
+
+ _allocated = true;
+
+ while (_out)
+ _out->disconnect();
+
+ return true;
+}
+
+void IMusePart_Amiga::release() {
+ _allocated = false;
+
+ while (_out)
+ _out->disconnect();
+}
+
+void IMusePart_Amiga::send(uint32 b) {
+ _driver->send(b | _id);
+}
+
+void IMusePart_Amiga::noteOff(byte note) {
+ for (SoundChannel_Amiga *cur = _out; cur; cur = cur->next()) {
+ if (note == cur->note()) {
+ if (_sustain)
+ cur->ctrl_sustain(true);
+ else
+ cur->disconnect();
+ }
+ }
+}
+
+void IMusePart_Amiga::noteOn(byte note, byte velocity) {
+ if (!velocity) {
+ noteOff(note);
+ return;
+ }
+
+ SoundChannel_Amiga *chan = SoundChannel_Amiga::allocate(_priority);
+ if (!chan)
+ return;
+
+ chan->connect(this);
+ // The velocity parameter is ignored here.
+ chan->noteOn(note, _volume, _program, _transpose, (_pitchBend * _pitchBendSensitivity) >> 6);
+}
+
+void IMusePart_Amiga::controlChange(byte control, byte value) {
+ switch (control) {
+ case 1:
+ controlModulationWheel(value);
+ break;
+ case 7:
+ controlVolume(value);
+ break;
+ case 10:
+ // The original driver has no support for this.
+ break;
+ case 64:
+ controlSustain(value);
+ break;
+ case 123:
+ while (_out)
+ _out->disconnect();
+ break;
+ default:
+ break;
+ }
+}
+
+void IMusePart_Amiga::programChange(byte program) {
+ _program = program;
+}
+
+void IMusePart_Amiga::pitchBend(int16 bend) {
+ _pitchBend = bend;
+ for (SoundChannel_Amiga *cur = _out; cur; cur = cur->next())
+ cur->transposePitchBend(_transpose, (_pitchBend * _pitchBendSensitivity) >> 6);
+}
+
+void IMusePart_Amiga::pitchBendFactor(byte value) {
+ _pitchBendSensitivity = value;
+ for (SoundChannel_Amiga *cur = _out; cur; cur = cur->next())
+ cur->transposePitchBend(_transpose, (_pitchBend * _pitchBendSensitivity) >> 6);
+}
+
+void IMusePart_Amiga::transpose(int8 value) {
+ _transpose = value << 1;
+ for (SoundChannel_Amiga *cur = _out; cur; cur = cur->next())
+ cur->transposePitchBend(_transpose, (_pitchBend * _pitchBendSensitivity) >> 6);
+}
+
+void IMusePart_Amiga::priority(byte value) {
+ _priority = value;
+}
+
+void IMusePart_Amiga::controlModulationWheel(byte value) {
+ _modulation = (int8)value;
+}
+
+void IMusePart_Amiga::controlVolume(byte value) {
+ _volume = value;
+ for (SoundChannel_Amiga *cur = _out; cur; cur = cur->next())
+ cur->ctrl_volume(_volume);
+}
+
+void IMusePart_Amiga::controlSustain(byte value) {
+ _sustain = value;
+ if (!value) {
+ for (SoundChannel_Amiga *cur = _out; cur; cur = cur->next())
+ cur->ctrl_sustain(false);
+ }
+}
+
+IMuseDriver_Amiga::IMuseDriver_Amiga(Audio::Mixer *mixer) : Paula(true, mixer->getOutputRate(), (mixer->getOutputRate() * 1000) / 181818), _mixer(mixer), _isOpen(false), _soundHandle(),
+ _numParts(24), _baseTempo(5500), _internalTempo(5500), _timerProc(0), _timerProcPara(0), _parts(0), _chan(0), _instruments(0), _missingFiles(0), _ticker(0) {
+ setAudioFilter(true);
+
+ _instruments = new Instrument_Amiga[129];
+ memset(_instruments, 0, sizeof(Instrument_Amiga) * 129);
+ loadInstrument(128);
+
+ _parts = new IMusePart_Amiga*[_numParts];
+ for (int i = 0; i < _numParts; i++)
+ _parts[i] = new IMusePart_Amiga(this, i);
+
+ _chan = new SoundChannel_Amiga*[4];
+ for (int i = 0; i < 4; i++)
+ _chan[i] = new SoundChannel_Amiga(this, i, _instruments);
+}
+
+IMuseDriver_Amiga::~IMuseDriver_Amiga() {
+ close();
+
+ Common::StackLock lock(_mutex);
+
+ if (_chan) {
+ for (int i = 0; i < 4; i++)
+ delete _chan[i];
+ delete[] _chan;
+ }
+ _chan = 0;
+
+ if (_parts) {
+ for (int i = 0; i < _numParts; i++)
+ delete _parts[i];
+ delete[] _parts;
+ }
+ _parts = 0;
+
+ delete[] _instruments;
+}
+
+int IMuseDriver_Amiga::open() {
+ if (_isOpen)
+ return MERR_ALREADY_OPEN;
+
+ // Load all instruments at once. The original will load the programs that are necessary for the currently playing
+ // sounds into a fixed 100000 bytes buffer. The approach here needs more memory (approx. 480 KB for MI2), but we
+ // can easily afford this and it saves me the trouble of implementing a loader into the imuse code. The original
+ // loader is quite unpleasant, since it scans the whole imuse midi track for program change events and collects
+ // the program numbers for each such event in a buffer. Afterwards these instruments will get loaded.
+ for (int i = 0; i < 128; ++i)
+ loadInstrument(i);
+
+ // Actually not all of the .IMS files are required to play. Many of these contain copies of the same instruments.
+ // Each floppy disk contains one of the .IMS files. This would reduce the number of necessary floppy disk changes
+ // when playing from the floppy disks. Obviously we don't need the redundancy files. The error dialog will display
+ // only the required files. These are different for MI2 and INDY4.
+ if (_missingFiles) {
+ Common::String message = _("This AMIGA version is missing (at least) the following file(s):\n\n");
+ for (int i = 0; i < 11; ++i) {
+ if (_missingFiles & (1 << i))
+ message += Common::String::format("AMIGA%d.IMS\n", i + 1);
+ }
+ message += _("\nPlease copy these file(s) into the game data directory.\n\n");
+ ::GUI::displayErrorDialog(message.c_str());
+ return MERR_DEVICE_NOT_AVAILABLE;
+ }
+
+ startPaula();
+ _mixer->playStream(Audio::Mixer::kPlainSoundType,
+ &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+
+ _isOpen = true;
+
+ return 0;
+}
+
+void IMuseDriver_Amiga::close() {
+ if (!_isOpen)
+ return;
+
+ _isOpen = false;
+
+ Common::StackLock lock(_mutex);
+
+ stopPaula();
+ setTimerCallback(0, 0);
+ _mixer->stopHandle(_soundHandle);
+
+ unloadInstruments();
+
+ g_system->delayMillis(20);
+}
+
+void IMuseDriver_Amiga::send(uint32 b) {
+ if (!_isOpen)
+ return;
+
+ byte param2 = (b >> 16) & 0xFF;
+ byte param1 = (b >> 8) & 0xFF;
+ byte cmd = b & 0xF0;
+
+ IMusePart_Amiga *p = _parts[b & 0x0F];
+
+ switch (cmd) {
+ case 0x80:
+ p->noteOff(param1);
+ break;
+ case 0x90:
+ p->noteOn(param1, param2);
+ break;
+ case 0xB0:
+ p->controlChange(param1, param2);
+ break;
+ case 0xC0:
+ p->programChange(param1);
+ break;
+ case 0xE0:
+ p->pitchBend((param1 | (param2 << 7)) - 0x2000);
+ break;
+ case 0xF0:
+ warning("IMuseDriver_Amiga: Receiving SysEx command on a send() call");
+ break;
+
+ default:
+ break;
+ }
+}
+
+void IMuseDriver_Amiga::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
+ _timerProc = timer_proc;
+ _timerProcPara = timer_param;
+}
+
+uint32 IMuseDriver_Amiga::getBaseTempo() {
+ return _baseTempo;
+}
+
+MidiChannel *IMuseDriver_Amiga::allocateChannel() {
+ if (!_isOpen)
+ return 0;
+
+ for (int i = 0; i < _numParts; ++i) {
+ if (_parts[i]->allocate())
+ return _parts[i];
+ }
+
+ return 0;
+}
+
+MidiChannel *IMuseDriver_Amiga::getPercussionChannel() {
+ return 0;
+}
+
+void IMuseDriver_Amiga::interrupt() {
+ if (!_isOpen)
+ return;
+
+ for (_ticker += _internalTempo; _ticker >= _baseTempo; _ticker -= _baseTempo) {
+ updateParser();
+ updateSounds();
+ }
+}
+
+void IMuseDriver_Amiga::updateParser() {
+ if (_timerProc)
+ _timerProc(_timerProcPara);
+}
+
+void IMuseDriver_Amiga::updateSounds() {
+ for (int i = 0; i < 4; i++)
+ _chan[i]->updateLevel();
+ for (int i = 0; i < 4; i++)
+ _chan[i]->updateEnvelope();
+}
+
+void IMuseDriver_Amiga::loadInstrument(int program) {
+ Common::StackLock lock(_mutex);
+
+ if (program == 128) {
+ // The hard-coded default instrument definitions and sample data are the same in MI2 and INDY4.
+ static const int8 defaultData[16] = { 0, 49, 90, 117, 127, 117, 90, 49, 0, -49, -90, -117, -127, -117, -90, -49 };
+ static Instrument_Amiga::Samples defaultSamples = { 428, 60, 0, 127, 33, 0, /*0, 0,*/16, 0, 0, 5, 300, 5, 100, defaultData };
+ _instruments[128].numBlocks = 1;
+ memcpy(&_instruments[128].samples[0], &defaultSamples, sizeof(Instrument_Amiga::Samples));
+ }
+
+ if (program > 127)
+ return;
+
+ Common::File ims;
+ int32 header[10];
+ uint32 offset = 0;
+ memset(header, 0, sizeof(header));
+
+ for (int i = 0; i < 8; ++i) {
+ if (_instruments[program].samples[i].data) {
+ delete[] _instruments[program].samples[i].data;
+ _instruments[program].samples[i].data = 0;
+ }
+ }
+
+ for (int fileNo = 1; fileNo != -1 && !ims.isOpen(); ) {
+ if (!ims.open(Common::String::format("amiga%d.ims", fileNo))) {
+ _missingFiles |= (1 << (fileNo - 1));
+ return;
+ }
+
+ ims.seek(16 + (program << 2), SEEK_SET);
+ offset = ims.readUint32BE();
+ if (offset & 0x40000000) {
+ offset &= ~0x40000000;
+ ims.seek(16 + (offset << 2), SEEK_SET);
+ offset = ims.readUint32BE();
+ }
+
+ if (offset & 0x80000000) {
+ offset &= ~0x80000000;
+ ims.close();
+ fileNo = offset ? offset : -1;
+ } else {
+ ims.seek(552 + offset, SEEK_SET);
+ for (int i = 0; i < 10; ++i)
+ header[i] = ims.readSint32BE();
+ }
+ }
+
+ if (!ims.isOpen())
+ return;
+
+ for (int block = 0; block < 8; ++block) {
+ int size = 0;
+
+ if (header[block] != -1)
+ size = (block != 7 && header[block + 1] != -1 ? header[block + 1] : header[9]) - header[block];
+
+ if (size <= 0)
+ break;
+
+ size -= 38;
+ Instrument_Amiga::Samples *s = &_instruments[program].samples[block];
+ ims.seek(594 + offset + header[block], SEEK_SET);
+ int8 *buf = new int8[size];
+
+ s->rate = ims.readUint16BE();
+ s->baseNote = ims.readUint16BE();
+ s->noteRangeMin = ims.readSint16BE();
+ s->noteRangeMax = ims.readSint16BE();
+ s->sustainLevel = ims.readSint16BE();
+ s->type = ims.readUint16BE();
+ ims.skip(8);
+ s->numSamples = size;
+ s->dr_offset = ims.readUint32BE();
+ s->dr_numSamples = ims.readUint32BE();
+ s->levelFadeDelayAT = ims.readSint16BE();
+ s->levelFadeDelayRL = ims.readSint16BE();
+ s->levelFadeTriggerRL = ims.readSint16BE();
+ s->levelFadeDelayDC = ims.readSint16BE();
+ ims.read(buf, size);
+ s->data = buf;
+ _instruments[program].numBlocks = block + 1;
+ }
+
+ ims.close();
+}
+
+void IMuseDriver_Amiga::unloadInstruments() {
+ Common::StackLock lock(_mutex);
+ for (int prg = 0; prg < 128; ++prg) {
+ for (int block = 0; block < 8; ++block) {
+ if (_instruments[prg].samples[block].data)
+ delete[] _instruments[prg].samples[block].data;
+ }
+ }
+ memset(_instruments, 0, sizeof(Instrument_Amiga) * 128);
+}
+
+}
diff --git a/engines/scumm/imuse/drivers/amiga.h b/engines/scumm/imuse/drivers/amiga.h
new file mode 100644
index 0000000..77c7507
--- /dev/null
+++ b/engines/scumm/imuse/drivers/amiga.h
@@ -0,0 +1,85 @@
+/* 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 IMUSE_DRV_AMIGA_H
+#define IMUSE_DRV_AMIGA_H
+
+#include "audio/mididrv.h"
+#include "audio/mods/paula.h"
+#include "audio/mixer.h"
+
+namespace Scumm {
+
+class IMusePart_Amiga;
+class SoundChannel_Amiga;
+struct Instrument_Amiga;
+
+class IMuseDriver_Amiga : public MidiDriver, public Audio::Paula {
+friend class SoundChannel_Amiga;
+public:
+ IMuseDriver_Amiga(Audio::Mixer *mixer);
+ ~IMuseDriver_Amiga();
+
+ int open();
+ bool isOpen() const { return _isOpen; }
+ void close();
+
+ void send(uint32 b);
+
+ void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc);
+
+ uint32 getBaseTempo();
+ MidiChannel *allocateChannel();
+ MidiChannel *getPercussionChannel();
+
+ void interrupt();
+
+private:
+ void updateParser();
+ void updateSounds();
+
+ void loadInstrument(int program);
+ void unloadInstruments();
+
+ IMusePart_Amiga **_parts;
+ SoundChannel_Amiga **_chan;
+
+ Common::TimerManager::TimerProc _timerProc;
+ void *_timerProcPara;
+
+ Audio::Mixer *_mixer;
+ Audio::SoundHandle _soundHandle;
+
+ int32 _ticker;
+ bool _isOpen;
+
+ Instrument_Amiga *_instruments;
+ uint16 _missingFiles;
+
+ const int32 _baseTempo;
+ const int32 _internalTempo;
+ const uint8 _numParts;
+};
+
+}
+
+#endif
diff --git a/engines/scumm/imuse/imuse.cpp b/engines/scumm/imuse/imuse.cpp
index f5526ab..5fd5d21 100644
--- a/engines/scumm/imuse/imuse.cpp
+++ b/engines/scumm/imuse/imuse.cpp
@@ -157,7 +157,9 @@ bool IMuseInternal::isMT32(int sound) {
case MKTAG('S', 'P', 'K', ' '):
return false;
- case MKTAG('A', 'M', 'I', ' '):
+ case MKTAG('A', 'M', 'I', ' '): // Amiga
+ return false;
+
case MKTAG('R', 'O', 'L', ' '):
return true;
@@ -199,7 +201,9 @@ bool IMuseInternal::isMIDI(int sound) {
case MKTAG('S', 'P', 'K', ' '):
return false;
- case MKTAG('A', 'M', 'I', ' '):
+ case MKTAG('A', 'M', 'I', ' '): // Amiga
+ return false;
+
case MKTAG('R', 'O', 'L', ' '):
return true;
@@ -236,7 +240,9 @@ bool IMuseInternal::supportsPercussion(int sound) {
case MKTAG('S', 'P', 'K', ' '):
return false;
- case MKTAG('A', 'M', 'I', ' '):
+ case MKTAG('A', 'M', 'I', ' '): // Amiga
+ return false;
+
case MKTAG('R', 'O', 'L', ' '):
return true;
diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index f5ba4ba..bcc1eba 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -32,6 +32,7 @@ MODULE_OBJS := \
imuse/pcspk.o \
imuse/sysex_samnmax.o \
imuse/sysex_scumm.o \
+ imuse/drivers/amiga.o \
input.o \
midiparser_ro.o \
object.o \
Commit: 0899ecc98760bba79bccc2026bcfa44542d46d8f
https://github.com/scummvm/scummvm/commit/0899ecc98760bba79bccc2026bcfa44542d46d8f
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-14T20:56:27+02:00
Commit Message:
SCUMM: hook up Amiga MI2 + INDY4 to new sound driver
Changed paths:
engines/scumm/detection_tables.h
engines/scumm/imuse/imuse.cpp
engines/scumm/scumm-md5.h
engines/scumm/scumm.cpp
diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h
index ce8eecf..3dae5e1 100644
--- a/engines/scumm/detection_tables.h
+++ b/engines/scumm/detection_tables.h
@@ -247,13 +247,15 @@ static const GameSettings gameVariantsTable[] = {
{"monkey", "SEGA", 0, GID_MONKEY, 5, 0, MDT_NONE, GF_AUDIOTRACKS, Common::kPlatformSegaCD, GUIO2(GUIO_NOSPEECH, GUIO_NOMIDI)},
{"monkey", "SE Talkie", 0, GID_MONKEY, 5, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, GF_AUDIOTRACKS, UNK, GUIO0()},
- {"monkey2", "", 0, GID_MONKEY2, 5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO1(GUIO_NOSPEECH)},
+ {"monkey2", "", 0, GID_MONKEY2, 5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO1(GUIO_NOSPEECH)},
+ {"monkey2", "Amiga", 0, GID_MONKEY2, 5, 0, MDT_AMIGA, 0, Common::kPlatformAmiga, GUIO2(GUIO_NOSPEECH, GUIO_MIDIAMIGA)},
{"monkey2", "FM-TOWNS", 0, GID_MONKEY2, 5, 0, MDT_PCSPK | MDT_TOWNS | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, Common::kPlatformFMTowns, GUIO5(GUIO_NOSPEECH, GUIO_MIDITOWNS, GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_NOASPECT)},
{"monkey2", "SE Talkie",0, GID_MONKEY2, 5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO0()},
{"atlantis", "", 0, GID_INDY4, 5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO0()},
{"atlantis", "Steam", "steam", GID_INDY4, 5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO0()},
{"atlantis", "Floppy", 0, GID_INDY4, 5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO1(GUIO_NOSPEECH)},
+ {"atlantis", "Amiga", 0, GID_INDY4, 5, 0, MDT_AMIGA, 0, Common::kPlatformAmiga, GUIO2(GUIO_NOSPEECH, GUIO_MIDIAMIGA)},
{"atlantis", "FM-TOWNS", 0, GID_INDY4, 5, 0, MDT_TOWNS | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, Common::kPlatformFMTowns, GUIO4(GUIO_MIDITOWNS, GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_NOASPECT)},
{"tentacle", "", 0, GID_TENTACLE, 6, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_GM, GF_USE_KEY, UNK, GUIO0()},
diff --git a/engines/scumm/imuse/imuse.cpp b/engines/scumm/imuse/imuse.cpp
index 5fd5d21..ad921e4 100644
--- a/engines/scumm/imuse/imuse.cpp
+++ b/engines/scumm/imuse/imuse.cpp
@@ -201,8 +201,8 @@ bool IMuseInternal::isMIDI(int sound) {
case MKTAG('S', 'P', 'K', ' '):
return false;
- case MKTAG('A', 'M', 'I', ' '): // Amiga
- return false;
+ case MKTAG('A', 'M', 'I', ' '): // Amiga (return true, since the driver is initalized as native midi)
+ return true;
case MKTAG('R', 'O', 'L', ' '):
return true;
diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h
index 101676d..8a09bbb 100644
--- a/engines/scumm/scumm-md5.h
+++ b/engines/scumm/scumm-md5.h
@@ -68,10 +68,10 @@ static const MD5Table md5table[] = {
{ "10d8e66cd11049ce64815ebb9fd76eb3", "spyozon", "", "", -1, Common::FR_FRA, Common::kPlatformUnknown },
{ "111b36172bdc9bfe498e135878c03d38", "pajama", "HE 101", "", 66878, Common::NL_NLD, Common::kPlatformWii },
{ "114acdc2659a273c220f86ee9edb24c1", "maniac", "V2", "V2", -1, Common::FR_FRA, Common::kPlatformDOS },
- { "11ddf1fde76e3156eb3a38da213f484e", "monkey2", "", "", -1, Common::IT_ITA, Common::kPlatformAmiga },
+ { "11ddf1fde76e3156eb3a38da213f484e", "monkey2", "Amiga", "", -1, Common::IT_ITA, Common::kPlatformAmiga },
{ "11e6e244078ff09b0f3832e35420e0a7", "catalog", "", "Demo", -1, Common::EN_ANY, Common::kPlatformWindows },
{ "12cdc256eae5a461bcc9a49975999841", "atlantis", "Floppy", "Demo", -1, Common::EN_ANY, Common::kPlatformDOS },
- { "132bff65e6367c09cc69318ce1b59333", "monkey2", "", "", 11155, Common::EN_ANY, Common::kPlatformAmiga },
+ { "132bff65e6367c09cc69318ce1b59333", "monkey2", "Amiga", "", 11155, Common::EN_ANY, Common::kPlatformAmiga },
{ "1387d16aa620dc1c2d1fd87f8a9e7a09", "puttcircus", "", "Demo", -1, Common::FR_FRA, Common::kPlatformWindows },
{ "13d2a86a7290813a1c386490447d72db", "fbear", "HE 62", "", -1, Common::EN_ANY, Common::kPlatform3DO },
{ "145bd3373574feb668cc2eea2ec6cf86", "balloon", "HE 80", "", -1, Common::RU_RUS, Common::kPlatformWindows },
@@ -81,7 +81,7 @@ static const MD5Table md5table[] = {
{ "157367c3c21e0d03a0cba44361b4cf65", "indy3", "No AdLib", "EGA", -1, Common::EN_ANY, Common::kPlatformAtariST },
{ "15878e3bee2e1e759184abee98589eaa", "spyfox", "HE 100", "", -1, Common::EN_ANY, Common::kPlatformIOS },
{ "15e03ffbfeddb9c2aebc13dcb2a4a8f4", "monkey", "VGA", "VGA", 8357, Common::EN_ANY, Common::kPlatformDOS },
- { "15f588e887e857e8c56fe6ade4956168", "atlantis", "Floppy", "Floppy", -1, Common::ES_ESP, Common::kPlatformAmiga },
+ { "15f588e887e857e8c56fe6ade4956168", "atlantis", "Amiga", "Floppy", -1, Common::ES_ESP, Common::kPlatformAmiga },
{ "16542a7342a918bfe4ba512007d36c47", "FreddisFunShop", "HE 99L", "", -1, Common::EN_USA, Common::kPlatformUnknown },
{ "166553538ff320c69edafeee29525419", "samnmax", "", "CD", 199195304, Common::EN_ANY, Common::kPlatformMacintosh },
{ "16effd200aa6b8abe9c569c3e578814d", "freddi4", "HE 99", "Demo", -1, Common::NL_NLD, Common::kPlatformUnknown },
@@ -115,7 +115,7 @@ static const MD5Table md5table[] = {
{ "2012f854d83d9cc6f73b2b544cd8bbf8", "water", "HE 80", "", -1, Common::RU_RUS, Common::kPlatformWindows },
{ "20176076d708bf14407bcc9bdcd7a418", "pajama3", "", "", -1, Common::RU_RUS, Common::kPlatformWindows },
{ "204453e33456c4faa26e276229fe5b76", "spyfox2", "", "Demo", 14689, Common::DE_DEU, Common::kPlatformWindows },
- { "20da6fce37805423966aaa8f3c2426aa", "atlantis", "Floppy", "Floppy", -1, Common::FR_FRA, Common::kPlatformAmiga },
+ { "20da6fce37805423966aaa8f3c2426aa", "atlantis", "Amiga", "Floppy", -1, Common::FR_FRA, Common::kPlatformAmiga },
{ "2108d83dcf09f8adb4bc524669c8cf51", "PuttTime", "HE 99", "Updated", -1, Common::EN_USA, Common::kPlatformUnknown },
{ "21a6592322f92550f144f68a8a4e685e", "dig", "", "", -1, Common::FR_FRA, Common::kPlatformMacintosh },
{ "21abe302e1b1e2b66d6f5c12e241ebfd", "freddicove", "unenc", "Unencrypted", -1, Common::RU_RUS, Common::kPlatformWindows },
@@ -182,7 +182,7 @@ static const MD5Table md5table[] = {
{ "399b217b0c8d65d0398076da486363a9", "indy3", "VGA", "VGA", 6295, Common::DE_DEU, Common::kPlatformDOS },
{ "39cb9dec16fa16f38d79acd80effb059", "loom", "EGA", "EGA", -1, Common::UNK_LANG, Common::kPlatformAmiga },
{ "39fd6db10d0222d817025c4d3346e3b4", "farm", "HE 72", "Demo", -1, Common::EN_ANY, Common::kPlatformMacintosh },
- { "3a03dab514e4038df192d8a8de469788", "atlantis", "Floppy", "Floppy", -1, Common::EN_ANY, Common::kPlatformAmiga },
+ { "3a03dab514e4038df192d8a8de469788", "atlantis", "Amiga", "Floppy", -1, Common::EN_ANY, Common::kPlatformAmiga },
{ "3a0c35f3c147b98a2bdf8d400cfc4ab5", "indy3", "FM-TOWNS", "", -1, Common::JA_JPN, Common::kPlatformFMTowns },
{ "3a3e592b074f595489f7f11e150c398d", "puttzoo", "HE 99", "Updated", -1, Common::EN_ANY, Common::kPlatformWindows },
{ "3a5d13675e9a23aedac0bac7730f0ac1", "samnmax", "", "CD", 228446581, Common::FR_FRA, Common::kPlatformMacintosh },
@@ -243,7 +243,7 @@ static const MD5Table md5table[] = {
{ "4f04b321a95d4315ce6d65f8e1dd0368", "maze", "HE 80", "", -1, Common::EN_USA, Common::kPlatformUnknown },
{ "4f138ac6f9b2ac5a41bc68b2c3296064", "freddi4", "HE 99", "", -1, Common::FR_FRA, Common::kPlatformWindows },
{ "4f1d6f8b38343dba405472538b5037ed", "fbear", "HE 62", "", 7717, Common::EN_ANY, Common::kPlatformDOS },
- { "4f267a901719623de7dde83e47d5b474", "atlantis", "Floppy", "Floppy", -1, Common::DE_DEU, Common::kPlatformAmiga },
+ { "4f267a901719623de7dde83e47d5b474", "atlantis", "Amiga", "Floppy", -1, Common::DE_DEU, Common::kPlatformAmiga },
{ "4f580a021eee026f3b4589e17d130d78", "freddi4", "", "", 44190, Common::UNK_LANG, Common::kPlatformUnknown },
{ "4fa6870d9bc8c313b65d54b1da5a1891", "pajama", "", "", -1, Common::NL_NLD, Common::kPlatformUnknown },
{ "4fbbe9f64b8bc547503a379a301183ce", "tentacle", "", "CD", -1, Common::IT_ITA, Common::kPlatformUnknown },
@@ -265,7 +265,7 @@ static const MD5Table md5table[] = {
{ "56b5922751be7ffd771b38dda56b028b", "freddi", "HE 100", "", 34837, Common::NL_NLD, Common::kPlatformWii },
{ "56e8c37a0a08c3a7076f82417461a877", "indy3", "EGA", "EGA", -1, Common::EN_ANY, Common::kPlatformDOS },
{ "5719fc8a13b4638b78d9d8d12f091f94", "puttrace", "HE 99", "", -1, Common::FR_FRA, Common::kPlatformWindows },
- { "5798972220cd458be2626d54c80f71d7", "atlantis", "Floppy", "Floppy", -1, Common::IT_ITA, Common::kPlatformAmiga },
+ { "5798972220cd458be2626d54c80f71d7", "atlantis", "Amiga", "Floppy", -1, Common::IT_ITA, Common::kPlatformAmiga },
{ "57a17febe2183f521250e55d55b83e60", "PuttTime", "HE 99", "", -1, Common::FR_FRA, Common::kPlatformWindows },
{ "57a5cfec9ef231a007043cc1917e8988", "freddi", "HE 100", "", -1, Common::EN_ANY, Common::kPlatformWii },
{ "57b0d89af79befe1cabce3bece869e7f", "tentacle", "Floppy", "Floppy", -1, Common::DE_DEU, Common::kPlatformDOS },
@@ -336,7 +336,7 @@ static const MD5Table md5table[] = {
{ "6dead580b0ff14d5f7b33b4219f04159", "samnmax", "", "Demo", 16556335, Common::EN_ANY, Common::kPlatformMacintosh },
{ "6df20c50c1ab19799de9be7ae7716881", "fbear", "HE 62", "Demo", -1, Common::EN_ANY, Common::kPlatformMacintosh },
{ "6e959d65358eedf9b68b81e304b97fa4", "tentacle", "", "CD", 7932, Common::DE_DEU, Common::kPlatformUnknown },
- { "6ea966b4d660c870b9ee790d1fbfc535", "monkey2", "", "", -1, Common::ES_ESP, Common::kPlatformAmiga },
+ { "6ea966b4d660c870b9ee790d1fbfc535", "monkey2", "Amiga", "", -1, Common::ES_ESP, Common::kPlatformAmiga },
{ "6f0be328c64d689bb606d22a389e1b0f", "loom", "No AdLib", "EGA", 5748, Common::EN_ANY, Common::kPlatformMacintosh },
{ "6f4d64ce1b44470536cae2eb6fe8aefd", "spyfox", "HE 100", "", 49742, Common::FR_FRA, Common::kPlatformWii },
{ "6f6ef668c608c7f534fea6e6d3878dde", "indy3", "EGA", "EGA", -1, Common::DE_DEU, Common::kPlatformDOS },
@@ -550,7 +550,7 @@ static const MD5Table md5table[] = {
{ "c225bec1b6c0798a2b8c89ac226dc793", "pajama", "HE 101", "", -1, Common::EN_ANY, Common::kPlatformWii },
{ "c24c490373aeb48fbd54caa8e7ae376d", "loom", "No AdLib", "EGA", -1, Common::DE_DEU, Common::kPlatformAtariST },
{ "c25755b08a8d0d47695e05f1e2111bfc", "freddi4", "", "Demo", -1, Common::EN_USA, Common::kPlatformUnknown },
- { "c30ef068add4277104243c31ce46c12b", "monkey2", "", "", -1, Common::FR_FRA, Common::kPlatformAmiga },
+ { "c30ef068add4277104243c31ce46c12b", "monkey2", "Amiga", "", -1, Common::FR_FRA, Common::kPlatformAmiga },
{ "c3196c5349e53e387aaff1533d95e53a", "samnmax", "Floppy", "Demo", -1, Common::EN_ANY, Common::kPlatformDOS },
{ "c3b22fa4654bb580b20325ebf4174841", "puttzoo", "", "", -1, Common::NL_NLD, Common::kPlatformWindows },
{ "c3df37df9d3b481b45f75283a9907c47", "loom", "EGA", "EGA", -1, Common::IT_ITA, Common::kPlatformDOS },
@@ -621,7 +621,7 @@ static const MD5Table md5table[] = {
{ "d9d0dd93d16ab4dec55cabc2b86bbd17", "samnmax", "", "Demo", 6478, Common::EN_ANY, Common::kPlatformDOS },
{ "da09e666fc8f5b78d7b0ac65d1a3b56e", "monkey2", "FM-TOWNS", "", 11135, Common::EN_ANY, Common::kPlatformFMTowns },
{ "da6269b18fcb08189c0aa9c95533cce2", "monkey", "CD", "CD", 8955, Common::IT_ITA, Common::kPlatformDOS },
- { "da669b20271b85182e9c17a2a37ea02e", "monkey2", "", "", -1, Common::DE_DEU, Common::kPlatformAmiga },
+ { "da669b20271b85182e9c17a2a37ea02e", "monkey2", "Amiga", "", -1, Common::DE_DEU, Common::kPlatformAmiga },
{ "db21a6e338fe3b70c2723b6530865bf2", "PuttTime", "HE 85", "", -1, Common::FR_FRA, Common::kPlatformUnknown },
{ "db74136c20557eca6ed3411bff39f7a1", "puttcircus", "", "", -1, Common::EN_GRB, Common::kPlatformWindows },
{ "dbf4d59d70b826733f379f998354d350", "BluesBirthday", "", "Demo", -1, Common::EN_ANY, Common::kPlatformUnknown },
@@ -643,7 +643,7 @@ static const MD5Table md5table[] = {
{ "e41de1c2a15abbcdbf9977e2d7e8a340", "freddi2", "HE 100", "Updated", -1, Common::RU_RUS, Common::kPlatformWindows },
{ "e441af0b65983c1a4b3a52f9c7b15484", "spyfox", "HE 100", "", 49742, Common::EN_USA, Common::kPlatformWii },
{ "e44ea295a3f8fe4f41983080dab1e9ce", "freddi", "HE 90", "Updated", -1, Common::FR_FRA, Common::kPlatformMacintosh },
- { "e534d29afb3c6e0ee9dc3d53c5956714", "atlantis", "Floppy", "Floppy", -1, Common::DE_DEU, Common::kPlatformAmiga },
+ { "e534d29afb3c6e0ee9dc3d53c5956714", "atlantis", "Amiga", "Floppy", -1, Common::DE_DEU, Common::kPlatformAmiga },
{ "e5563c8358443c4352fcddf7402a5e0a", "pajama2", "HE 98.5", "", -1, Common::FR_FRA, Common::kPlatformWindows },
{ "e5c112140ad6574997de033a8e2a2964", "readtime", "", "", -1, Common::EN_ANY, Common::kPlatformUnknown },
{ "e62056ba675ad65d8854ab3c5ad4b3c0", "spyfox2", "", "Mini Game", 14689, Common::EN_GRB, Common::kPlatformWindows },
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 1102890..f6e0f9e 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -78,6 +78,7 @@
#include "scumm/verbs.h"
#include "scumm/imuse/pcspk.h"
#include "scumm/imuse/mac_m68k.h"
+#include "scumm/imuse/drivers/amiga.h"
#include "backends/audiocd/audiocd.h"
@@ -1837,6 +1838,9 @@ void ScummEngine::setupMusic(int midi) {
case MT_NULL:
_sound->_musicType = MDT_NONE;
break;
+ case MT_AMIGA:
+ _sound->_musicType = MDT_AMIGA;
+ break;
case MT_PCSPK:
_sound->_musicType = MDT_PCSPK;
break;
@@ -1985,6 +1989,10 @@ void ScummEngine::setupMusic(int midi) {
_native_mt32 = false;
// Ignore non-native drivers. This also ignores the multi MIDI setting.
useOnlyNative = true;
+ } else if (_sound->_musicType == MDT_AMIGA) {
+ nativeMidiDriver = new IMuseDriver_Amiga(_mixer);
+ _native_mt32 = false;
+ useOnlyNative = true;
} else if (_sound->_musicType != MDT_ADLIB && _sound->_musicType != MDT_TOWNS && _sound->_musicType != MDT_PCSPK) {
nativeMidiDriver = MidiDriver::createMidi(dev);
}
Commit: d1b64aab0cd1477f6d56c9ced8b84886d66de4d8
https://github.com/scummvm/scummvm/commit/d1b64aab0cd1477f6d56c9ced8b84886d66de4d8
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-14T21:45:29+02:00
Commit Message:
SCUMM: (iMuse/Amiga) - improve accuracy
This fixes the issue that some rhythm instruments didn't receive correct notes. The changes have been limited to the Amiga versions.
Changed paths:
engines/scumm/imuse/imuse.cpp
engines/scumm/imuse/imuse.h
engines/scumm/imuse/imuse_internal.h
engines/scumm/imuse/imuse_part.cpp
engines/scumm/scumm.cpp
diff --git a/engines/scumm/imuse/imuse.cpp b/engines/scumm/imuse/imuse.cpp
index ad921e4..37a0465 100644
--- a/engines/scumm/imuse/imuse.cpp
+++ b/engines/scumm/imuse/imuse.cpp
@@ -45,6 +45,7 @@ namespace Scumm {
IMuseInternal::IMuseInternal() :
_native_mt32(false),
_enable_gs(false),
+ _isAmiga(false),
_midi_adlib(NULL),
_midi_native(NULL),
_sysex(NULL),
@@ -481,6 +482,10 @@ uint32 IMuseInternal::property(int prop, uint32 value) {
}
break;
+ case IMuse::PROP_AMIGA:
+ _isAmiga = (value > 0);
+ break;
+
case IMuse::PROP_LIMIT_PLAYERS:
if (value > 0 && value <= ARRAYSIZE(_players))
_player_limit = (int)value;
diff --git a/engines/scumm/imuse/imuse.h b/engines/scumm/imuse/imuse.h
index a30d3db..7b2fb81 100644
--- a/engines/scumm/imuse/imuse.h
+++ b/engines/scumm/imuse/imuse.h
@@ -53,6 +53,7 @@ public:
PROP_TEMPO_BASE,
PROP_NATIVE_MT32,
PROP_GS,
+ PROP_AMIGA,
PROP_LIMIT_PLAYERS,
PROP_RECYCLE_PLAYERS,
PROP_GAME_ID,
diff --git a/engines/scumm/imuse/imuse_internal.h b/engines/scumm/imuse/imuse_internal.h
index 7aff68a..4b9b750 100644
--- a/engines/scumm/imuse/imuse_internal.h
+++ b/engines/scumm/imuse/imuse_internal.h
@@ -372,6 +372,7 @@ struct Part : public Common::Serializable {
private:
void sendPitchBend();
+ void sendTranspose();
void sendPanPosition(uint8 value);
void sendEffectLevel(uint8 value);
};
@@ -399,6 +400,7 @@ class IMuseInternal : public IMuse {
protected:
bool _native_mt32;
bool _enable_gs;
+ bool _isAmiga;
MidiDriver *_midi_adlib;
MidiDriver *_midi_native;
TimerCallbackInfo _timer_info_adlib;
diff --git a/engines/scumm/imuse/imuse_part.cpp b/engines/scumm/imuse/imuse_part.cpp
index 33492a1..1614076 100644
--- a/engines/scumm/imuse/imuse_part.cpp
+++ b/engines/scumm/imuse/imuse_part.cpp
@@ -145,8 +145,17 @@ void Part::set_pan(int8 pan) {
void Part::set_transpose(int8 transpose) {
_transpose = transpose;
- _transpose_eff = (_transpose == -128) ? 0 : transpose_clamp(_transpose + _player->getTranspose(), -24, 24);
- sendPitchBend();
+
+ if (_se->_isAmiga) {
+ // The Amiga version does a check like this. While this is probably a bug (a signed int8 can never be 128),
+ // the playback depends on this being implemented exactly like in the original driver. I found this bug with
+ // the WinUAE debugger. I don't know whether this is an Amiga only thing...
+ _transpose_eff = /*(_transpose == 128) ? 0 : */transpose_clamp(_transpose + _player->getTranspose(), -12, 12);
+ sendTranspose();
+ } else {
+ _transpose_eff = (_transpose == -128) ? 0 : transpose_clamp(_transpose + _player->getTranspose(), -24, 24);
+ sendPitchBend();
+ }
}
void Part::sustain(bool value) {
@@ -332,6 +341,7 @@ void Part::sendAll() {
return;
_mc->pitchBendFactor(_pitchbend_factor);
+ sendTranspose();
sendPitchBend();
_mc->volume(_vol_eff);
_mc->sustain(_pedal);
@@ -358,7 +368,24 @@ void Part::sendPitchBend() {
// so we'll do the scaling ourselves.
if (_player->_se->isNativeMT32())
bend = bend * _pitchbend_factor / 12;
- _mc->pitchBend(clamp(bend + (_detune_eff * 64 / 12) + (_transpose_eff * 8192 / 12), -8192, 8191));
+
+ // We send the transpose value separately for Amiga (which is more like the original handles this).
+ // Some rhythm instruments depend on this.
+ int8 transpose = _se->_isAmiga ? 0 : _transpose_eff;
+ _mc->pitchBend(clamp(bend + (_detune_eff * 64 / 12) + (transpose * 8192 / 12), -8192, 8191));
+}
+
+void Part::sendTranspose() {
+ if (!_mc)
+ return;
+
+ // See comment above. The transpose function was never implemented into our other drivers,
+ // since this seems to have been handled via pitchBend() instead. The original drivers do have
+ // such functions.
+ if (!_se->_isAmiga)
+ return;
+
+ _mc->transpose(_transpose_eff);
}
void Part::programChange(byte value) {
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index f6e0f9e..64f45c1 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -2039,6 +2039,8 @@ void ScummEngine::setupMusic(int midi) {
}
if (_sound->_musicType == MDT_PCSPK)
_imuse->property(IMuse::PROP_PC_SPEAKER, 1);
+ if (_sound->_musicType == MDT_AMIGA)
+ _imuse->property(IMuse::PROP_AMIGA, 1);
}
}
}
Commit: 01f99f1a0a39a5e09f9eafc9e03ece4a8470bf1c
https://github.com/scummvm/scummvm/commit/01f99f1a0a39a5e09f9eafc9e03ece4a8470bf1c
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-14T21:45:43+02:00
Commit Message:
SCUMM: imuse driver directory cleanup
- move mac, pc speaker and fm-towns ims sound drivers into separate directory
(AdLib and MT32/GM drivers are still too entangled with common code to be moved so easily, especially MT32/GM. It would require lots of changes to the common code and possibly to all engines using the MidiDriver class. So I leave that for now.)
Changed paths:
A engines/scumm/imuse/drivers/fmtowns.cpp
A engines/scumm/imuse/drivers/fmtowns.h
A engines/scumm/imuse/drivers/mac_m68k.cpp
A engines/scumm/imuse/drivers/mac_m68k.h
A engines/scumm/imuse/drivers/pcspk.cpp
A engines/scumm/imuse/drivers/pcspk.h
R audio/softsynth/fmtowns_pc98/towns_midi.cpp
R audio/softsynth/fmtowns_pc98/towns_midi.h
R engines/scumm/imuse/mac_m68k.cpp
R engines/scumm/imuse/mac_m68k.h
R engines/scumm/imuse/pcspk.cpp
R engines/scumm/imuse/pcspk.h
audio/module.mk
audio/softsynth/fmtowns_pc98/towns_pc98_plugins.cpp
engines/scumm/module.mk
engines/scumm/players/player_towns.h
engines/scumm/scumm.cpp
diff --git a/audio/module.mk b/audio/module.mk
index 686c70b..c2d764a 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -49,7 +49,6 @@ MODULE_OBJS := \
softsynth/fmtowns_pc98/pc98_audio.o \
softsynth/fmtowns_pc98/towns_audio.o \
softsynth/fmtowns_pc98/towns_euphony.o \
- softsynth/fmtowns_pc98/towns_midi.o \
softsynth/fmtowns_pc98/towns_pc98_driver.o \
softsynth/fmtowns_pc98/towns_pc98_fmsynth.o \
softsynth/fmtowns_pc98/towns_pc98_plugins.o \
diff --git a/audio/softsynth/fmtowns_pc98/towns_midi.cpp b/audio/softsynth/fmtowns_pc98/towns_midi.cpp
deleted file mode 100644
index c02b047..0000000
--- a/audio/softsynth/fmtowns_pc98/towns_midi.cpp
+++ /dev/null
@@ -1,1029 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#include "audio/softsynth/fmtowns_pc98/towns_midi.h"
-#include "audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h"
-#include "common/textconsole.h"
-#include "common/system.h"
-
-class TownsMidiOutputChannel {
-friend class TownsMidiInputChannel;
-public:
- TownsMidiOutputChannel(MidiDriver_TOWNS *driver, int chanId);
- ~TownsMidiOutputChannel();
-
- void noteOn(uint8 msb, uint16 lsb);
- void noteOnPitchBend(uint8 msb, uint16 lsb);
- void setupProgram(const uint8 *data, uint8 mLevelPara, uint8 tLevelPara);
- void setupEffects(int index, uint8 flags, const uint8 *effectData);
- void setModWheel(uint8 value);
-
- void connect(TownsMidiInputChannel *chan);
- void disconnect();
-
- bool update();
-
- enum CheckPriorityStatus {
- kDisconnected = -2,
- kHighPriority = -1
- };
-
- int checkPriority(int pri);
-
-private:
- struct EffectEnvelope {
- uint8 state;
- int32 currentLevel;
- int32 duration;
- int32 maxLevel;
- int32 startLevel;
- uint8 loop;
- uint8 stateTargetLevels[4];
- uint8 stateModWheelLevels[4];
- int8 modWheelSensitivity;
- int8 modWheelState;
- int8 modWheelLast;
- uint16 numSteps;
- uint32 stepCounter;
- int32 incrPerStep;
- int8 dir;
- uint32 incrPerStepRem;
- uint32 incrCountRem;
- } *_effectEnvelopes;
-
- struct EffectDef {
- int32 phase;
- uint8 type;
- uint8 useModWheel;
- uint8 loopRefresh;
- EffectEnvelope *s;
- } *_effectDefs;
-
- void startEffect(EffectEnvelope *s, const uint8 *effectData);
- void updateEffectGenerator(EffectEnvelope *s, EffectDef *d);
- int advanceEffectEnvelope(EffectEnvelope *s, EffectDef *d);
- void initNextEnvelopeState(EffectEnvelope *s);
- int16 getEffectStartLevel(uint8 type);
- int getEffectModLevel(int lvl, int mod);
-
- void keyOn();
- void keyOff();
- void keyOnSetFreq(uint16 frq);
- void out(uint8 reg, uint8 val);
-
- TownsMidiInputChannel *_in;
- TownsMidiOutputChannel *_prev;
- TownsMidiOutputChannel *_next;
- uint8 _adjustModTl;
- uint8 _chan;
- uint8 _note;
- uint8 _operator2Tl;
- uint8 _operator1Tl;
- uint8 _sustainNoteOff;
- int16 _duration;
-
- uint16 _freq;
- int16 _freqAdjust;
-
- MidiDriver_TOWNS *_driver;
-
- static const uint8 _chanMap[];
- static const uint8 _chanMap2[];
- static const uint8 _effectDefaults[];
- static const uint16 _effectEnvStepTable[];
- static const uint8 _freqMSB[];
- static const uint16 _freqLSB[];
-};
-
-class TownsMidiInputChannel : public MidiChannel {
-friend class TownsMidiOutputChannel;
-public:
- TownsMidiInputChannel(MidiDriver_TOWNS *driver, int chanIndex);
- ~TownsMidiInputChannel();
-
- MidiDriver *device() { return _driver; }
- byte getNumber() { return _chanIndex; }
- bool allocate();
- void release();
-
- void send(uint32 b);
-
- void noteOff(byte note);
- void noteOn(byte note, byte velocity);
- void programChange(byte program);
- void pitchBend(int16 bend);
- void controlChange(byte control, byte value);
- void pitchBendFactor(byte value);
- void priority(byte value);
- void sysEx_customInstrument(uint32 type, const byte *instr);
-
-private:
- void controlModulationWheel(byte value);
- void controlVolume(byte value);
- void controlPanPos(byte value);
- void controlSustain(byte value);
-
- void releasePedal();
-
- TownsMidiOutputChannel *_out;
-
- uint8 *_instrument;
- uint8 _chanIndex;
- uint8 _priority;
- uint8 _tl;
- int8 _transpose;
- int8 _detune;
- int8 _modWheel;
- uint8 _sustain;
- uint8 _pitchBendFactor;
- int16 _pitchBend;
- uint16 _freqLSB;
-
- bool _allocated;
-
- MidiDriver_TOWNS *_driver;
-
- static const uint8 _programAdjustLevel[];
-};
-
-class TownsMidiChanState {
-public:
- TownsMidiChanState();
- ~TownsMidiChanState() {}
- uint8 get(uint8 type);
-
- uint8 unk1;
- uint8 mulAmsFms;
- uint8 tl;
- uint8 attDec;
- uint8 sus;
- uint8 fgAlg;
- uint8 unk2;
-};
-
-TownsMidiChanState::TownsMidiChanState() {
- unk1 = mulAmsFms = tl = attDec = sus = fgAlg = unk2 = 0;
-}
-
-uint8 TownsMidiChanState::get(uint8 type) {
- switch (type) {
- case 0:
- return unk1;
- case 1:
- return mulAmsFms;
- case 2:
- return tl;
- case 3:
- return attDec;
- case 4:
- return sus;
- case 5:
- return fgAlg;
- case 6:
- return unk2;
- default:
- break;
- }
- return 0;
-}
-
-TownsMidiOutputChannel::TownsMidiOutputChannel(MidiDriver_TOWNS *driver, int chanIndex) : _driver(driver), _chan(chanIndex),
- _in(0), _prev(0), _next(0), _adjustModTl(0), _operator2Tl(0), _note(0), _operator1Tl(0), _sustainNoteOff(0), _duration(0), _freq(0), _freqAdjust(0) {
- _effectEnvelopes = new EffectEnvelope[2];
- _effectDefs = new EffectDef[2];
-
- memset(_effectEnvelopes, 0, 2 * sizeof(EffectEnvelope));
- memset(_effectDefs, 0, 2 * sizeof(EffectDef));
- _effectDefs[0].s = &_effectEnvelopes[1];
- _effectDefs[1].s = &_effectEnvelopes[0];
-}
-
-TownsMidiOutputChannel::~TownsMidiOutputChannel() {
- delete[] _effectEnvelopes;
- delete[] _effectDefs;
-}
-
-void TownsMidiOutputChannel::noteOn(uint8 msb, uint16 lsb) {
- _freq = (msb << 7) + lsb;
- _freqAdjust = 0;
- keyOnSetFreq(_freq);
-}
-
-void TownsMidiOutputChannel::noteOnPitchBend(uint8 msb, uint16 lsb) {
- _freq = (msb << 7) + lsb;
- keyOnSetFreq(_freq + _freqAdjust);
-}
-
-void TownsMidiOutputChannel::setupProgram(const uint8 *data, uint8 mLevelPara, uint8 tLevelPara) {
- // This driver uses only 2 operators and 2 algorithms (algorithm 5 and 7),
- // since it is just a modified AdLib driver. It also uses AdLib programs.
- // There are no FM-TOWNS specific programs. This is the reason for the low quality of the FM-TOWNS
- // music (unsuitable data is just forced into the wrong audio device).
-
- static const uint8 mul[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 12, 12, 15, 15 };
- uint8 chan = _chanMap[_chan];
-
- uint8 mulAmsFms1 = _driver->_chanState[chan].mulAmsFms = data[0];
- uint8 tl1 = _driver->_chanState[chan].tl = (data[1] | 0x3f) - mLevelPara;
- uint8 attDec1 = _driver->_chanState[chan].attDec = ~data[2];
- uint8 sus1 = _driver->_chanState[chan].sus = ~data[3];
- _driver->_chanState[chan].unk2 = data[4];
- chan += 3;
-
- out(0x30, mul[mulAmsFms1 & 0x0f]);
- out(0x40, (tl1 & 0x3f) + 15);
- out(0x50, ((attDec1 >> 4) << 1) | ((attDec1 >> 4) & 1));
- out(0x60, ((attDec1 << 1) | (attDec1 & 1)) & 0x1f);
- out(0x70, (mulAmsFms1 & 0x20) ^ 0x20 ? (((sus1 & 0x0f) << 1) | 1) : 0);
- out(0x80, sus1);
-
- uint8 mulAmsFms2 = _driver->_chanState[chan].mulAmsFms = data[5];
- uint8 tl2 = _driver->_chanState[chan].tl = (data[6] | 0x3f) - tLevelPara;
- uint8 attDec2 = _driver->_chanState[chan].attDec = ~data[7];
- uint8 sus2 = _driver->_chanState[chan].sus = ~data[8];
- _driver->_chanState[chan].unk2 = data[9];
-
- uint8 mul2 = mul[mulAmsFms2 & 0x0f];
- tl2 = (tl2 & 0x3f) + 15;
- uint8 ar2 = ((attDec2 >> 4) << 1) | ((attDec2 >> 4) & 1);
- uint8 dec2 = ((attDec2 << 1) | (attDec2 & 1)) & 0x1f;
- uint8 sus2r = (mulAmsFms2 & 0x20) ^ 0x20 ? (((sus2 & 0x0f) << 1) | 1) : 0;
-
- for (int i = 4; i < 16; i += 4) {
- out(0x30 + i, mul2);
- out(0x40 + i, tl2);
- out(0x50 + i, ar2);
- out(0x60 + i, dec2);
- out(0x70 + i, sus2r);
- out(0x80 + i, sus2);
- }
-
- _driver->_chanState[chan].fgAlg = data[10];
-
- uint8 alg = 5 + 2 * (data[10] & 1);
- uint8 fb = 4 * (data[10] & 0x0e);
- out(0xb0, fb | alg);
- uint8 t = mulAmsFms1 | mulAmsFms2;
- out(0xb4, (0xc0 | ((t & 0x80) >> 3) | ((t & 0x40) >> 5)));
-}
-
-void TownsMidiOutputChannel::setupEffects(int index, uint8 flags, const uint8 *effectData) {
- uint16 effectMaxLevel[] = { 0x2FF, 0x1F, 0x07, 0x3F, 0x0F, 0x0F, 0x0F, 0x03, 0x3F, 0x0F, 0x0F, 0x0F, 0x03, 0x3E, 0x1F };
- uint8 effectType[] = { 0x1D, 0x1C, 0x1B, 0x00, 0x03, 0x04, 0x07, 0x08, 0x0D, 0x10, 0x11, 0x14, 0x15, 0x1e, 0x1f, 0x00 };
-
- EffectEnvelope *s = &_effectEnvelopes[index];
- EffectDef *d = &_effectDefs[index];
-
- d->phase = 0;
- d->useModWheel = flags & 0x40;
- s->loop = flags & 0x20;
- d->loopRefresh = flags & 0x10;
- d->type = effectType[flags & 0x0f];
- s->maxLevel = effectMaxLevel[flags & 0x0f];
- s->modWheelSensitivity = 31;
- s->modWheelState = d->useModWheel ? _in->_modWheel >> 2 : 31;
-
- switch (d->type) {
- case 0:
- s->startLevel = _operator2Tl;
- break;
- case 13:
- s->startLevel = _operator1Tl;
- break;
- case 30:
- s->startLevel = 31;
- d->s->modWheelState = 0;
- break;
- case 31:
- s->startLevel = 0;
- d->s->modWheelSensitivity = 0;
- break;
- default:
- s->startLevel = getEffectStartLevel(d->type);
- break;
- }
-
- startEffect(s, effectData);
-}
-
-void TownsMidiOutputChannel::setModWheel(uint8 value) {
- if (_effectEnvelopes[0].state != kEnvReady && _effectDefs[0].type)
- _effectEnvelopes[0].modWheelState = value >> 2;
-
- if (_effectEnvelopes[1].state != kEnvReady && _effectDefs[1].type)
- _effectEnvelopes[1].modWheelState = value >> 2;
-}
-
-void TownsMidiOutputChannel::connect(TownsMidiInputChannel *chan) {
- if (!chan)
- return;
-
- _in = chan;
- _next = chan->_out;
- _prev = 0;
- chan->_out = this;
- if (_next)
- _next->_prev = this;
-}
-
-void TownsMidiOutputChannel::disconnect() {
- keyOff();
-
- TownsMidiOutputChannel *p = _prev;
- TownsMidiOutputChannel *n = _next;
-
- if (n)
- n->_prev = p;
- if (p)
- p->_next = n;
- else
- _in->_out = n;
- _in = 0;
-}
-
-bool TownsMidiOutputChannel::update() {
- if (!_in)
- return false;
-
- if (_duration) {
- _duration -= 17;
- if (_duration <= 0) {
- disconnect();
- return true;
- }
- }
-
- for (int i = 0; i < 2; i++) {
- if (_effectEnvelopes[i].state != kEnvReady)
- updateEffectGenerator(&_effectEnvelopes[i], &_effectDefs[i]);
- }
-
- return false;
-}
-
-int TownsMidiOutputChannel::checkPriority(int pri) {
- if (!_in)
- return kDisconnected;
-
- if (!_next && pri >= _in->_priority)
- return _in->_priority;
-
- return kHighPriority;
-}
-
-void TownsMidiOutputChannel::startEffect(EffectEnvelope *s, const uint8 *effectData) {
- s->state = kEnvAttacking;
- s->currentLevel = 0;
- s->modWheelLast = 31;
- s->duration = effectData[0] * 63;
- s->stateTargetLevels[0] = effectData[1];
- s->stateTargetLevels[1] = effectData[3];
- s->stateTargetLevels[2] = effectData[5];
- s->stateTargetLevels[3] = effectData[6];
- s->stateModWheelLevels[0] = effectData[2];
- s->stateModWheelLevels[1] = effectData[4];
- s->stateModWheelLevels[2] = 0;
- s->stateModWheelLevels[3] = effectData[7];
- initNextEnvelopeState(s);
-}
-
-void TownsMidiOutputChannel::updateEffectGenerator(EffectEnvelope *s, EffectDef *d) {
- uint8 f = advanceEffectEnvelope(s, d);
-
- if (f & 1) {
- switch (d->type) {
- case 0:
- _operator2Tl = s->startLevel + d->phase;
- break;
- case 13:
- _operator1Tl = s->startLevel + d->phase;
- break;
- case 30:
- d->s->modWheelState = d->phase;
- break;
- case 31:
- d->s->modWheelSensitivity = d->phase;
- break;
- default:
- break;
- }
- }
-
- if (f & 2) {
- if (d->loopRefresh)
- keyOn();
- }
-}
-
-int TownsMidiOutputChannel::advanceEffectEnvelope(EffectEnvelope *s, EffectDef *d) {
- if (s->duration) {
- s->duration -= 17;
- if (s->duration <= 0) {
- s->state = kEnvReady;
- return 0;
- }
- }
-
- int32 t = s->currentLevel + s->incrPerStep;
-
- s->incrCountRem += s->incrPerStepRem;
- if (s->incrCountRem >= s->numSteps) {
- s->incrCountRem -= s->numSteps;
- t += s->dir;
- }
-
- int retFlags = 0;
-
- if (t != s->currentLevel || (s->modWheelState != s->modWheelLast)) {
- s->currentLevel = t;
- s->modWheelLast = s->modWheelState;
- t = getEffectModLevel(t, s->modWheelState);
- if (t != d->phase) {
- d->phase = t;
- retFlags |= 1;
- }
- }
-
- if (--s->stepCounter)
- return retFlags;
-
- if (++s->state > kEnvReleasing) {
- if (!s->loop) {
- s->state = kEnvReady;
- return retFlags;
- }
- s->state = kEnvAttacking;
- retFlags |= 2;
- }
-
- initNextEnvelopeState(s);
-
- return retFlags;
-}
-
-void TownsMidiOutputChannel::initNextEnvelopeState(EffectEnvelope *s) {
- uint8 v = s->stateTargetLevels[s->state - 1];
- int32 e = _effectEnvStepTable[_driver->_operatorLevelTable[((v & 0x7f) << 5) + s->modWheelSensitivity]];
-
- if (v & 0x80)
- e = _driver->randomValue(e);
-
- if (!e)
- e = 1;
-
- s->numSteps = s->stepCounter = e;
- int32 d = 0;
-
- if (s->state != kEnvSustaining) {
- v = s->stateModWheelLevels[s->state - 1];
- e = getEffectModLevel(s->maxLevel, (v & 0x7f) - 31);
-
- if (v & 0x80)
- e = _driver->randomValue(e);
-
- if (e + s->startLevel > s->maxLevel) {
- e = s->maxLevel - s->startLevel;
- } else {
- if (e + s->startLevel < 0)
- e = -s->startLevel;
- }
-
- d = e - s->currentLevel;
- }
-
- s->incrPerStep = d / s->numSteps;
- s->dir = (d < 0) ? -1 : 1;
- d *= s->dir;
- s->incrPerStepRem = d % s->numSteps;
- s->incrCountRem = 0;
-}
-
-int16 TownsMidiOutputChannel::getEffectStartLevel(uint8 type) {
- uint8 chan = (type < 13) ? _chanMap2[_chan] : ((type < 26) ? _chanMap[_chan] : _chan);
-
- if (type == 28)
- return 15;
- else if (type == 29)
- return 383;
- else if (type > 29)
- return 0;
- else if (type > 12)
- type -= 13;
-
- const uint8 *def = &_effectDefaults[type << 2];
- uint8 res = (_driver->_chanState[chan].get(def[0] >> 5) & def[2]) >> def[1];
- if (def[3])
- res = def[3] - res;
-
- return res;
-}
-
-int TownsMidiOutputChannel::getEffectModLevel(int lvl, int mod) {
- if (mod == 0)
- return 0;
-
- if (mod == 31)
- return lvl;
-
- if (lvl > 63 || lvl < -63)
- return ((lvl + 1) * mod) >> 5;
-
- if (mod < 0) {
- if (lvl < 0)
- return _driver->_operatorLevelTable[((-lvl) << 5) - mod];
- else
- return -_driver->_operatorLevelTable[(lvl << 5) - mod];
- } else {
- if (lvl < 0)
- return -_driver->_operatorLevelTable[((-lvl) << 5) + mod];
- else
- return _driver->_operatorLevelTable[(lvl << 5) + mod];
- }
-
- return 0;
-}
-
-void TownsMidiOutputChannel::keyOn() {
- out(0x28, 0x30);
-}
-
-void TownsMidiOutputChannel::keyOff() {
- out(0x28, 0);
-}
-
-void TownsMidiOutputChannel::keyOnSetFreq(uint16 frq) {
- uint16 note = (frq << 1) >> 8;
- frq = (_freqMSB[note] << 11) | _freqLSB[note];
- out(0xa4, frq >> 8);
- out(0xa0, frq & 0xff);
- //out(0x28, 0x00);
- out(0x28, 0x30);
-}
-
-void TownsMidiOutputChannel::out(uint8 reg, uint8 val) {
- static const uint8 chanRegOffs[] = { 0, 1, 2, 0, 1, 2 };
- static const uint8 keyValOffs[] = { 0, 1, 2, 4, 5, 6 };
-
- if (reg == 0x28)
- val = (val & 0xf0) | keyValOffs[_chan];
- if (reg < 0x30)
- _driver->_intf->callback(17, 0, reg, val);
- else
- _driver->_intf->callback(17, _chan / 3, (reg & ~3) | chanRegOffs[_chan], val);
-}
-
-const uint8 TownsMidiOutputChannel::_chanMap[] = {
- 0, 1, 2, 8, 9, 10
-};
-
-const uint8 TownsMidiOutputChannel::_chanMap2[] = {
- 3, 4, 5, 11, 12, 13
-};
-
-const uint8 TownsMidiOutputChannel::_effectDefaults[] = {
- 0x40, 0x00, 0x3F, 0x3F, 0xE0, 0x02, 0x00, 0x00, 0x40, 0x06, 0xC0, 0x00,
- 0x20, 0x00, 0x0F, 0x00, 0x60, 0x04, 0xF0, 0x0F, 0x60, 0x00, 0x0F, 0x0F,
- 0x80, 0x04, 0xF0, 0x0F, 0x80, 0x00, 0x0F, 0x0F, 0xE0, 0x00, 0x03, 0x00,
- 0x20, 0x07, 0x80, 0x00, 0x20, 0x06, 0x40, 0x00, 0x20, 0x05, 0x20, 0x00,
- 0x20, 0x04, 0x10, 0x00, 0xC0, 0x00, 0x01, 0x00, 0xC0, 0x01, 0x0E, 0x00
-};
-
-const uint16 TownsMidiOutputChannel::_effectEnvStepTable[] = {
- 0x0001, 0x0002, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009,
- 0x000A, 0x000C, 0x000E, 0x0010, 0x0012, 0x0015, 0x0018, 0x001E,
- 0x0024, 0x0032, 0x0040, 0x0052, 0x0064, 0x0088, 0x00A0, 0x00C0,
- 0x00F0, 0x0114, 0x0154, 0x01CC, 0x0258, 0x035C, 0x04B0, 0x0640
-};
-
-const uint8 TownsMidiOutputChannel::_freqMSB[] = {
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
- 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
- 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
- 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
- 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
- 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
- 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
- 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
- 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x80, 0x81, 0x83, 0x85,
- 0x87, 0x88, 0x8A, 0x8C, 0x8E, 0x8F, 0x91, 0x93, 0x95, 0x96, 0x98, 0x9A,
- 0x9C, 0x9E, 0x9F, 0xA1, 0xA3, 0xA5, 0xA6, 0xA8, 0xAA, 0xAC, 0xAD, 0xAF,
- 0xB1, 0xB3, 0xB4, 0xB6, 0xB8, 0xBA, 0xBC, 0xBD, 0xBF, 0xC1, 0xC3, 0xC4,
- 0xC6, 0xC8, 0xCA, 0xCB, 0xCD, 0xCF, 0xD1, 0xD2, 0xD4, 0xD6, 0xD8, 0xDA,
- 0xDB, 0xDD, 0xDF, 0xE1, 0xE2, 0xE4, 0xE6, 0xE8, 0xE9, 0xEB, 0xED, 0xEF
-};
-
-const uint16 TownsMidiOutputChannel::_freqLSB[] = {
- 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6,
- 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x0301, 0x032F,
- 0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
- 0x055B, 0x02D6, 0x0301, 0x032F, 0x0360, 0x0393, 0x03C9, 0x0403,
- 0x0440, 0x0481, 0x04C6, 0x050E, 0x055B, 0x02D6, 0x0301, 0x032F,
- 0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
- 0x055B, 0x02D6, 0x0301, 0x032F, 0x0360, 0x0393, 0x03C9, 0x0403,
- 0x0440, 0x0481, 0x04C6, 0x050E, 0x055B, 0x02D6, 0x0301, 0x032F,
- 0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
- 0x055B, 0x02D6, 0x0301, 0x032F, 0x0360, 0x0393, 0x03C9, 0x0403,
- 0x0440, 0x0481, 0x04C6, 0x050E, 0x055B, 0x02D6, 0x0301, 0x032F,
- 0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
- 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B,
- 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B,
- 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B,
- 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B
-};
-
-TownsMidiInputChannel::TownsMidiInputChannel(MidiDriver_TOWNS *driver, int chanIndex) : MidiChannel(), _driver(driver), _out(0), _chanIndex(chanIndex),
- _priority(0), _tl(0), _transpose(0), _pitchBendFactor(0), _pitchBend(0), _sustain(0), _freqLSB(0), _detune(0), _modWheel(0), _allocated(false) {
- _instrument = new uint8[30];
- memset(_instrument, 0, 30);
-}
-
-TownsMidiInputChannel::~TownsMidiInputChannel() {
- delete[] _instrument;
-}
-
-bool TownsMidiInputChannel::allocate() {
- if (_allocated)
- return false;
- _allocated = true;
- return true;
-}
-
-void TownsMidiInputChannel::release() {
- _allocated = false;
-}
-
-void TownsMidiInputChannel::send(uint32 b) {
- _driver->send(b | _chanIndex);
-}
-
-void TownsMidiInputChannel::noteOff(byte note) {
- if (!_out)
- return;
-
- for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next) {
- if (oc->_note != note)
- continue;
-
- if (_sustain)
- oc->_sustainNoteOff = 1;
- else
- oc->disconnect();
- }
-}
-
-void TownsMidiInputChannel::noteOn(byte note, byte velocity) {
- TownsMidiOutputChannel *oc = _driver->allocateOutputChannel(_priority);
-
- if (!oc)
- return;
-
- oc->connect(this);
-
- oc->_adjustModTl = _instrument[10] & 1;
- oc->_note = note;
- oc->_sustainNoteOff = 0;
- oc->_duration = _instrument[29] * 63;
-
- oc->_operator1Tl = (_instrument[1] & 0x3f) + _driver->_operatorLevelTable[((velocity >> 1) << 5) + (_instrument[4] >> 2)];
- if (oc->_operator1Tl > 63)
- oc->_operator1Tl = 63;
-
- oc->_operator2Tl = (_instrument[6] & 0x3f) + _driver->_operatorLevelTable[((velocity >> 1) << 5) + (_instrument[9] >> 2)];
- if (oc->_operator2Tl > 63)
- oc->_operator2Tl = 63;
-
- oc->setupProgram(_instrument, oc->_adjustModTl == 1 ? _programAdjustLevel[_driver->_operatorLevelTable[(_tl >> 2) + (oc->_operator1Tl << 5)]] : oc->_operator1Tl, _programAdjustLevel[_driver->_operatorLevelTable[(_tl >> 2) + (oc->_operator2Tl << 5)]]);
- oc->noteOn(note + _transpose, _freqLSB);
-
- if (_instrument[11] & 0x80)
- oc->setupEffects(0, _instrument[11], &_instrument[12]);
- else
- oc->_effectEnvelopes[0].state = kEnvReady;
-
- if (_instrument[20] & 0x80)
- oc->setupEffects(1, _instrument[20], &_instrument[21]);
- else
- oc->_effectEnvelopes[1].state = kEnvReady;
-}
-
-void TownsMidiInputChannel::programChange(byte program) {
- // Not implemented (The loading and assignment of programs
- // is handled externally by the SCUMM engine. The programs
- // get sent via sysEx_customInstrument.)
-}
-
-void TownsMidiInputChannel::pitchBend(int16 bend) {
- _pitchBend = bend;
- _freqLSB = ((_pitchBend * _pitchBendFactor) >> 6) + _detune;
- for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next)
- oc->noteOnPitchBend(oc->_note + oc->_in->_transpose, _freqLSB);
-}
-
-void TownsMidiInputChannel::controlChange(byte control, byte value) {
- switch (control) {
- case 1:
- controlModulationWheel(value);
- break;
- case 7:
- controlVolume(value);
- break;
- case 10:
- controlPanPos(value);
- break;
- case 64:
- controlSustain(value);
- break;
- case 123:
- while (_out)
- _out->disconnect();
- break;
- default:
- break;
- }
-}
-
-void TownsMidiInputChannel::pitchBendFactor(byte value) {
- _pitchBendFactor = value;
- _freqLSB = ((_pitchBend * _pitchBendFactor) >> 6) + _detune;
- for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next)
- oc->noteOnPitchBend(oc->_note + oc->_in->_transpose, _freqLSB);
-}
-
-void TownsMidiInputChannel::priority(byte value) {
- _priority = value;
-}
-
-void TownsMidiInputChannel::sysEx_customInstrument(uint32 type, const byte *instr) {
- memcpy(_instrument, instr, 30);
-}
-
-void TownsMidiInputChannel::controlModulationWheel(byte value) {
- _modWheel = value;
- for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next)
- oc->setModWheel(value);
-}
-
-void TownsMidiInputChannel::controlVolume(byte value) {
- /* This is all done inside the imuse code
- uint16 v1 = _ctrlVolume + 1;
- uint16 v2 = value;
- if (_chanIndex != 16) {
- _ctrlVolume = value;
- v2 = _player->getEffectiveVolume();
- }
- _tl = (v1 * v2) >> 7;*/
-
- _tl = value;
-}
-
-void TownsMidiInputChannel::controlPanPos(byte value) {
- // not implemented
-}
-
-void TownsMidiInputChannel::controlSustain(byte value) {
- _sustain = value;
- if (!value)
- releasePedal();
-}
-
-void TownsMidiInputChannel::releasePedal() {
- for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next) {
- if (oc->_sustainNoteOff)
- oc->disconnect();
- }
-}
-
-const uint8 TownsMidiInputChannel::_programAdjustLevel[] = {
- 0x00, 0x04, 0x07, 0x0B, 0x0D, 0x10, 0x12, 0x14,
- 0x16, 0x18, 0x1A, 0x1B, 0x1D, 0x1E, 0x1F, 0x21,
- 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
- 0x2A, 0x2B, 0x2C, 0x2C, 0x2D, 0x2E, 0x2F, 0x2F,
- 0x30, 0x31, 0x31, 0x32, 0x33, 0x33, 0x34, 0x35,
- 0x35, 0x36, 0x36, 0x37, 0x37, 0x38, 0x38, 0x39,
- 0x39, 0x3A, 0x3A, 0x3B, 0x3B, 0x3C, 0x3C, 0x3C,
- 0x3D, 0x3D, 0x3E, 0x3E, 0x3E, 0x3F, 0x3F, 0x3F
-};
-
-MidiDriver_TOWNS::MidiDriver_TOWNS(Audio::Mixer *mixer) : _timerProc(0), _timerProcPara(0), _channels(0), _out(0),
- _baseTempo(10080), _chanState(0), _operatorLevelTable(0), _tickCounter(0), _rand(1), _allocCurPos(0), _isOpen(false) {
- // We set exteral mutex handling to true to avoid lockups in SCUMM which has its own mutex.
- _intf = new TownsAudioInterface(mixer, this, true);
-
- _channels = new TownsMidiInputChannel*[32];
- for (int i = 0; i < 32; i++)
- _channels[i] = new TownsMidiInputChannel(this, i > 8 ? (i + 1) : i);
-
- _out = new TownsMidiOutputChannel*[6];
- for (int i = 0; i < 6; i++)
- _out[i] = new TownsMidiOutputChannel(this, i);
-
- _chanState = new TownsMidiChanState[32];
-
- _operatorLevelTable = new uint8[2048];
- for (int i = 0; i < 64; i++) {
- for (int ii = 0; ii < 32; ii++)
- _operatorLevelTable[(i << 5) + ii] = ((i * (ii + 1)) >> 5) & 0xff;
- }
- for (int i = 0; i < 64; i++)
- _operatorLevelTable[i << 5] = 0;
-}
-
-MidiDriver_TOWNS::~MidiDriver_TOWNS() {
- close();
- delete _intf;
-
- if (_channels) {
- for (int i = 0; i < 32; i++)
- delete _channels[i];
- delete[] _channels;
- }
- _channels = 0;
-
- if (_out) {
- for (int i = 0; i < 6; i++)
- delete _out[i];
- delete[] _out;
- }
- _out = 0;
-
- delete[] _chanState;
- _chanState = 0;
- delete[] _operatorLevelTable;
- _operatorLevelTable = 0;
-}
-
-int MidiDriver_TOWNS::open() {
- if (_isOpen)
- return MERR_ALREADY_OPEN;
-
- if (!_intf->init())
- return MERR_CANNOT_CONNECT;
-
- _intf->callback(0);
-
- _intf->callback(21, 255, 1);
- _intf->callback(21, 0, 1);
- _intf->callback(22, 255, 221);
-
- _intf->callback(33, 8);
- _intf->setSoundEffectChanMask(~0x3f);
-
- _allocCurPos = 0;
-
- _isOpen = true;
-
- return 0;
-}
-
-void MidiDriver_TOWNS::close() {
- if (!_isOpen)
- return;
-
- _isOpen = false;
-
- setTimerCallback(0, 0);
- g_system->delayMillis(20);
-}
-
-void MidiDriver_TOWNS::send(uint32 b) {
- if (!_isOpen)
- return;
-
- byte param2 = (b >> 16) & 0xFF;
- byte param1 = (b >> 8) & 0xFF;
- byte cmd = b & 0xF0;
-
- TownsMidiInputChannel *c = _channels[b & 0x0F];
-
- switch (cmd) {
- case 0x80:
- c->noteOff(param1);
- break;
- case 0x90:
- if (param2)
- c->noteOn(param1, param2);
- else
- c->noteOff(param1);
- break;
- case 0xB0:
- c->controlChange(param1, param2);
- break;
- case 0xC0:
- c->programChange(param1);
- break;
- case 0xE0:
- c->pitchBend((param1 | (param2 << 7)) - 0x2000);
- break;
- case 0xF0:
- warning("MidiDriver_TOWNS: Receiving SysEx command on a send() call");
- break;
-
- default:
- break;
- }
-}
-
-void MidiDriver_TOWNS::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
- _timerProc = timer_proc;
- _timerProcPara = timer_param;
-}
-
-uint32 MidiDriver_TOWNS::getBaseTempo() {
- return _baseTempo;
-}
-
-MidiChannel *MidiDriver_TOWNS::allocateChannel() {
- if (!_isOpen)
- return 0;
-
- for (int i = 0; i < 32; ++i) {
- TownsMidiInputChannel *chan = _channels[i];
- if (chan->allocate())
- return chan;
- }
-
- return 0;
-}
-
-MidiChannel *MidiDriver_TOWNS::getPercussionChannel() {
- return 0;
-}
-
-void MidiDriver_TOWNS::timerCallback(int timerId) {
- if (!_isOpen)
- return;
-
- switch (timerId) {
- case 1:
- updateParser();
- updateOutputChannels();
- break;
- default:
- break;
- }
-}
-
-void MidiDriver_TOWNS::updateParser() {
- if (_timerProc)
- _timerProc(_timerProcPara);
-}
-
-void MidiDriver_TOWNS::updateOutputChannels() {
- _tickCounter += _baseTempo;
- while (_tickCounter >= 16667) {
- _tickCounter -= 16667;
- for (int i = 0; i < 6; i++) {
- if (_out[i]->update())
- return;
- }
- }
-}
-
-TownsMidiOutputChannel *MidiDriver_TOWNS::allocateOutputChannel(uint8 pri) {
- TownsMidiOutputChannel *res = 0;
-
- for (int i = 0; i < 6; i++) {
- if (++_allocCurPos == 6)
- _allocCurPos = 0;
-
- int s = _out[_allocCurPos]->checkPriority(pri);
- if (s == TownsMidiOutputChannel::kDisconnected)
- return _out[_allocCurPos];
-
- if (s != TownsMidiOutputChannel::kHighPriority) {
- pri = s;
- res = _out[_allocCurPos];
- }
- }
-
- if (res)
- res->disconnect();
-
- return res;
-}
-
-int MidiDriver_TOWNS::randomValue(int para) {
- _rand = (_rand & 1) ? (_rand >> 1) ^ 0xb8 : (_rand >> 1);
- return (_rand * para) >> 8;
-}
diff --git a/audio/softsynth/fmtowns_pc98/towns_midi.h b/audio/softsynth/fmtowns_pc98/towns_midi.h
deleted file mode 100644
index 1143dba..0000000
--- a/audio/softsynth/fmtowns_pc98/towns_midi.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#ifndef TOWNS_MIDI_H
-#define TOWNS_MIDI_H
-
-#include "audio/softsynth/fmtowns_pc98/towns_audio.h"
-#include "audio/mididrv.h"
-
-
-class TownsMidiOutputChannel;
-class TownsMidiInputChannel;
-class TownsMidiChanState;
-
-class MidiDriver_TOWNS : public MidiDriver, public TownsAudioInterfacePluginDriver {
-friend class TownsMidiInputChannel;
-friend class TownsMidiOutputChannel;
-public:
- MidiDriver_TOWNS(Audio::Mixer *mixer);
- ~MidiDriver_TOWNS();
-
- int open();
- bool isOpen() const { return _isOpen; }
- void close();
-
- void send(uint32 b);
-
- void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc);
-
- uint32 getBaseTempo();
- MidiChannel *allocateChannel();
- MidiChannel *getPercussionChannel();
-
- void timerCallback(int timerId);
-
-private:
- void updateParser();
- void updateOutputChannels();
-
- TownsMidiOutputChannel *allocateOutputChannel(uint8 pri);
-
- int randomValue(int para);
-
- TownsMidiInputChannel **_channels;
- TownsMidiOutputChannel **_out;
- TownsMidiChanState *_chanState;
-
- Common::TimerManager::TimerProc _timerProc;
- void *_timerProcPara;
-
- TownsAudioInterface *_intf;
-
- uint32 _tickCounter;
- uint8 _allocCurPos;
- uint8 _rand;
-
- bool _isOpen;
-
- uint8 *_operatorLevelTable;
-
- const uint16 _baseTempo;
-};
-
-#endif
diff --git a/audio/softsynth/fmtowns_pc98/towns_pc98_plugins.cpp b/audio/softsynth/fmtowns_pc98/towns_pc98_plugins.cpp
index 194bfc4..4103484 100644
--- a/audio/softsynth/fmtowns_pc98/towns_pc98_plugins.cpp
+++ b/audio/softsynth/fmtowns_pc98/towns_pc98_plugins.cpp
@@ -20,7 +20,6 @@
*
*/
-#include "audio/softsynth/fmtowns_pc98/towns_midi.h"
#include "audio/musicplugin.h"
#include "common/translation.h"
#include "common/error.h"
@@ -48,8 +47,8 @@ MusicDevices TownsEmuMusicPlugin::getDevices() const {
}
Common::Error TownsEmuMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const {
- *mididriver = new MidiDriver_TOWNS(g_system->getMixer());
- return Common::kNoError;
+ *mididriver = 0;
+ return Common::kUnknownError;
}
class PC98EmuMusicPlugin : public MusicPluginObject {
@@ -73,8 +72,8 @@ MusicDevices PC98EmuMusicPlugin::getDevices() const {
}
Common::Error PC98EmuMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const {
- //*mididriver = /**/
- return Common::kNoError;
+ *mididriver = 0;
+ return Common::kUnknownError;
}
//#if PLUGIN_ENABLED_DYNAMIC(TOWNS)
diff --git a/engines/scumm/imuse/drivers/fmtowns.cpp b/engines/scumm/imuse/drivers/fmtowns.cpp
new file mode 100644
index 0000000..37765e0
--- /dev/null
+++ b/engines/scumm/imuse/drivers/fmtowns.cpp
@@ -0,0 +1,1029 @@
+/* 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 "engines/scumm/imuse/drivers/fmtowns.h"
+#include "audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h"
+#include "common/textconsole.h"
+#include "common/system.h"
+
+class TownsMidiOutputChannel {
+friend class TownsMidiInputChannel;
+public:
+ TownsMidiOutputChannel(MidiDriver_TOWNS *driver, int chanId);
+ ~TownsMidiOutputChannel();
+
+ void noteOn(uint8 msb, uint16 lsb);
+ void noteOnPitchBend(uint8 msb, uint16 lsb);
+ void setupProgram(const uint8 *data, uint8 mLevelPara, uint8 tLevelPara);
+ void setupEffects(int index, uint8 flags, const uint8 *effectData);
+ void setModWheel(uint8 value);
+
+ void connect(TownsMidiInputChannel *chan);
+ void disconnect();
+
+ bool update();
+
+ enum CheckPriorityStatus {
+ kDisconnected = -2,
+ kHighPriority = -1
+ };
+
+ int checkPriority(int pri);
+
+private:
+ struct EffectEnvelope {
+ uint8 state;
+ int32 currentLevel;
+ int32 duration;
+ int32 maxLevel;
+ int32 startLevel;
+ uint8 loop;
+ uint8 stateTargetLevels[4];
+ uint8 stateModWheelLevels[4];
+ int8 modWheelSensitivity;
+ int8 modWheelState;
+ int8 modWheelLast;
+ uint16 numSteps;
+ uint32 stepCounter;
+ int32 incrPerStep;
+ int8 dir;
+ uint32 incrPerStepRem;
+ uint32 incrCountRem;
+ } *_effectEnvelopes;
+
+ struct EffectDef {
+ int32 phase;
+ uint8 type;
+ uint8 useModWheel;
+ uint8 loopRefresh;
+ EffectEnvelope *s;
+ } *_effectDefs;
+
+ void startEffect(EffectEnvelope *s, const uint8 *effectData);
+ void updateEffectGenerator(EffectEnvelope *s, EffectDef *d);
+ int advanceEffectEnvelope(EffectEnvelope *s, EffectDef *d);
+ void initNextEnvelopeState(EffectEnvelope *s);
+ int16 getEffectStartLevel(uint8 type);
+ int getEffectModLevel(int lvl, int mod);
+
+ void keyOn();
+ void keyOff();
+ void keyOnSetFreq(uint16 frq);
+ void out(uint8 reg, uint8 val);
+
+ TownsMidiInputChannel *_in;
+ TownsMidiOutputChannel *_prev;
+ TownsMidiOutputChannel *_next;
+ uint8 _adjustModTl;
+ uint8 _chan;
+ uint8 _note;
+ uint8 _operator2Tl;
+ uint8 _operator1Tl;
+ uint8 _sustainNoteOff;
+ int16 _duration;
+
+ uint16 _freq;
+ int16 _freqAdjust;
+
+ MidiDriver_TOWNS *_driver;
+
+ static const uint8 _chanMap[];
+ static const uint8 _chanMap2[];
+ static const uint8 _effectDefaults[];
+ static const uint16 _effectEnvStepTable[];
+ static const uint8 _freqMSB[];
+ static const uint16 _freqLSB[];
+};
+
+class TownsMidiInputChannel : public MidiChannel {
+friend class TownsMidiOutputChannel;
+public:
+ TownsMidiInputChannel(MidiDriver_TOWNS *driver, int chanIndex);
+ ~TownsMidiInputChannel();
+
+ MidiDriver *device() { return _driver; }
+ byte getNumber() { return _chanIndex; }
+ bool allocate();
+ void release();
+
+ void send(uint32 b);
+
+ void noteOff(byte note);
+ void noteOn(byte note, byte velocity);
+ void programChange(byte program);
+ void pitchBend(int16 bend);
+ void controlChange(byte control, byte value);
+ void pitchBendFactor(byte value);
+ void priority(byte value);
+ void sysEx_customInstrument(uint32 type, const byte *instr);
+
+private:
+ void controlModulationWheel(byte value);
+ void controlVolume(byte value);
+ void controlPanPos(byte value);
+ void controlSustain(byte value);
+
+ void releasePedal();
+
+ TownsMidiOutputChannel *_out;
+
+ uint8 *_instrument;
+ uint8 _chanIndex;
+ uint8 _priority;
+ uint8 _tl;
+ int8 _transpose;
+ int8 _detune;
+ int8 _modWheel;
+ uint8 _sustain;
+ uint8 _pitchBendFactor;
+ int16 _pitchBend;
+ uint16 _freqLSB;
+
+ bool _allocated;
+
+ MidiDriver_TOWNS *_driver;
+
+ static const uint8 _programAdjustLevel[];
+};
+
+class TownsMidiChanState {
+public:
+ TownsMidiChanState();
+ ~TownsMidiChanState() {}
+ uint8 get(uint8 type);
+
+ uint8 unk1;
+ uint8 mulAmsFms;
+ uint8 tl;
+ uint8 attDec;
+ uint8 sus;
+ uint8 fgAlg;
+ uint8 unk2;
+};
+
+TownsMidiChanState::TownsMidiChanState() {
+ unk1 = mulAmsFms = tl = attDec = sus = fgAlg = unk2 = 0;
+}
+
+uint8 TownsMidiChanState::get(uint8 type) {
+ switch (type) {
+ case 0:
+ return unk1;
+ case 1:
+ return mulAmsFms;
+ case 2:
+ return tl;
+ case 3:
+ return attDec;
+ case 4:
+ return sus;
+ case 5:
+ return fgAlg;
+ case 6:
+ return unk2;
+ default:
+ break;
+ }
+ return 0;
+}
+
+TownsMidiOutputChannel::TownsMidiOutputChannel(MidiDriver_TOWNS *driver, int chanIndex) : _driver(driver), _chan(chanIndex),
+ _in(0), _prev(0), _next(0), _adjustModTl(0), _operator2Tl(0), _note(0), _operator1Tl(0), _sustainNoteOff(0), _duration(0), _freq(0), _freqAdjust(0) {
+ _effectEnvelopes = new EffectEnvelope[2];
+ _effectDefs = new EffectDef[2];
+
+ memset(_effectEnvelopes, 0, 2 * sizeof(EffectEnvelope));
+ memset(_effectDefs, 0, 2 * sizeof(EffectDef));
+ _effectDefs[0].s = &_effectEnvelopes[1];
+ _effectDefs[1].s = &_effectEnvelopes[0];
+}
+
+TownsMidiOutputChannel::~TownsMidiOutputChannel() {
+ delete[] _effectEnvelopes;
+ delete[] _effectDefs;
+}
+
+void TownsMidiOutputChannel::noteOn(uint8 msb, uint16 lsb) {
+ _freq = (msb << 7) + lsb;
+ _freqAdjust = 0;
+ keyOnSetFreq(_freq);
+}
+
+void TownsMidiOutputChannel::noteOnPitchBend(uint8 msb, uint16 lsb) {
+ _freq = (msb << 7) + lsb;
+ keyOnSetFreq(_freq + _freqAdjust);
+}
+
+void TownsMidiOutputChannel::setupProgram(const uint8 *data, uint8 mLevelPara, uint8 tLevelPara) {
+ // This driver uses only 2 operators and 2 algorithms (algorithm 5 and 7),
+ // since it is just a modified AdLib driver. It also uses AdLib programs.
+ // There are no FM-TOWNS specific programs. This is the reason for the low quality of the FM-TOWNS
+ // music (unsuitable data is just forced into the wrong audio device).
+
+ static const uint8 mul[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 12, 12, 15, 15 };
+ uint8 chan = _chanMap[_chan];
+
+ uint8 mulAmsFms1 = _driver->_chanState[chan].mulAmsFms = data[0];
+ uint8 tl1 = _driver->_chanState[chan].tl = (data[1] | 0x3f) - mLevelPara;
+ uint8 attDec1 = _driver->_chanState[chan].attDec = ~data[2];
+ uint8 sus1 = _driver->_chanState[chan].sus = ~data[3];
+ _driver->_chanState[chan].unk2 = data[4];
+ chan += 3;
+
+ out(0x30, mul[mulAmsFms1 & 0x0f]);
+ out(0x40, (tl1 & 0x3f) + 15);
+ out(0x50, ((attDec1 >> 4) << 1) | ((attDec1 >> 4) & 1));
+ out(0x60, ((attDec1 << 1) | (attDec1 & 1)) & 0x1f);
+ out(0x70, (mulAmsFms1 & 0x20) ^ 0x20 ? (((sus1 & 0x0f) << 1) | 1) : 0);
+ out(0x80, sus1);
+
+ uint8 mulAmsFms2 = _driver->_chanState[chan].mulAmsFms = data[5];
+ uint8 tl2 = _driver->_chanState[chan].tl = (data[6] | 0x3f) - tLevelPara;
+ uint8 attDec2 = _driver->_chanState[chan].attDec = ~data[7];
+ uint8 sus2 = _driver->_chanState[chan].sus = ~data[8];
+ _driver->_chanState[chan].unk2 = data[9];
+
+ uint8 mul2 = mul[mulAmsFms2 & 0x0f];
+ tl2 = (tl2 & 0x3f) + 15;
+ uint8 ar2 = ((attDec2 >> 4) << 1) | ((attDec2 >> 4) & 1);
+ uint8 dec2 = ((attDec2 << 1) | (attDec2 & 1)) & 0x1f;
+ uint8 sus2r = (mulAmsFms2 & 0x20) ^ 0x20 ? (((sus2 & 0x0f) << 1) | 1) : 0;
+
+ for (int i = 4; i < 16; i += 4) {
+ out(0x30 + i, mul2);
+ out(0x40 + i, tl2);
+ out(0x50 + i, ar2);
+ out(0x60 + i, dec2);
+ out(0x70 + i, sus2r);
+ out(0x80 + i, sus2);
+ }
+
+ _driver->_chanState[chan].fgAlg = data[10];
+
+ uint8 alg = 5 + 2 * (data[10] & 1);
+ uint8 fb = 4 * (data[10] & 0x0e);
+ out(0xb0, fb | alg);
+ uint8 t = mulAmsFms1 | mulAmsFms2;
+ out(0xb4, (0xc0 | ((t & 0x80) >> 3) | ((t & 0x40) >> 5)));
+}
+
+void TownsMidiOutputChannel::setupEffects(int index, uint8 flags, const uint8 *effectData) {
+ uint16 effectMaxLevel[] = { 0x2FF, 0x1F, 0x07, 0x3F, 0x0F, 0x0F, 0x0F, 0x03, 0x3F, 0x0F, 0x0F, 0x0F, 0x03, 0x3E, 0x1F };
+ uint8 effectType[] = { 0x1D, 0x1C, 0x1B, 0x00, 0x03, 0x04, 0x07, 0x08, 0x0D, 0x10, 0x11, 0x14, 0x15, 0x1e, 0x1f, 0x00 };
+
+ EffectEnvelope *s = &_effectEnvelopes[index];
+ EffectDef *d = &_effectDefs[index];
+
+ d->phase = 0;
+ d->useModWheel = flags & 0x40;
+ s->loop = flags & 0x20;
+ d->loopRefresh = flags & 0x10;
+ d->type = effectType[flags & 0x0f];
+ s->maxLevel = effectMaxLevel[flags & 0x0f];
+ s->modWheelSensitivity = 31;
+ s->modWheelState = d->useModWheel ? _in->_modWheel >> 2 : 31;
+
+ switch (d->type) {
+ case 0:
+ s->startLevel = _operator2Tl;
+ break;
+ case 13:
+ s->startLevel = _operator1Tl;
+ break;
+ case 30:
+ s->startLevel = 31;
+ d->s->modWheelState = 0;
+ break;
+ case 31:
+ s->startLevel = 0;
+ d->s->modWheelSensitivity = 0;
+ break;
+ default:
+ s->startLevel = getEffectStartLevel(d->type);
+ break;
+ }
+
+ startEffect(s, effectData);
+}
+
+void TownsMidiOutputChannel::setModWheel(uint8 value) {
+ if (_effectEnvelopes[0].state != kEnvReady && _effectDefs[0].type)
+ _effectEnvelopes[0].modWheelState = value >> 2;
+
+ if (_effectEnvelopes[1].state != kEnvReady && _effectDefs[1].type)
+ _effectEnvelopes[1].modWheelState = value >> 2;
+}
+
+void TownsMidiOutputChannel::connect(TownsMidiInputChannel *chan) {
+ if (!chan)
+ return;
+
+ _in = chan;
+ _next = chan->_out;
+ _prev = 0;
+ chan->_out = this;
+ if (_next)
+ _next->_prev = this;
+}
+
+void TownsMidiOutputChannel::disconnect() {
+ keyOff();
+
+ TownsMidiOutputChannel *p = _prev;
+ TownsMidiOutputChannel *n = _next;
+
+ if (n)
+ n->_prev = p;
+ if (p)
+ p->_next = n;
+ else
+ _in->_out = n;
+ _in = 0;
+}
+
+bool TownsMidiOutputChannel::update() {
+ if (!_in)
+ return false;
+
+ if (_duration) {
+ _duration -= 17;
+ if (_duration <= 0) {
+ disconnect();
+ return true;
+ }
+ }
+
+ for (int i = 0; i < 2; i++) {
+ if (_effectEnvelopes[i].state != kEnvReady)
+ updateEffectGenerator(&_effectEnvelopes[i], &_effectDefs[i]);
+ }
+
+ return false;
+}
+
+int TownsMidiOutputChannel::checkPriority(int pri) {
+ if (!_in)
+ return kDisconnected;
+
+ if (!_next && pri >= _in->_priority)
+ return _in->_priority;
+
+ return kHighPriority;
+}
+
+void TownsMidiOutputChannel::startEffect(EffectEnvelope *s, const uint8 *effectData) {
+ s->state = kEnvAttacking;
+ s->currentLevel = 0;
+ s->modWheelLast = 31;
+ s->duration = effectData[0] * 63;
+ s->stateTargetLevels[0] = effectData[1];
+ s->stateTargetLevels[1] = effectData[3];
+ s->stateTargetLevels[2] = effectData[5];
+ s->stateTargetLevels[3] = effectData[6];
+ s->stateModWheelLevels[0] = effectData[2];
+ s->stateModWheelLevels[1] = effectData[4];
+ s->stateModWheelLevels[2] = 0;
+ s->stateModWheelLevels[3] = effectData[7];
+ initNextEnvelopeState(s);
+}
+
+void TownsMidiOutputChannel::updateEffectGenerator(EffectEnvelope *s, EffectDef *d) {
+ uint8 f = advanceEffectEnvelope(s, d);
+
+ if (f & 1) {
+ switch (d->type) {
+ case 0:
+ _operator2Tl = s->startLevel + d->phase;
+ break;
+ case 13:
+ _operator1Tl = s->startLevel + d->phase;
+ break;
+ case 30:
+ d->s->modWheelState = d->phase;
+ break;
+ case 31:
+ d->s->modWheelSensitivity = d->phase;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (f & 2) {
+ if (d->loopRefresh)
+ keyOn();
+ }
+}
+
+int TownsMidiOutputChannel::advanceEffectEnvelope(EffectEnvelope *s, EffectDef *d) {
+ if (s->duration) {
+ s->duration -= 17;
+ if (s->duration <= 0) {
+ s->state = kEnvReady;
+ return 0;
+ }
+ }
+
+ int32 t = s->currentLevel + s->incrPerStep;
+
+ s->incrCountRem += s->incrPerStepRem;
+ if (s->incrCountRem >= s->numSteps) {
+ s->incrCountRem -= s->numSteps;
+ t += s->dir;
+ }
+
+ int retFlags = 0;
+
+ if (t != s->currentLevel || (s->modWheelState != s->modWheelLast)) {
+ s->currentLevel = t;
+ s->modWheelLast = s->modWheelState;
+ t = getEffectModLevel(t, s->modWheelState);
+ if (t != d->phase) {
+ d->phase = t;
+ retFlags |= 1;
+ }
+ }
+
+ if (--s->stepCounter)
+ return retFlags;
+
+ if (++s->state > kEnvReleasing) {
+ if (!s->loop) {
+ s->state = kEnvReady;
+ return retFlags;
+ }
+ s->state = kEnvAttacking;
+ retFlags |= 2;
+ }
+
+ initNextEnvelopeState(s);
+
+ return retFlags;
+}
+
+void TownsMidiOutputChannel::initNextEnvelopeState(EffectEnvelope *s) {
+ uint8 v = s->stateTargetLevels[s->state - 1];
+ int32 e = _effectEnvStepTable[_driver->_operatorLevelTable[((v & 0x7f) << 5) + s->modWheelSensitivity]];
+
+ if (v & 0x80)
+ e = _driver->randomValue(e);
+
+ if (!e)
+ e = 1;
+
+ s->numSteps = s->stepCounter = e;
+ int32 d = 0;
+
+ if (s->state != kEnvSustaining) {
+ v = s->stateModWheelLevels[s->state - 1];
+ e = getEffectModLevel(s->maxLevel, (v & 0x7f) - 31);
+
+ if (v & 0x80)
+ e = _driver->randomValue(e);
+
+ if (e + s->startLevel > s->maxLevel) {
+ e = s->maxLevel - s->startLevel;
+ } else {
+ if (e + s->startLevel < 0)
+ e = -s->startLevel;
+ }
+
+ d = e - s->currentLevel;
+ }
+
+ s->incrPerStep = d / s->numSteps;
+ s->dir = (d < 0) ? -1 : 1;
+ d *= s->dir;
+ s->incrPerStepRem = d % s->numSteps;
+ s->incrCountRem = 0;
+}
+
+int16 TownsMidiOutputChannel::getEffectStartLevel(uint8 type) {
+ uint8 chan = (type < 13) ? _chanMap2[_chan] : ((type < 26) ? _chanMap[_chan] : _chan);
+
+ if (type == 28)
+ return 15;
+ else if (type == 29)
+ return 383;
+ else if (type > 29)
+ return 0;
+ else if (type > 12)
+ type -= 13;
+
+ const uint8 *def = &_effectDefaults[type << 2];
+ uint8 res = (_driver->_chanState[chan].get(def[0] >> 5) & def[2]) >> def[1];
+ if (def[3])
+ res = def[3] - res;
+
+ return res;
+}
+
+int TownsMidiOutputChannel::getEffectModLevel(int lvl, int mod) {
+ if (mod == 0)
+ return 0;
+
+ if (mod == 31)
+ return lvl;
+
+ if (lvl > 63 || lvl < -63)
+ return ((lvl + 1) * mod) >> 5;
+
+ if (mod < 0) {
+ if (lvl < 0)
+ return _driver->_operatorLevelTable[((-lvl) << 5) - mod];
+ else
+ return -_driver->_operatorLevelTable[(lvl << 5) - mod];
+ } else {
+ if (lvl < 0)
+ return -_driver->_operatorLevelTable[((-lvl) << 5) + mod];
+ else
+ return _driver->_operatorLevelTable[(lvl << 5) + mod];
+ }
+
+ return 0;
+}
+
+void TownsMidiOutputChannel::keyOn() {
+ out(0x28, 0x30);
+}
+
+void TownsMidiOutputChannel::keyOff() {
+ out(0x28, 0);
+}
+
+void TownsMidiOutputChannel::keyOnSetFreq(uint16 frq) {
+ uint16 note = (frq << 1) >> 8;
+ frq = (_freqMSB[note] << 11) | _freqLSB[note];
+ out(0xa4, frq >> 8);
+ out(0xa0, frq & 0xff);
+ //out(0x28, 0x00);
+ out(0x28, 0x30);
+}
+
+void TownsMidiOutputChannel::out(uint8 reg, uint8 val) {
+ static const uint8 chanRegOffs[] = { 0, 1, 2, 0, 1, 2 };
+ static const uint8 keyValOffs[] = { 0, 1, 2, 4, 5, 6 };
+
+ if (reg == 0x28)
+ val = (val & 0xf0) | keyValOffs[_chan];
+ if (reg < 0x30)
+ _driver->_intf->callback(17, 0, reg, val);
+ else
+ _driver->_intf->callback(17, _chan / 3, (reg & ~3) | chanRegOffs[_chan], val);
+}
+
+const uint8 TownsMidiOutputChannel::_chanMap[] = {
+ 0, 1, 2, 8, 9, 10
+};
+
+const uint8 TownsMidiOutputChannel::_chanMap2[] = {
+ 3, 4, 5, 11, 12, 13
+};
+
+const uint8 TownsMidiOutputChannel::_effectDefaults[] = {
+ 0x40, 0x00, 0x3F, 0x3F, 0xE0, 0x02, 0x00, 0x00, 0x40, 0x06, 0xC0, 0x00,
+ 0x20, 0x00, 0x0F, 0x00, 0x60, 0x04, 0xF0, 0x0F, 0x60, 0x00, 0x0F, 0x0F,
+ 0x80, 0x04, 0xF0, 0x0F, 0x80, 0x00, 0x0F, 0x0F, 0xE0, 0x00, 0x03, 0x00,
+ 0x20, 0x07, 0x80, 0x00, 0x20, 0x06, 0x40, 0x00, 0x20, 0x05, 0x20, 0x00,
+ 0x20, 0x04, 0x10, 0x00, 0xC0, 0x00, 0x01, 0x00, 0xC0, 0x01, 0x0E, 0x00
+};
+
+const uint16 TownsMidiOutputChannel::_effectEnvStepTable[] = {
+ 0x0001, 0x0002, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009,
+ 0x000A, 0x000C, 0x000E, 0x0010, 0x0012, 0x0015, 0x0018, 0x001E,
+ 0x0024, 0x0032, 0x0040, 0x0052, 0x0064, 0x0088, 0x00A0, 0x00C0,
+ 0x00F0, 0x0114, 0x0154, 0x01CC, 0x0258, 0x035C, 0x04B0, 0x0640
+};
+
+const uint8 TownsMidiOutputChannel::_freqMSB[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x80, 0x81, 0x83, 0x85,
+ 0x87, 0x88, 0x8A, 0x8C, 0x8E, 0x8F, 0x91, 0x93, 0x95, 0x96, 0x98, 0x9A,
+ 0x9C, 0x9E, 0x9F, 0xA1, 0xA3, 0xA5, 0xA6, 0xA8, 0xAA, 0xAC, 0xAD, 0xAF,
+ 0xB1, 0xB3, 0xB4, 0xB6, 0xB8, 0xBA, 0xBC, 0xBD, 0xBF, 0xC1, 0xC3, 0xC4,
+ 0xC6, 0xC8, 0xCA, 0xCB, 0xCD, 0xCF, 0xD1, 0xD2, 0xD4, 0xD6, 0xD8, 0xDA,
+ 0xDB, 0xDD, 0xDF, 0xE1, 0xE2, 0xE4, 0xE6, 0xE8, 0xE9, 0xEB, 0xED, 0xEF
+};
+
+const uint16 TownsMidiOutputChannel::_freqLSB[] = {
+ 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6,
+ 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x0301, 0x032F,
+ 0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
+ 0x055B, 0x02D6, 0x0301, 0x032F, 0x0360, 0x0393, 0x03C9, 0x0403,
+ 0x0440, 0x0481, 0x04C6, 0x050E, 0x055B, 0x02D6, 0x0301, 0x032F,
+ 0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
+ 0x055B, 0x02D6, 0x0301, 0x032F, 0x0360, 0x0393, 0x03C9, 0x0403,
+ 0x0440, 0x0481, 0x04C6, 0x050E, 0x055B, 0x02D6, 0x0301, 0x032F,
+ 0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
+ 0x055B, 0x02D6, 0x0301, 0x032F, 0x0360, 0x0393, 0x03C9, 0x0403,
+ 0x0440, 0x0481, 0x04C6, 0x050E, 0x055B, 0x02D6, 0x0301, 0x032F,
+ 0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
+ 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B,
+ 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B,
+ 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B,
+ 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B
+};
+
+TownsMidiInputChannel::TownsMidiInputChannel(MidiDriver_TOWNS *driver, int chanIndex) : MidiChannel(), _driver(driver), _out(0), _chanIndex(chanIndex),
+ _priority(0), _tl(0), _transpose(0), _pitchBendFactor(0), _pitchBend(0), _sustain(0), _freqLSB(0), _detune(0), _modWheel(0), _allocated(false) {
+ _instrument = new uint8[30];
+ memset(_instrument, 0, 30);
+}
+
+TownsMidiInputChannel::~TownsMidiInputChannel() {
+ delete[] _instrument;
+}
+
+bool TownsMidiInputChannel::allocate() {
+ if (_allocated)
+ return false;
+ _allocated = true;
+ return true;
+}
+
+void TownsMidiInputChannel::release() {
+ _allocated = false;
+}
+
+void TownsMidiInputChannel::send(uint32 b) {
+ _driver->send(b | _chanIndex);
+}
+
+void TownsMidiInputChannel::noteOff(byte note) {
+ if (!_out)
+ return;
+
+ for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next) {
+ if (oc->_note != note)
+ continue;
+
+ if (_sustain)
+ oc->_sustainNoteOff = 1;
+ else
+ oc->disconnect();
+ }
+}
+
+void TownsMidiInputChannel::noteOn(byte note, byte velocity) {
+ TownsMidiOutputChannel *oc = _driver->allocateOutputChannel(_priority);
+
+ if (!oc)
+ return;
+
+ oc->connect(this);
+
+ oc->_adjustModTl = _instrument[10] & 1;
+ oc->_note = note;
+ oc->_sustainNoteOff = 0;
+ oc->_duration = _instrument[29] * 63;
+
+ oc->_operator1Tl = (_instrument[1] & 0x3f) + _driver->_operatorLevelTable[((velocity >> 1) << 5) + (_instrument[4] >> 2)];
+ if (oc->_operator1Tl > 63)
+ oc->_operator1Tl = 63;
+
+ oc->_operator2Tl = (_instrument[6] & 0x3f) + _driver->_operatorLevelTable[((velocity >> 1) << 5) + (_instrument[9] >> 2)];
+ if (oc->_operator2Tl > 63)
+ oc->_operator2Tl = 63;
+
+ oc->setupProgram(_instrument, oc->_adjustModTl == 1 ? _programAdjustLevel[_driver->_operatorLevelTable[(_tl >> 2) + (oc->_operator1Tl << 5)]] : oc->_operator1Tl, _programAdjustLevel[_driver->_operatorLevelTable[(_tl >> 2) + (oc->_operator2Tl << 5)]]);
+ oc->noteOn(note + _transpose, _freqLSB);
+
+ if (_instrument[11] & 0x80)
+ oc->setupEffects(0, _instrument[11], &_instrument[12]);
+ else
+ oc->_effectEnvelopes[0].state = kEnvReady;
+
+ if (_instrument[20] & 0x80)
+ oc->setupEffects(1, _instrument[20], &_instrument[21]);
+ else
+ oc->_effectEnvelopes[1].state = kEnvReady;
+}
+
+void TownsMidiInputChannel::programChange(byte program) {
+ // Not implemented (The loading and assignment of programs
+ // is handled externally by the SCUMM engine. The programs
+ // get sent via sysEx_customInstrument.)
+}
+
+void TownsMidiInputChannel::pitchBend(int16 bend) {
+ _pitchBend = bend;
+ _freqLSB = ((_pitchBend * _pitchBendFactor) >> 6) + _detune;
+ for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next)
+ oc->noteOnPitchBend(oc->_note + oc->_in->_transpose, _freqLSB);
+}
+
+void TownsMidiInputChannel::controlChange(byte control, byte value) {
+ switch (control) {
+ case 1:
+ controlModulationWheel(value);
+ break;
+ case 7:
+ controlVolume(value);
+ break;
+ case 10:
+ controlPanPos(value);
+ break;
+ case 64:
+ controlSustain(value);
+ break;
+ case 123:
+ while (_out)
+ _out->disconnect();
+ break;
+ default:
+ break;
+ }
+}
+
+void TownsMidiInputChannel::pitchBendFactor(byte value) {
+ _pitchBendFactor = value;
+ _freqLSB = ((_pitchBend * _pitchBendFactor) >> 6) + _detune;
+ for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next)
+ oc->noteOnPitchBend(oc->_note + oc->_in->_transpose, _freqLSB);
+}
+
+void TownsMidiInputChannel::priority(byte value) {
+ _priority = value;
+}
+
+void TownsMidiInputChannel::sysEx_customInstrument(uint32 type, const byte *instr) {
+ memcpy(_instrument, instr, 30);
+}
+
+void TownsMidiInputChannel::controlModulationWheel(byte value) {
+ _modWheel = value;
+ for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next)
+ oc->setModWheel(value);
+}
+
+void TownsMidiInputChannel::controlVolume(byte value) {
+ /* This is all done inside the imuse code
+ uint16 v1 = _ctrlVolume + 1;
+ uint16 v2 = value;
+ if (_chanIndex != 16) {
+ _ctrlVolume = value;
+ v2 = _player->getEffectiveVolume();
+ }
+ _tl = (v1 * v2) >> 7;*/
+
+ _tl = value;
+}
+
+void TownsMidiInputChannel::controlPanPos(byte value) {
+ // not implemented
+}
+
+void TownsMidiInputChannel::controlSustain(byte value) {
+ _sustain = value;
+ if (!value)
+ releasePedal();
+}
+
+void TownsMidiInputChannel::releasePedal() {
+ for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next) {
+ if (oc->_sustainNoteOff)
+ oc->disconnect();
+ }
+}
+
+const uint8 TownsMidiInputChannel::_programAdjustLevel[] = {
+ 0x00, 0x04, 0x07, 0x0B, 0x0D, 0x10, 0x12, 0x14,
+ 0x16, 0x18, 0x1A, 0x1B, 0x1D, 0x1E, 0x1F, 0x21,
+ 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
+ 0x2A, 0x2B, 0x2C, 0x2C, 0x2D, 0x2E, 0x2F, 0x2F,
+ 0x30, 0x31, 0x31, 0x32, 0x33, 0x33, 0x34, 0x35,
+ 0x35, 0x36, 0x36, 0x37, 0x37, 0x38, 0x38, 0x39,
+ 0x39, 0x3A, 0x3A, 0x3B, 0x3B, 0x3C, 0x3C, 0x3C,
+ 0x3D, 0x3D, 0x3E, 0x3E, 0x3E, 0x3F, 0x3F, 0x3F
+};
+
+MidiDriver_TOWNS::MidiDriver_TOWNS(Audio::Mixer *mixer) : _timerProc(0), _timerProcPara(0), _channels(0), _out(0),
+ _baseTempo(10080), _chanState(0), _operatorLevelTable(0), _tickCounter(0), _rand(1), _allocCurPos(0), _isOpen(false) {
+ // We set exteral mutex handling to true to avoid lockups in SCUMM which has its own mutex.
+ _intf = new TownsAudioInterface(mixer, this, true);
+
+ _channels = new TownsMidiInputChannel*[32];
+ for (int i = 0; i < 32; i++)
+ _channels[i] = new TownsMidiInputChannel(this, i > 8 ? (i + 1) : i);
+
+ _out = new TownsMidiOutputChannel*[6];
+ for (int i = 0; i < 6; i++)
+ _out[i] = new TownsMidiOutputChannel(this, i);
+
+ _chanState = new TownsMidiChanState[32];
+
+ _operatorLevelTable = new uint8[2048];
+ for (int i = 0; i < 64; i++) {
+ for (int ii = 0; ii < 32; ii++)
+ _operatorLevelTable[(i << 5) + ii] = ((i * (ii + 1)) >> 5) & 0xff;
+ }
+ for (int i = 0; i < 64; i++)
+ _operatorLevelTable[i << 5] = 0;
+}
+
+MidiDriver_TOWNS::~MidiDriver_TOWNS() {
+ close();
+ delete _intf;
+
+ if (_channels) {
+ for (int i = 0; i < 32; i++)
+ delete _channels[i];
+ delete[] _channels;
+ }
+ _channels = 0;
+
+ if (_out) {
+ for (int i = 0; i < 6; i++)
+ delete _out[i];
+ delete[] _out;
+ }
+ _out = 0;
+
+ delete[] _chanState;
+ _chanState = 0;
+ delete[] _operatorLevelTable;
+ _operatorLevelTable = 0;
+}
+
+int MidiDriver_TOWNS::open() {
+ if (_isOpen)
+ return MERR_ALREADY_OPEN;
+
+ if (!_intf->init())
+ return MERR_CANNOT_CONNECT;
+
+ _intf->callback(0);
+
+ _intf->callback(21, 255, 1);
+ _intf->callback(21, 0, 1);
+ _intf->callback(22, 255, 221);
+
+ _intf->callback(33, 8);
+ _intf->setSoundEffectChanMask(~0x3f);
+
+ _allocCurPos = 0;
+
+ _isOpen = true;
+
+ return 0;
+}
+
+void MidiDriver_TOWNS::close() {
+ if (!_isOpen)
+ return;
+
+ _isOpen = false;
+
+ setTimerCallback(0, 0);
+ g_system->delayMillis(20);
+}
+
+void MidiDriver_TOWNS::send(uint32 b) {
+ if (!_isOpen)
+ return;
+
+ byte param2 = (b >> 16) & 0xFF;
+ byte param1 = (b >> 8) & 0xFF;
+ byte cmd = b & 0xF0;
+
+ TownsMidiInputChannel *c = _channels[b & 0x0F];
+
+ switch (cmd) {
+ case 0x80:
+ c->noteOff(param1);
+ break;
+ case 0x90:
+ if (param2)
+ c->noteOn(param1, param2);
+ else
+ c->noteOff(param1);
+ break;
+ case 0xB0:
+ c->controlChange(param1, param2);
+ break;
+ case 0xC0:
+ c->programChange(param1);
+ break;
+ case 0xE0:
+ c->pitchBend((param1 | (param2 << 7)) - 0x2000);
+ break;
+ case 0xF0:
+ warning("MidiDriver_TOWNS: Receiving SysEx command on a send() call");
+ break;
+
+ default:
+ break;
+ }
+}
+
+void MidiDriver_TOWNS::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
+ _timerProc = timer_proc;
+ _timerProcPara = timer_param;
+}
+
+uint32 MidiDriver_TOWNS::getBaseTempo() {
+ return _baseTempo;
+}
+
+MidiChannel *MidiDriver_TOWNS::allocateChannel() {
+ if (!_isOpen)
+ return 0;
+
+ for (int i = 0; i < 32; ++i) {
+ TownsMidiInputChannel *chan = _channels[i];
+ if (chan->allocate())
+ return chan;
+ }
+
+ return 0;
+}
+
+MidiChannel *MidiDriver_TOWNS::getPercussionChannel() {
+ return 0;
+}
+
+void MidiDriver_TOWNS::timerCallback(int timerId) {
+ if (!_isOpen)
+ return;
+
+ switch (timerId) {
+ case 1:
+ updateParser();
+ updateOutputChannels();
+ break;
+ default:
+ break;
+ }
+}
+
+void MidiDriver_TOWNS::updateParser() {
+ if (_timerProc)
+ _timerProc(_timerProcPara);
+}
+
+void MidiDriver_TOWNS::updateOutputChannels() {
+ _tickCounter += _baseTempo;
+ while (_tickCounter >= 16667) {
+ _tickCounter -= 16667;
+ for (int i = 0; i < 6; i++) {
+ if (_out[i]->update())
+ return;
+ }
+ }
+}
+
+TownsMidiOutputChannel *MidiDriver_TOWNS::allocateOutputChannel(uint8 pri) {
+ TownsMidiOutputChannel *res = 0;
+
+ for (int i = 0; i < 6; i++) {
+ if (++_allocCurPos == 6)
+ _allocCurPos = 0;
+
+ int s = _out[_allocCurPos]->checkPriority(pri);
+ if (s == TownsMidiOutputChannel::kDisconnected)
+ return _out[_allocCurPos];
+
+ if (s != TownsMidiOutputChannel::kHighPriority) {
+ pri = s;
+ res = _out[_allocCurPos];
+ }
+ }
+
+ if (res)
+ res->disconnect();
+
+ return res;
+}
+
+int MidiDriver_TOWNS::randomValue(int para) {
+ _rand = (_rand & 1) ? (_rand >> 1) ^ 0xb8 : (_rand >> 1);
+ return (_rand * para) >> 8;
+}
diff --git a/engines/scumm/imuse/drivers/fmtowns.h b/engines/scumm/imuse/drivers/fmtowns.h
new file mode 100644
index 0000000..5413e37
--- /dev/null
+++ b/engines/scumm/imuse/drivers/fmtowns.h
@@ -0,0 +1,83 @@
+/* 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 IMUSE_DRV_FMTOWNS_H
+#define IMUSE_DRV_FMTOWNS_H
+
+#include "audio/softsynth/fmtowns_pc98/towns_audio.h"
+#include "audio/mididrv.h"
+
+
+class TownsMidiOutputChannel;
+class TownsMidiInputChannel;
+class TownsMidiChanState;
+
+class MidiDriver_TOWNS : public MidiDriver, public TownsAudioInterfacePluginDriver {
+friend class TownsMidiInputChannel;
+friend class TownsMidiOutputChannel;
+public:
+ MidiDriver_TOWNS(Audio::Mixer *mixer);
+ ~MidiDriver_TOWNS();
+
+ int open();
+ bool isOpen() const { return _isOpen; }
+ void close();
+
+ void send(uint32 b);
+
+ void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc);
+
+ uint32 getBaseTempo();
+ MidiChannel *allocateChannel();
+ MidiChannel *getPercussionChannel();
+
+ void timerCallback(int timerId);
+
+private:
+ void updateParser();
+ void updateOutputChannels();
+
+ TownsMidiOutputChannel *allocateOutputChannel(uint8 pri);
+
+ int randomValue(int para);
+
+ TownsMidiInputChannel **_channels;
+ TownsMidiOutputChannel **_out;
+ TownsMidiChanState *_chanState;
+
+ Common::TimerManager::TimerProc _timerProc;
+ void *_timerProcPara;
+
+ TownsAudioInterface *_intf;
+
+ uint32 _tickCounter;
+ uint8 _allocCurPos;
+ uint8 _rand;
+
+ bool _isOpen;
+
+ uint8 *_operatorLevelTable;
+
+ const uint16 _baseTempo;
+};
+
+#endif
diff --git a/engines/scumm/imuse/drivers/mac_m68k.cpp b/engines/scumm/imuse/drivers/mac_m68k.cpp
new file mode 100644
index 0000000..1bcef44
--- /dev/null
+++ b/engines/scumm/imuse/drivers/mac_m68k.cpp
@@ -0,0 +1,515 @@
+/* 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/drivers/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")) {
+ if (!resource.hasResFork()) {
+ error("MacM68kDriver::loadAllInstruments: \"iMUSE Setups\" loaded, but no resource fork present");
+ }
+
+ 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/drivers/mac_m68k.h b/engines/scumm/imuse/drivers/mac_m68k.h
new file mode 100644
index 0000000..31beaf4
--- /dev/null
+++ b/engines/scumm/imuse/drivers/mac_m68k.h
@@ -0,0 +1,178 @@
+/* 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/drivers/pcspk.cpp b/engines/scumm/imuse/drivers/pcspk.cpp
new file mode 100644
index 0000000..0e516c2
--- /dev/null
+++ b/engines/scumm/imuse/drivers/pcspk.cpp
@@ -0,0 +1,835 @@
+/* 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/drivers/pcspk.h"
+
+#include "common/util.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;
+ _effectTimer = 0;
+ _randBase = 1;
+
+ // We need to take care we only send note frequencies, when the internal
+ // settings actually changed, thus we need some extra state to keep track
+ // of that.
+ _lastActiveChannel = 0;
+ _lastActiveOut = 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 PcSpkDriver::close() {
+ if (!_isOpen)
+ return;
+ _isOpen = false;
+
+ _mixer->stopHandle(_mixerSoundHandle);
+}
+
+void PcSpkDriver::send(uint32 d) {
+ assert((d & 0x0F) < 6);
+ _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) {
+ out.unkA += out.unkB;
+ if (out.instrument)
+ out.unkE = ((int8)out.instrument[out.unkA] * out.unkC) >> 4;
+ }
+
+ ++_effectTimer;
+ if (_effectTimer > 3) {
+ _effectTimer = 0;
+
+ if (out.effectEnvelopeA.state)
+ updateEffectGenerator(_channels[i], out.effectEnvelopeA, out.effectDefA);
+ if (out.effectEnvelopeB.state)
+ updateEffectGenerator(_channels[i], out.effectEnvelopeB, out.effectDefB);
+ }
+ } 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();
+ _lastActiveChannel = 0;
+ _lastActiveOut = 0;
+ }
+}
+
+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();
+ _lastActiveChannel = 0;
+ _lastActiveOut = 0;
+ } 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;
+
+ // Only output in case the active channel changed or the frequency changed.
+ // This is not faithful to the original. Since our timings differ we would
+ // get distorted sound otherwise though.
+ if (_lastActiveChannel != _activeChannel || _lastActiveOut != out) {
+ _pcSpk.play(Audio::PCSpeaker::kWaveFormSquare, 1193180 / frequency, -1);
+ _lastActiveChannel = _activeChannel;
+ _lastActiveOut = out;
+ }
+}
+
+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];
+
+ if (_instrument[4] * 256 < ARRAYSIZE(PcSpkDriver::_outInstrumentData))
+ _out.instrument = _owner->_outInstrumentData + _instrument[4] * 256;
+ else
+ _out.instrument = 0;
+
+ _out.unkA = 0;
+ _out.unkB = _instrument[1];
+ _out.unkC = _instrument[2];
+ _out.unkE = 0;
+ _out.unk60 = 0;
+ _out.active = 1;
+
+ // In case we get a note on event on the last active channel, we reset the
+ // last active channel, thus we assure the frequency is correctly set, even
+ // when the same note was sent.
+ if (_owner->_lastActiveChannel == this) {
+ _owner->_lastActiveChannel = 0;
+ _owner->_lastActiveOut = 0;
+ }
+ _owner->updateNote();
+
+ _out.unkC += PcSpkDriver::getEffectModifier(_instrument[3] + ((velocity & 0xFE) << 4));
+ if (_out.unkC > 63)
+ _out.unkC = 63;
+
+ if ((_instrument[5] & 0x80) != 0)
+ _owner->setupEffects(*this, _out.effectEnvelopeA, _out.effectDefA, _instrument[5], _instrument + 6);
+
+ if ((_instrument[14] & 0x80) != 0)
+ _owner->setupEffects(*this, _out.effectEnvelopeB, _out.effectDefB, _instrument[14], _instrument + 15);
+}
+
+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.useModWheel)
+ _out.effectEnvelopeA.modWheelState = (value >> 2);
+ if (_out.effectEnvelopeB.state && _out.effectDefB.useModWheel)
+ _out.effectEnvelopeB.modWheelState = (value >> 2);
+ break;
+
+ case 7:
+ _tl = value;
+ if (_owner->_activeChannel == this) {
+ if (_tl == 0) {
+ _owner->_lastActiveChannel = 0;
+ _owner->_lastActiveOut = 0;
+ _owner->_pcSpk.stop();
+ } else {
+ _owner->output((_out.note << 7) + _pitchBend + _out.unk60 + _out.unkE);
+ }
+ }
+ 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));
+}
+
+uint8 PcSpkDriver::getEffectModifier(uint16 level) {
+ uint8 base = level / 32;
+ uint8 index = level % 32;
+
+ if (index == 0)
+ return 0;
+
+ return (base * (index + 1)) >> 5;
+}
+
+int16 PcSpkDriver::getEffectModLevel(int16 level, int8 mod) {
+ if (!mod) {
+ return 0;
+ } else if (mod == 31) {
+ return level;
+ } else if (level < -63 || level > 63) {
+ return (mod * (level + 1)) >> 6;
+ } else if (mod < 0) {
+ if (level < 0)
+ return getEffectModifier(((-level) << 5) - mod);
+ else
+ return -getEffectModifier((level << 5) - mod);
+ } else {
+ if (level < 0)
+ return -getEffectModifier(((-level) << 5) + mod);
+ else
+ return getEffectModifier(((-level) << 5) + mod);
+ }
+}
+
+int16 PcSpkDriver::getRandScale(int16 input) {
+ if (_randBase & 1)
+ _randBase = (_randBase >> 1) ^ 0xB8;
+ else
+ _randBase >>= 1;
+
+ return (_randBase * input) >> 8;
+}
+
+void PcSpkDriver::setupEffects(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def, byte flags, const byte *data) {
+ def.phase = 0;
+ def.useModWheel = flags & 0x40;
+ env.loop = flags & 0x20;
+ def.type = flags & 0x1F;
+
+ env.modWheelSensitivity = 31;
+ if (def.useModWheel)
+ env.modWheelState = chan._modWheel >> 2;
+ else
+ env.modWheelState = 31;
+
+ switch (def.type) {
+ case 0:
+ env.maxLevel = 767;
+ env.startLevel = 383;
+ break;
+
+ case 1:
+ env.maxLevel = 31;
+ env.startLevel = 15;
+ break;
+
+ case 2:
+ env.maxLevel = 63;
+ env.startLevel = chan._out.unkB;
+ break;
+
+ case 3:
+ env.maxLevel = 63;
+ env.startLevel = chan._out.unkC;
+ break;
+
+ case 4:
+ env.maxLevel = 3;
+ env.startLevel = chan._instrument[4];
+ break;
+
+ case 5:
+ env.maxLevel = 62;
+ env.startLevel = 31;
+ env.modWheelState = 0;
+ break;
+
+ case 6:
+ env.maxLevel = 31;
+ env.startLevel = 0;
+ env.modWheelSensitivity = 0;
+ break;
+
+ default:
+ break;
+ }
+
+ startEffect(env, data);
+}
+
+void PcSpkDriver::startEffect(EffectEnvelope &env, const byte *data) {
+ env.state = 1;
+ env.currentLevel = 0;
+ env.modWheelLast = 31;
+ env.duration = data[0] * 63;
+
+ env.stateTargetLevels[0] = data[1];
+ env.stateTargetLevels[1] = data[3];
+ env.stateTargetLevels[2] = data[5];
+ env.stateTargetLevels[3] = data[6];
+
+ env.stateModWheelLevels[0] = data[2];
+ env.stateModWheelLevels[1] = data[4];
+ env.stateModWheelLevels[2] = 0;
+ env.stateModWheelLevels[3] = data[7];
+
+ initNextEnvelopeState(env);
+}
+
+void PcSpkDriver::initNextEnvelopeState(EffectEnvelope &env) {
+ uint8 lastState = env.state - 1;
+
+ uint16 stepCount = _effectEnvStepTable[getEffectModifier(((env.stateTargetLevels[lastState] & 0x7F) << 5) + env.modWheelSensitivity)];
+ if (env.stateTargetLevels[lastState] & 0x80)
+ stepCount = getRandScale(stepCount);
+ if (!stepCount)
+ stepCount = 1;
+
+ env.stateNumSteps = env.stateStepCounter = stepCount;
+
+ int16 totalChange = 0;
+ if (lastState != 2) {
+ totalChange = getEffectModLevel(env.maxLevel, (env.stateModWheelLevels[lastState] & 0x7F) - 31);
+ if (env.stateModWheelLevels[lastState] & 0x80)
+ totalChange = getRandScale(totalChange);
+
+ if (totalChange + env.startLevel > env.maxLevel)
+ totalChange = env.maxLevel - env.startLevel;
+ else if (totalChange + env.startLevel < 0)
+ totalChange = -env.startLevel;
+
+ totalChange -= env.currentLevel;
+ }
+
+ env.changePerStep = totalChange / stepCount;
+ if (totalChange < 0) {
+ totalChange = -totalChange;
+ env.dir = -1;
+ } else {
+ env.dir = 1;
+ }
+ env.changePerStepRem = totalChange % stepCount;
+ env.changeCountRem = 0;
+}
+
+void PcSpkDriver::updateEffectGenerator(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def) {
+ if (advanceEffectEnvelope(env, def) & 1) {
+ switch (def.type) {
+ case 0: case 1:
+ chan._out.unk60 = def.phase << 4;
+ break;
+
+ case 2:
+ chan._out.unkB = (def.phase & 0xFF) + chan._instrument[1];
+ break;
+
+ case 3:
+ chan._out.unkC = (def.phase & 0xFF) + chan._instrument[2];
+ break;
+
+ case 4:
+ if ((chan._instrument[4] + (def.phase & 0xFF)) * 256 < ARRAYSIZE(_outInstrumentData))
+ chan._out.instrument = _outInstrumentData + (chan._instrument[4] + (def.phase & 0xFF)) * 256;
+ else
+ chan._out.instrument = 0;
+ break;
+
+ case 5:
+ env.modWheelState = (def.phase & 0xFF);
+ break;
+
+ case 6:
+ env.modWheelSensitivity = (def.phase & 0xFF);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+uint8 PcSpkDriver::advanceEffectEnvelope(EffectEnvelope &env, EffectDefinition &def) {
+ if (env.duration != 0) {
+ env.duration -= 17;
+ if (env.duration <= 0) {
+ env.state = 0;
+ return 0;
+ }
+ }
+
+ uint8 changedFlags = 0;
+ int16 newLevel = env.currentLevel + env.changePerStep;
+ env.changeCountRem += env.changePerStepRem;
+ if (env.changeCountRem >= env.stateNumSteps) {
+ env.changeCountRem -= env.stateNumSteps;
+ newLevel += env.dir;
+ }
+
+ if (env.currentLevel != newLevel || env.modWheelLast != env.modWheelState) {
+ env.currentLevel = newLevel;
+ env.modWheelLast = env.modWheelState;
+
+ int16 newPhase = getEffectModLevel(newLevel, env.modWheelState);
+ if (def.phase != newPhase) {
+ changedFlags |= 1;
+ def.phase = newPhase;
+ }
+ }
+
+ --env.stateStepCounter;
+ if (!env.stateStepCounter) {
+ ++env.state;
+ if (env.state > 4) {
+ if (env.loop) {
+ env.state = 1;
+ changedFlags |= 2;
+ } else {
+ env.state = 0;
+ return changedFlags;
+ }
+ }
+
+ initNextEnvelopeState(env);
+ }
+
+ return changedFlags;
+}
+
+const byte PcSpkDriver::_outInstrumentData[1024] = {
+ 0x00, 0x03, 0x06, 0x09, 0x0C, 0x0F, 0x12, 0x15,
+ 0x18, 0x1B, 0x1E, 0x21, 0x24, 0x27, 0x2A, 0x2D,
+ 0x30, 0x33, 0x36, 0x39, 0x3B, 0x3E, 0x41, 0x43,
+ 0x46, 0x49, 0x4B, 0x4E, 0x50, 0x52, 0x55, 0x57,
+ 0x59, 0x5B, 0x5E, 0x60, 0x62, 0x64, 0x66, 0x67,
+ 0x69, 0x6B, 0x6C, 0x6E, 0x70, 0x71, 0x72, 0x74,
+ 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7B,
+ 0x7C, 0x7D, 0x7D, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E,
+ 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7D, 0x7D,
+ 0x7C, 0x7B, 0x7B, 0x7A, 0x79, 0x78, 0x77, 0x76,
+ 0x75, 0x74, 0x72, 0x71, 0x70, 0x6E, 0x6C, 0x6B,
+ 0x69, 0x67, 0x66, 0x64, 0x62, 0x60, 0x5E, 0x5B,
+ 0x59, 0x57, 0x55, 0x52, 0x50, 0x4E, 0x4B, 0x49,
+ 0x46, 0x43, 0x41, 0x3E, 0x3B, 0x39, 0x36, 0x33,
+ 0x30, 0x2D, 0x2A, 0x27, 0x24, 0x21, 0x1E, 0x1B,
+ 0x18, 0x15, 0x12, 0x0F, 0x0C, 0x09, 0x06, 0x03,
+ 0x00, 0xFD, 0xFA, 0xF7, 0xF4, 0xF1, 0xEE, 0xEB,
+ 0xE8, 0xE5, 0xE2, 0xDF, 0xDC, 0xD9, 0xD6, 0xD3,
+ 0xD0, 0xCD, 0xCA, 0xC7, 0xC5, 0xC2, 0xBF, 0xBD,
+ 0xBA, 0xB7, 0xB5, 0xB2, 0xB0, 0xAE, 0xAB, 0xA9,
+ 0xA7, 0xA5, 0xA2, 0xA0, 0x9E, 0x9C, 0x9A, 0x99,
+ 0x97, 0x95, 0x94, 0x92, 0x90, 0x8F, 0x8E, 0x8C,
+ 0x8B, 0x8A, 0x89, 0x88, 0x87, 0x86, 0x85, 0x85,
+ 0x84, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x83, 0x83,
+ 0x84, 0x85, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A,
+ 0x8B, 0x8C, 0x8E, 0x8F, 0x90, 0x92, 0x94, 0x95,
+ 0x97, 0x99, 0x9A, 0x9C, 0x9E, 0xA0, 0xA2, 0xA5,
+ 0xA7, 0xA9, 0xAB, 0xAE, 0xB0, 0xB2, 0xB5, 0xB7,
+ 0xBA, 0xBD, 0xBF, 0xC2, 0xC5, 0xC7, 0xCA, 0xCD,
+ 0xD0, 0xD3, 0xD6, 0xD9, 0xDC, 0xDF, 0xE2, 0xE5,
+ 0xE8, 0xEB, 0xEE, 0xF1, 0xF4, 0xF7, 0xFA, 0xFD,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+ 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
+ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+ 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
+ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
+ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+ 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+ 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
+ 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
+ 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
+ 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7,
+ 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
+ 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7,
+ 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
+ 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7,
+ 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
+ 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7,
+ 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
+ 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
+ 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x29, 0x23, 0xBE, 0x84, 0xE1, 0x6C, 0xD6, 0xAE,
+ 0x52, 0x90, 0x49, 0xF1, 0xF1, 0xBB, 0xE9, 0xEB,
+ 0xB3, 0xA6, 0xDB, 0x3C, 0x87, 0x0C, 0x3E, 0x99,
+ 0x24, 0x5E, 0x0D, 0x1C, 0x06, 0xB7, 0x47, 0xDE,
+ 0xB3, 0x12, 0x4D, 0xC8, 0x43, 0xBB, 0x8B, 0xA6,
+ 0x1F, 0x03, 0x5A, 0x7D, 0x09, 0x38, 0x25, 0x1F,
+ 0x5D, 0xD4, 0xCB, 0xFC, 0x96, 0xF5, 0x45, 0x3B,
+ 0x13, 0x0D, 0x89, 0x0A, 0x1C, 0xDB, 0xAE, 0x32,
+ 0x20, 0x9A, 0x50, 0xEE, 0x40, 0x78, 0x36, 0xFD,
+ 0x12, 0x49, 0x32, 0xF6, 0x9E, 0x7D, 0x49, 0xDC,
+ 0xAD, 0x4F, 0x14, 0xF2, 0x44, 0x40, 0x66, 0xD0,
+ 0x6B, 0xC4, 0x30, 0xB7, 0x32, 0x3B, 0xA1, 0x22,
+ 0xF6, 0x22, 0x91, 0x9D, 0xE1, 0x8B, 0x1F, 0xDA,
+ 0xB0, 0xCA, 0x99, 0x02, 0xB9, 0x72, 0x9D, 0x49,
+ 0x2C, 0x80, 0x7E, 0xC5, 0x99, 0xD5, 0xE9, 0x80,
+ 0xB2, 0xEA, 0xC9, 0xCC, 0x53, 0xBF, 0x67, 0xD6,
+ 0xBF, 0x14, 0xD6, 0x7E, 0x2D, 0xDC, 0x8E, 0x66,
+ 0x83, 0xEF, 0x57, 0x49, 0x61, 0xFF, 0x69, 0x8F,
+ 0x61, 0xCD, 0xD1, 0x1E, 0x9D, 0x9C, 0x16, 0x72,
+ 0x72, 0xE6, 0x1D, 0xF0, 0x84, 0x4F, 0x4A, 0x77,
+ 0x02, 0xD7, 0xE8, 0x39, 0x2C, 0x53, 0xCB, 0xC9,
+ 0x12, 0x1E, 0x33, 0x74, 0x9E, 0x0C, 0xF4, 0xD5,
+ 0xD4, 0x9F, 0xD4, 0xA4, 0x59, 0x7E, 0x35, 0xCF,
+ 0x32, 0x22, 0xF4, 0xCC, 0xCF, 0xD3, 0x90, 0x2D,
+ 0x48, 0xD3, 0x8F, 0x75, 0xE6, 0xD9, 0x1D, 0x2A,
+ 0xE5, 0xC0, 0xF7, 0x2B, 0x78, 0x81, 0x87, 0x44,
+ 0x0E, 0x5F, 0x50, 0x00, 0xD4, 0x61, 0x8D, 0xBE,
+ 0x7B, 0x05, 0x15, 0x07, 0x3B, 0x33, 0x82, 0x1F,
+ 0x18, 0x70, 0x92, 0xDA, 0x64, 0x54, 0xCE, 0xB1,
+ 0x85, 0x3E, 0x69, 0x15, 0xF8, 0x46, 0x6A, 0x04,
+ 0x96, 0x73, 0x0E, 0xD9, 0x16, 0x2F, 0x67, 0x68,
+ 0xD4, 0xF7, 0x4A, 0x4A, 0xD0, 0x57, 0x68, 0x76
+};
+
+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::_effectEnvStepTable[] = {
+ 1, 2, 4, 5,
+ 6, 7, 8, 9,
+ 10, 12, 14, 16,
+ 18, 21, 24, 30,
+ 36, 50, 64, 82,
+ 100, 136, 160, 192,
+ 240, 276, 340, 460,
+ 600, 860, 1200, 1600
+};
+
+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/drivers/pcspk.h b/engines/scumm/imuse/drivers/pcspk.h
new file mode 100644
index 0000000..6a107e1
--- /dev/null
+++ b/engines/scumm/imuse/drivers/pcspk.h
@@ -0,0 +1,161 @@
+/* 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;
+ int _effectTimer;
+ uint8 _randBase;
+
+ void updateNote();
+ void output(uint16 out);
+
+ static uint8 getEffectModifier(uint16 level);
+ int16 getEffectModLevel(int16 level, int8 mod);
+ int16 getRandScale(int16 input);
+
+ struct EffectEnvelope {
+ uint8 state;
+ int16 currentLevel;
+ int16 duration;
+ int16 maxLevel;
+ int16 startLevel;
+ uint8 loop;
+ uint8 stateTargetLevels[4];
+ uint8 stateModWheelLevels[4];
+ uint8 modWheelSensitivity;
+ uint8 modWheelState;
+ uint8 modWheelLast;
+ int16 stateNumSteps;
+ int16 stateStepCounter;
+ int16 changePerStep;
+ int8 dir;
+ int16 changePerStepRem;
+ int16 changeCountRem;
+ };
+
+ struct EffectDefinition {
+ int16 phase;
+ uint8 type;
+ uint8 useModWheel;
+ EffectEnvelope *envelope;
+ };
+
+ struct OutputChannel {
+ uint8 active;
+ uint8 note;
+ uint8 sustainNoteOff;
+ uint8 length;
+ const uint8 *instrument;
+ uint8 unkA;
+ uint8 unkB;
+ uint8 unkC;
+ int16 unkE;
+ EffectEnvelope effectEnvelopeA;
+ EffectDefinition effectDefA;
+ EffectEnvelope effectEnvelopeB;
+ EffectDefinition effectDefB;
+ int16 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;
+ };
+
+ void setupEffects(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def, byte flags, const byte *data);
+ void startEffect(EffectEnvelope &env, const byte *data);
+ void initNextEnvelopeState(EffectEnvelope &env);
+ void updateEffectGenerator(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def);
+ uint8 advanceEffectEnvelope(EffectEnvelope &env, EffectDefinition &def);
+
+ MidiChannel_PcSpk _channels[6];
+ MidiChannel_PcSpk *_activeChannel;
+
+ MidiChannel_PcSpk *_lastActiveChannel;
+ uint16 _lastActiveOut;
+
+ static const byte _outInstrumentData[1024];
+ static const byte _outputTable1[];
+ static const byte _outputTable2[];
+ static const uint16 _effectEnvStepTable[];
+ static const uint16 _frequencyTable[];
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/imuse/mac_m68k.cpp b/engines/scumm/imuse/mac_m68k.cpp
deleted file mode 100644
index 8ebd8e4..0000000
--- a/engines/scumm/imuse/mac_m68k.cpp
+++ /dev/null
@@ -1,515 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#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")) {
- if (!resource.hasResFork()) {
- error("MacM68kDriver::loadAllInstruments: \"iMUSE Setups\" loaded, but no resource fork present");
- }
-
- 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
deleted file mode 100644
index 31beaf4..0000000
--- a/engines/scumm/imuse/mac_m68k.h
+++ /dev/null
@@ -1,178 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#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/pcspk.cpp b/engines/scumm/imuse/pcspk.cpp
deleted file mode 100644
index 856b771..0000000
--- a/engines/scumm/imuse/pcspk.cpp
+++ /dev/null
@@ -1,835 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#include "scumm/imuse/pcspk.h"
-
-#include "common/util.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;
- _effectTimer = 0;
- _randBase = 1;
-
- // We need to take care we only send note frequencies, when the internal
- // settings actually changed, thus we need some extra state to keep track
- // of that.
- _lastActiveChannel = 0;
- _lastActiveOut = 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 PcSpkDriver::close() {
- if (!_isOpen)
- return;
- _isOpen = false;
-
- _mixer->stopHandle(_mixerSoundHandle);
-}
-
-void PcSpkDriver::send(uint32 d) {
- assert((d & 0x0F) < 6);
- _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) {
- out.unkA += out.unkB;
- if (out.instrument)
- out.unkE = ((int8)out.instrument[out.unkA] * out.unkC) >> 4;
- }
-
- ++_effectTimer;
- if (_effectTimer > 3) {
- _effectTimer = 0;
-
- if (out.effectEnvelopeA.state)
- updateEffectGenerator(_channels[i], out.effectEnvelopeA, out.effectDefA);
- if (out.effectEnvelopeB.state)
- updateEffectGenerator(_channels[i], out.effectEnvelopeB, out.effectDefB);
- }
- } 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();
- _lastActiveChannel = 0;
- _lastActiveOut = 0;
- }
-}
-
-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();
- _lastActiveChannel = 0;
- _lastActiveOut = 0;
- } 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;
-
- // Only output in case the active channel changed or the frequency changed.
- // This is not faithful to the original. Since our timings differ we would
- // get distorted sound otherwise though.
- if (_lastActiveChannel != _activeChannel || _lastActiveOut != out) {
- _pcSpk.play(Audio::PCSpeaker::kWaveFormSquare, 1193180 / frequency, -1);
- _lastActiveChannel = _activeChannel;
- _lastActiveOut = out;
- }
-}
-
-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];
-
- if (_instrument[4] * 256 < ARRAYSIZE(PcSpkDriver::_outInstrumentData))
- _out.instrument = _owner->_outInstrumentData + _instrument[4] * 256;
- else
- _out.instrument = 0;
-
- _out.unkA = 0;
- _out.unkB = _instrument[1];
- _out.unkC = _instrument[2];
- _out.unkE = 0;
- _out.unk60 = 0;
- _out.active = 1;
-
- // In case we get a note on event on the last active channel, we reset the
- // last active channel, thus we assure the frequency is correctly set, even
- // when the same note was sent.
- if (_owner->_lastActiveChannel == this) {
- _owner->_lastActiveChannel = 0;
- _owner->_lastActiveOut = 0;
- }
- _owner->updateNote();
-
- _out.unkC += PcSpkDriver::getEffectModifier(_instrument[3] + ((velocity & 0xFE) << 4));
- if (_out.unkC > 63)
- _out.unkC = 63;
-
- if ((_instrument[5] & 0x80) != 0)
- _owner->setupEffects(*this, _out.effectEnvelopeA, _out.effectDefA, _instrument[5], _instrument + 6);
-
- if ((_instrument[14] & 0x80) != 0)
- _owner->setupEffects(*this, _out.effectEnvelopeB, _out.effectDefB, _instrument[14], _instrument + 15);
-}
-
-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.useModWheel)
- _out.effectEnvelopeA.modWheelState = (value >> 2);
- if (_out.effectEnvelopeB.state && _out.effectDefB.useModWheel)
- _out.effectEnvelopeB.modWheelState = (value >> 2);
- break;
-
- case 7:
- _tl = value;
- if (_owner->_activeChannel == this) {
- if (_tl == 0) {
- _owner->_lastActiveChannel = 0;
- _owner->_lastActiveOut = 0;
- _owner->_pcSpk.stop();
- } else {
- _owner->output((_out.note << 7) + _pitchBend + _out.unk60 + _out.unkE);
- }
- }
- 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));
-}
-
-uint8 PcSpkDriver::getEffectModifier(uint16 level) {
- uint8 base = level / 32;
- uint8 index = level % 32;
-
- if (index == 0)
- return 0;
-
- return (base * (index + 1)) >> 5;
-}
-
-int16 PcSpkDriver::getEffectModLevel(int16 level, int8 mod) {
- if (!mod) {
- return 0;
- } else if (mod == 31) {
- return level;
- } else if (level < -63 || level > 63) {
- return (mod * (level + 1)) >> 6;
- } else if (mod < 0) {
- if (level < 0)
- return getEffectModifier(((-level) << 5) - mod);
- else
- return -getEffectModifier((level << 5) - mod);
- } else {
- if (level < 0)
- return -getEffectModifier(((-level) << 5) + mod);
- else
- return getEffectModifier(((-level) << 5) + mod);
- }
-}
-
-int16 PcSpkDriver::getRandScale(int16 input) {
- if (_randBase & 1)
- _randBase = (_randBase >> 1) ^ 0xB8;
- else
- _randBase >>= 1;
-
- return (_randBase * input) >> 8;
-}
-
-void PcSpkDriver::setupEffects(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def, byte flags, const byte *data) {
- def.phase = 0;
- def.useModWheel = flags & 0x40;
- env.loop = flags & 0x20;
- def.type = flags & 0x1F;
-
- env.modWheelSensitivity = 31;
- if (def.useModWheel)
- env.modWheelState = chan._modWheel >> 2;
- else
- env.modWheelState = 31;
-
- switch (def.type) {
- case 0:
- env.maxLevel = 767;
- env.startLevel = 383;
- break;
-
- case 1:
- env.maxLevel = 31;
- env.startLevel = 15;
- break;
-
- case 2:
- env.maxLevel = 63;
- env.startLevel = chan._out.unkB;
- break;
-
- case 3:
- env.maxLevel = 63;
- env.startLevel = chan._out.unkC;
- break;
-
- case 4:
- env.maxLevel = 3;
- env.startLevel = chan._instrument[4];
- break;
-
- case 5:
- env.maxLevel = 62;
- env.startLevel = 31;
- env.modWheelState = 0;
- break;
-
- case 6:
- env.maxLevel = 31;
- env.startLevel = 0;
- env.modWheelSensitivity = 0;
- break;
-
- default:
- break;
- }
-
- startEffect(env, data);
-}
-
-void PcSpkDriver::startEffect(EffectEnvelope &env, const byte *data) {
- env.state = 1;
- env.currentLevel = 0;
- env.modWheelLast = 31;
- env.duration = data[0] * 63;
-
- env.stateTargetLevels[0] = data[1];
- env.stateTargetLevels[1] = data[3];
- env.stateTargetLevels[2] = data[5];
- env.stateTargetLevels[3] = data[6];
-
- env.stateModWheelLevels[0] = data[2];
- env.stateModWheelLevels[1] = data[4];
- env.stateModWheelLevels[2] = 0;
- env.stateModWheelLevels[3] = data[7];
-
- initNextEnvelopeState(env);
-}
-
-void PcSpkDriver::initNextEnvelopeState(EffectEnvelope &env) {
- uint8 lastState = env.state - 1;
-
- uint16 stepCount = _effectEnvStepTable[getEffectModifier(((env.stateTargetLevels[lastState] & 0x7F) << 5) + env.modWheelSensitivity)];
- if (env.stateTargetLevels[lastState] & 0x80)
- stepCount = getRandScale(stepCount);
- if (!stepCount)
- stepCount = 1;
-
- env.stateNumSteps = env.stateStepCounter = stepCount;
-
- int16 totalChange = 0;
- if (lastState != 2) {
- totalChange = getEffectModLevel(env.maxLevel, (env.stateModWheelLevels[lastState] & 0x7F) - 31);
- if (env.stateModWheelLevels[lastState] & 0x80)
- totalChange = getRandScale(totalChange);
-
- if (totalChange + env.startLevel > env.maxLevel)
- totalChange = env.maxLevel - env.startLevel;
- else if (totalChange + env.startLevel < 0)
- totalChange = -env.startLevel;
-
- totalChange -= env.currentLevel;
- }
-
- env.changePerStep = totalChange / stepCount;
- if (totalChange < 0) {
- totalChange = -totalChange;
- env.dir = -1;
- } else {
- env.dir = 1;
- }
- env.changePerStepRem = totalChange % stepCount;
- env.changeCountRem = 0;
-}
-
-void PcSpkDriver::updateEffectGenerator(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def) {
- if (advanceEffectEnvelope(env, def) & 1) {
- switch (def.type) {
- case 0: case 1:
- chan._out.unk60 = def.phase << 4;
- break;
-
- case 2:
- chan._out.unkB = (def.phase & 0xFF) + chan._instrument[1];
- break;
-
- case 3:
- chan._out.unkC = (def.phase & 0xFF) + chan._instrument[2];
- break;
-
- case 4:
- if ((chan._instrument[4] + (def.phase & 0xFF)) * 256 < ARRAYSIZE(_outInstrumentData))
- chan._out.instrument = _outInstrumentData + (chan._instrument[4] + (def.phase & 0xFF)) * 256;
- else
- chan._out.instrument = 0;
- break;
-
- case 5:
- env.modWheelState = (def.phase & 0xFF);
- break;
-
- case 6:
- env.modWheelSensitivity = (def.phase & 0xFF);
- break;
-
- default:
- break;
- }
- }
-}
-
-uint8 PcSpkDriver::advanceEffectEnvelope(EffectEnvelope &env, EffectDefinition &def) {
- if (env.duration != 0) {
- env.duration -= 17;
- if (env.duration <= 0) {
- env.state = 0;
- return 0;
- }
- }
-
- uint8 changedFlags = 0;
- int16 newLevel = env.currentLevel + env.changePerStep;
- env.changeCountRem += env.changePerStepRem;
- if (env.changeCountRem >= env.stateNumSteps) {
- env.changeCountRem -= env.stateNumSteps;
- newLevel += env.dir;
- }
-
- if (env.currentLevel != newLevel || env.modWheelLast != env.modWheelState) {
- env.currentLevel = newLevel;
- env.modWheelLast = env.modWheelState;
-
- int16 newPhase = getEffectModLevel(newLevel, env.modWheelState);
- if (def.phase != newPhase) {
- changedFlags |= 1;
- def.phase = newPhase;
- }
- }
-
- --env.stateStepCounter;
- if (!env.stateStepCounter) {
- ++env.state;
- if (env.state > 4) {
- if (env.loop) {
- env.state = 1;
- changedFlags |= 2;
- } else {
- env.state = 0;
- return changedFlags;
- }
- }
-
- initNextEnvelopeState(env);
- }
-
- return changedFlags;
-}
-
-const byte PcSpkDriver::_outInstrumentData[1024] = {
- 0x00, 0x03, 0x06, 0x09, 0x0C, 0x0F, 0x12, 0x15,
- 0x18, 0x1B, 0x1E, 0x21, 0x24, 0x27, 0x2A, 0x2D,
- 0x30, 0x33, 0x36, 0x39, 0x3B, 0x3E, 0x41, 0x43,
- 0x46, 0x49, 0x4B, 0x4E, 0x50, 0x52, 0x55, 0x57,
- 0x59, 0x5B, 0x5E, 0x60, 0x62, 0x64, 0x66, 0x67,
- 0x69, 0x6B, 0x6C, 0x6E, 0x70, 0x71, 0x72, 0x74,
- 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7B,
- 0x7C, 0x7D, 0x7D, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E,
- 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7D, 0x7D,
- 0x7C, 0x7B, 0x7B, 0x7A, 0x79, 0x78, 0x77, 0x76,
- 0x75, 0x74, 0x72, 0x71, 0x70, 0x6E, 0x6C, 0x6B,
- 0x69, 0x67, 0x66, 0x64, 0x62, 0x60, 0x5E, 0x5B,
- 0x59, 0x57, 0x55, 0x52, 0x50, 0x4E, 0x4B, 0x49,
- 0x46, 0x43, 0x41, 0x3E, 0x3B, 0x39, 0x36, 0x33,
- 0x30, 0x2D, 0x2A, 0x27, 0x24, 0x21, 0x1E, 0x1B,
- 0x18, 0x15, 0x12, 0x0F, 0x0C, 0x09, 0x06, 0x03,
- 0x00, 0xFD, 0xFA, 0xF7, 0xF4, 0xF1, 0xEE, 0xEB,
- 0xE8, 0xE5, 0xE2, 0xDF, 0xDC, 0xD9, 0xD6, 0xD3,
- 0xD0, 0xCD, 0xCA, 0xC7, 0xC5, 0xC2, 0xBF, 0xBD,
- 0xBA, 0xB7, 0xB5, 0xB2, 0xB0, 0xAE, 0xAB, 0xA9,
- 0xA7, 0xA5, 0xA2, 0xA0, 0x9E, 0x9C, 0x9A, 0x99,
- 0x97, 0x95, 0x94, 0x92, 0x90, 0x8F, 0x8E, 0x8C,
- 0x8B, 0x8A, 0x89, 0x88, 0x87, 0x86, 0x85, 0x85,
- 0x84, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, 0x82,
- 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x83, 0x83,
- 0x84, 0x85, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A,
- 0x8B, 0x8C, 0x8E, 0x8F, 0x90, 0x92, 0x94, 0x95,
- 0x97, 0x99, 0x9A, 0x9C, 0x9E, 0xA0, 0xA2, 0xA5,
- 0xA7, 0xA9, 0xAB, 0xAE, 0xB0, 0xB2, 0xB5, 0xB7,
- 0xBA, 0xBD, 0xBF, 0xC2, 0xC5, 0xC7, 0xCA, 0xCD,
- 0xD0, 0xD3, 0xD6, 0xD9, 0xDC, 0xDF, 0xE2, 0xE5,
- 0xE8, 0xEB, 0xEE, 0xF1, 0xF4, 0xF7, 0xFA, 0xFD,
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
- 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
- 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
- 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
- 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
- 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
- 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
- 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
- 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
- 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
- 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
- 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
- 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
- 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
- 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
- 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
- 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
- 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
- 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
- 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
- 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
- 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
- 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7,
- 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
- 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7,
- 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
- 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7,
- 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
- 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7,
- 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
- 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
- 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
- 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
- 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
- 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
- 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
- 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
- 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
- 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
- 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
- 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
- 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
- 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
- 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
- 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
- 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
- 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
- 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
- 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
- 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
- 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
- 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
- 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
- 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
- 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
- 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
- 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
- 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
- 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
- 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
- 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
- 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
- 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
- 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
- 0x29, 0x23, 0xBE, 0x84, 0xE1, 0x6C, 0xD6, 0xAE,
- 0x52, 0x90, 0x49, 0xF1, 0xF1, 0xBB, 0xE9, 0xEB,
- 0xB3, 0xA6, 0xDB, 0x3C, 0x87, 0x0C, 0x3E, 0x99,
- 0x24, 0x5E, 0x0D, 0x1C, 0x06, 0xB7, 0x47, 0xDE,
- 0xB3, 0x12, 0x4D, 0xC8, 0x43, 0xBB, 0x8B, 0xA6,
- 0x1F, 0x03, 0x5A, 0x7D, 0x09, 0x38, 0x25, 0x1F,
- 0x5D, 0xD4, 0xCB, 0xFC, 0x96, 0xF5, 0x45, 0x3B,
- 0x13, 0x0D, 0x89, 0x0A, 0x1C, 0xDB, 0xAE, 0x32,
- 0x20, 0x9A, 0x50, 0xEE, 0x40, 0x78, 0x36, 0xFD,
- 0x12, 0x49, 0x32, 0xF6, 0x9E, 0x7D, 0x49, 0xDC,
- 0xAD, 0x4F, 0x14, 0xF2, 0x44, 0x40, 0x66, 0xD0,
- 0x6B, 0xC4, 0x30, 0xB7, 0x32, 0x3B, 0xA1, 0x22,
- 0xF6, 0x22, 0x91, 0x9D, 0xE1, 0x8B, 0x1F, 0xDA,
- 0xB0, 0xCA, 0x99, 0x02, 0xB9, 0x72, 0x9D, 0x49,
- 0x2C, 0x80, 0x7E, 0xC5, 0x99, 0xD5, 0xE9, 0x80,
- 0xB2, 0xEA, 0xC9, 0xCC, 0x53, 0xBF, 0x67, 0xD6,
- 0xBF, 0x14, 0xD6, 0x7E, 0x2D, 0xDC, 0x8E, 0x66,
- 0x83, 0xEF, 0x57, 0x49, 0x61, 0xFF, 0x69, 0x8F,
- 0x61, 0xCD, 0xD1, 0x1E, 0x9D, 0x9C, 0x16, 0x72,
- 0x72, 0xE6, 0x1D, 0xF0, 0x84, 0x4F, 0x4A, 0x77,
- 0x02, 0xD7, 0xE8, 0x39, 0x2C, 0x53, 0xCB, 0xC9,
- 0x12, 0x1E, 0x33, 0x74, 0x9E, 0x0C, 0xF4, 0xD5,
- 0xD4, 0x9F, 0xD4, 0xA4, 0x59, 0x7E, 0x35, 0xCF,
- 0x32, 0x22, 0xF4, 0xCC, 0xCF, 0xD3, 0x90, 0x2D,
- 0x48, 0xD3, 0x8F, 0x75, 0xE6, 0xD9, 0x1D, 0x2A,
- 0xE5, 0xC0, 0xF7, 0x2B, 0x78, 0x81, 0x87, 0x44,
- 0x0E, 0x5F, 0x50, 0x00, 0xD4, 0x61, 0x8D, 0xBE,
- 0x7B, 0x05, 0x15, 0x07, 0x3B, 0x33, 0x82, 0x1F,
- 0x18, 0x70, 0x92, 0xDA, 0x64, 0x54, 0xCE, 0xB1,
- 0x85, 0x3E, 0x69, 0x15, 0xF8, 0x46, 0x6A, 0x04,
- 0x96, 0x73, 0x0E, 0xD9, 0x16, 0x2F, 0x67, 0x68,
- 0xD4, 0xF7, 0x4A, 0x4A, 0xD0, 0x57, 0x68, 0x76
-};
-
-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::_effectEnvStepTable[] = {
- 1, 2, 4, 5,
- 6, 7, 8, 9,
- 10, 12, 14, 16,
- 18, 21, 24, 30,
- 36, 50, 64, 82,
- 100, 136, 160, 192,
- 240, 276, 340, 460,
- 600, 860, 1200, 1600
-};
-
-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
deleted file mode 100644
index 6a107e1..0000000
--- a/engines/scumm/imuse/pcspk.h
+++ /dev/null
@@ -1,161 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#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;
- int _effectTimer;
- uint8 _randBase;
-
- void updateNote();
- void output(uint16 out);
-
- static uint8 getEffectModifier(uint16 level);
- int16 getEffectModLevel(int16 level, int8 mod);
- int16 getRandScale(int16 input);
-
- struct EffectEnvelope {
- uint8 state;
- int16 currentLevel;
- int16 duration;
- int16 maxLevel;
- int16 startLevel;
- uint8 loop;
- uint8 stateTargetLevels[4];
- uint8 stateModWheelLevels[4];
- uint8 modWheelSensitivity;
- uint8 modWheelState;
- uint8 modWheelLast;
- int16 stateNumSteps;
- int16 stateStepCounter;
- int16 changePerStep;
- int8 dir;
- int16 changePerStepRem;
- int16 changeCountRem;
- };
-
- struct EffectDefinition {
- int16 phase;
- uint8 type;
- uint8 useModWheel;
- EffectEnvelope *envelope;
- };
-
- struct OutputChannel {
- uint8 active;
- uint8 note;
- uint8 sustainNoteOff;
- uint8 length;
- const uint8 *instrument;
- uint8 unkA;
- uint8 unkB;
- uint8 unkC;
- int16 unkE;
- EffectEnvelope effectEnvelopeA;
- EffectDefinition effectDefA;
- EffectEnvelope effectEnvelopeB;
- EffectDefinition effectDefB;
- int16 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;
- };
-
- void setupEffects(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def, byte flags, const byte *data);
- void startEffect(EffectEnvelope &env, const byte *data);
- void initNextEnvelopeState(EffectEnvelope &env);
- void updateEffectGenerator(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def);
- uint8 advanceEffectEnvelope(EffectEnvelope &env, EffectDefinition &def);
-
- MidiChannel_PcSpk _channels[6];
- MidiChannel_PcSpk *_activeChannel;
-
- MidiChannel_PcSpk *_lastActiveChannel;
- uint16 _lastActiveOut;
-
- static const byte _outInstrumentData[1024];
- static const byte _outputTable1[];
- static const byte _outputTable2[];
- static const uint16 _effectEnvStepTable[];
- static const uint16 _frequencyTable[];
-};
-
-} // End of namespace Scumm
-
-#endif
diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index bcc1eba..799176f 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -28,11 +28,12 @@ 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 \
imuse/drivers/amiga.o \
+ imuse/drivers/fmtowns.o \
+ imuse/drivers/mac_m68k.o \
+ imuse/drivers/pcspk.o \
input.o \
midiparser_ro.o \
object.o \
diff --git a/engines/scumm/players/player_towns.h b/engines/scumm/players/player_towns.h
index ad51c3e..6283547 100644
--- a/engines/scumm/players/player_towns.h
+++ b/engines/scumm/players/player_towns.h
@@ -25,8 +25,8 @@
#include "scumm/scumm.h"
#include "scumm/imuse/imuse.h"
+#include "scumm/imuse/drivers/fmtowns.h"
#include "audio/softsynth/fmtowns_pc98/towns_euphony.h"
-#include "audio/softsynth/fmtowns_pc98/towns_midi.h"
namespace Scumm {
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 64f45c1..e3919ee 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -76,9 +76,10 @@
#include "scumm/he/cup_player_he.h"
#include "scumm/util.h"
#include "scumm/verbs.h"
-#include "scumm/imuse/pcspk.h"
-#include "scumm/imuse/mac_m68k.h"
+#include "scumm/imuse/drivers/pcspk.h"
+#include "scumm/imuse/drivers/mac_m68k.h"
#include "scumm/imuse/drivers/amiga.h"
+#include "scumm/imuse/drivers/fmtowns.h"
#include "backends/audiocd/audiocd.h"
@@ -2001,7 +2002,9 @@ void ScummEngine::setupMusic(int midi) {
nativeMidiDriver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
if (!useOnlyNative) {
- if (_sound->_musicType == MDT_ADLIB || _sound->_musicType == MDT_TOWNS || multi_midi) {
+ if (_sound->_musicType == MDT_TOWNS) {
+ adlibMidiDriver = new MidiDriver_TOWNS(_mixer);
+ } else if (_sound->_musicType == MDT_ADLIB || 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);
// Try to use OPL3 mode for Sam&Max when possible.
Commit: 4ee4d2d9aff62136c42004e4463b8d696571f9ec
https://github.com/scummvm/scummvm/commit/4ee4d2d9aff62136c42004e4463b8d696571f9ec
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-14T21:45:48+02:00
Commit Message:
SCUMM: (FM-Towns Audio) remove TODO
The audio track is not broken. LEC simply didn't make good euphony music tracks for the FM-Towns. There is nothing we can do about that.
The TODO implies that someone should mess around with the audio data (change the composition?).
Changed paths:
engines/scumm/players/player_towns.cpp
diff --git a/engines/scumm/players/player_towns.cpp b/engines/scumm/players/player_towns.cpp
index 41bcf9e..c5f6b21 100644
--- a/engines/scumm/players/player_towns.cpp
+++ b/engines/scumm/players/player_towns.cpp
@@ -262,9 +262,6 @@ void Player_Towns_v1::startSound(int sound) {
// type (255 instead of 1).
// It doesn't sound great but we'll enable it to have music at all in this scene.
// See Trac#1873 and Trac#10561.
- //
- // TODO: Check if playback of this can be improved somehow (maybe there's something else
- // off with the data for which we can add a workaround?).
playEuphonyTrack(sound, ptr + 6);
} else if (type == 2) {
Commit: 19643175a8fa605f8703c61c0060bbcaf69302ec
https://github.com/scummvm/scummvm/commit/19643175a8fa605f8703c61c0060bbcaf69302ec
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-14T21:45:52+02:00
Commit Message:
SCUMM: limit 'Unrecognized base tag' warning to valid cases
This warning will not only show up if a tag is actually unrecognized but also in cases where the tag is recognized, but the resource size is 0. This happens quite a lot in the Amiga version of MI2 with 'SOU ' tags.
Changed paths:
engines/scumm/sound.cpp
diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp
index c90b13c..14bf1c3 100644
--- a/engines/scumm/sound.cpp
+++ b/engines/scumm/sound.cpp
@@ -1335,7 +1335,8 @@ int ScummEngine::readSoundResource(ResId idx) {
}
}
- warning("Unrecognized base tag 0x%08x in sound %d", basetag, idx);
+ if (total_size)
+ warning("Unrecognized base tag 0x%08x in sound %d", basetag, idx);
_res->_types[rtSound][idx]._roomoffs = RES_INVALID_OFFSET;
return 0;
}
Commit: 6694cdcd7389f7e5452733c8bfcfda9585223d3d
https://github.com/scummvm/scummvm/commit/6694cdcd7389f7e5452733c8bfcfda9585223d3d
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-14T21:45:56+02:00
Commit Message:
SCUMM: update news (Amiga iMuse support)
Changed paths:
NEWS.md
diff --git a/NEWS.md b/NEWS.md
index c2decb6..1459ae6 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -61,6 +61,7 @@ For a more comprehensive changelog of the latest experimental code, see:
for this to take effect when using compressed audio.
- Fixed an issue in the wig maker room in the German version of SPY Fox 3: Operation Ozone
which makes the game completable.
+ - Added sound driver for the Amiga versions of Monkey Island 2 and Indiana Jones and the Fate of Atlantis
Sherlock:
- Fixed crash in Spanish version talking to lady in Tailor shop
More information about the Scummvm-git-logs
mailing list