/* 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 getNote() 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; 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) { 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->getNote()) { 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); } }