aboutsummaryrefslogtreecommitdiff
path: root/engines/scumm/imuse
diff options
context:
space:
mode:
authorathrxx2019-06-22 23:30:37 +0200
committerathrxx2019-07-14 20:56:27 +0200
commit9afdde2601b64f724c2e5c4d4b43ba04709a27f1 (patch)
tree47e9e22f026fd25010f67d3011c092e6f5e4c31b /engines/scumm/imuse
parentb1ae4319e53b5cbf28ce66bd7457349a2daddb12 (diff)
downloadscummvm-rg350-9afdde2601b64f724c2e5c4d4b43ba04709a27f1.tar.gz
scummvm-rg350-9afdde2601b64f724c2e5c4d4b43ba04709a27f1.tar.bz2
scummvm-rg350-9afdde2601b64f724c2e5c4d4b43ba04709a27f1.zip
SCUMM: add Amiga iMuse sound driver
(applies to MI2 and INDY4)
Diffstat (limited to 'engines/scumm/imuse')
-rw-r--r--engines/scumm/imuse/drivers/amiga.cpp875
-rw-r--r--engines/scumm/imuse/drivers/amiga.h85
-rw-r--r--engines/scumm/imuse/imuse.cpp12
3 files changed, 969 insertions, 3 deletions
diff --git a/engines/scumm/imuse/drivers/amiga.cpp b/engines/scumm/imuse/drivers/amiga.cpp
new file mode 100644
index 0000000000..4942fd5536
--- /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 0000000000..77c75077c5
--- /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 f5526ab11d..5fd5d21ff1 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;