Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,879 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#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(nullptr), releaseDataSize(0), repeatData(nullptr), 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() override {}
MidiDriver *device() override { return _driver; }
byte getNumber() override { return _id; }
bool allocate();
void release() override;
void send(uint32 b) override;
void noteOff(byte note) override;
void noteOn(byte note, byte velocity) override;
void controlChange(byte control, byte value) override;
void programChange(byte program) override;
void pitchBend(int16 bend) override;
void pitchBendFactor(byte value) override;
void transpose(int8 value) override;
void detune(int16 value) override;
void priority(byte value) override;
void sysEx_customInstrument(uint32 type, const byte *instr, uint32 dataSize) override {}
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;
int8 _detune;
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(nullptr), _next(nullptr), _prev(nullptr), _sustain(false), _note(0) {
assert(id > -1 && id < 4);
_channels[id] = this;
createVolumeTable();
}
SoundChannel_Amiga::~SoundChannel_Amiga() {
_channels[_id] = nullptr;
// 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 = nullptr;
}
SoundChannel_Amiga *SoundChannel_Amiga::allocate(int prio) {
SoundChannel_Amiga *res = nullptr;
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 = nullptr;
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 = nullptr;
}
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, nullptr, 0, period);
setRepeatData(nullptr, 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(nullptr, 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 = nullptr;
SoundChannel_Amiga *SoundChannel_Amiga::_channels[4] = { nullptr, nullptr, nullptr, nullptr };
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(nullptr), _priority(0), _program(0),
_pitchBend(0), _pitchBendSensitivity(2), _volume(0), _modulation(0), _transpose(0), _detune(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) + _detune);
}
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) + _detune);
}
void IMusePart_Amiga::pitchBendFactor(byte value) {
_pitchBendSensitivity = value;
for (SoundChannel_Amiga *cur = _out; cur; cur = cur->next())
cur->transposePitchBend(_transpose, ((_pitchBend * _pitchBendSensitivity) >> 6) + _detune);
}
void IMusePart_Amiga::transpose(int8 value) {
_transpose = value << 1;
for (SoundChannel_Amiga *cur = _out; cur; cur = cur->next())
cur->transposePitchBend(_transpose, ((_pitchBend * _pitchBendSensitivity) >> 6) + _detune);
}
void IMusePart_Amiga::detune(int16 value) {
_detune = (int8)value;
for (SoundChannel_Amiga *cur = _out; cur; cur = cur->next())
cur->transposePitchBend(_transpose, ((_pitchBend * _pitchBendSensitivity) >> 6) + _detune);
}
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(nullptr), _timerProcPara(nullptr), _parts(nullptr), _chan(nullptr), _instruments(nullptr), _missingFiles(0), _ticker(0) {
setAudioFilter(true);
_instruments = new 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 = nullptr;
if (_parts) {
for (int i = 0; i < _numParts; i++)
delete _parts[i];
delete[] _parts;
}
_parts = nullptr;
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::U32String 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);
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;
stopPaula();
setTimerCallback(nullptr, nullptr);
_mixer->stopHandle(_soundHandle);
Common::StackLock lock(_mutex);
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 nullptr;
for (int i = 0; i < _numParts; ++i) {
if (_parts[i]->allocate())
return _parts[i];
}
return nullptr;
}
MidiChannel *IMuseDriver_Amiga::getPercussionChannel() {
return nullptr;
}
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 const 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 = nullptr;
}
}
for (int fileNo = 1; fileNo != -1 && !ims.isOpen(); ) {
if (!ims.open(Common::Path(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);
}
}

View File

@@ -0,0 +1,84 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#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() override;
int open() override;
bool isOpen() const override { return _isOpen; }
void close() override;
void send(uint32 b) override;
void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override;
uint32 getBaseTempo() override;
MidiChannel *allocateChannel() override;
MidiChannel *getPercussionChannel() override;
void interrupt() override;
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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,86 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef IMUSE_DRV_FMTOWNS_H
#define IMUSE_DRV_FMTOWNS_H
#include "audio/softsynth/fmtowns_pc98/towns_audio.h"
#include "audio/mididrv.h"
namespace Scumm {
class TownsMidiOutputChannel;
class TownsMidiInputChannel;
class TownsMidiChanState;
class IMuseDriver_FMTowns : public MidiDriver, public TownsAudioInterfacePluginDriver {
friend class TownsMidiInputChannel;
friend class TownsMidiOutputChannel;
public:
IMuseDriver_FMTowns(Audio::Mixer *mixer);
~IMuseDriver_FMTowns() override;
int open() override;
bool isOpen() const override { return _isOpen; }
void close() override;
void send(uint32 b) override;
void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override;
uint32 getBaseTempo() override;
MidiChannel *allocateChannel() override;
MidiChannel *getPercussionChannel() override;
void timerCallback(int timerId) override;
private:
void updateParser();
void updateOutputChannels();
TownsMidiOutputChannel *allocateOutputChannel(uint8 pri);
int randomValue(int para);
TownsMidiInputChannel **_channels;
TownsMidiOutputChannel **_out;
TownsMidiChanState *_chanState;
const uint8 _numParts;
Common::TimerManager::TimerProc _timerProc;
void *_timerProcPara;
TownsAudioInterface *_intf;
uint32 _tickCounter;
uint8 _allocCurPos;
uint8 _rand;
bool _isOpen;
uint8 *_operatorLevelTable;
const uint16 _baseTempo;
};
} // end of namespace Scumm
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,80 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_IMUSE_DRV_MAC_H
#define SCUMM_IMUSE_DRV_MAC_H
#include "audio/mididrv.h"
#include "scumm/players/mac_sound_lowlevel.h"
namespace Audio {
class Mixer;
}
namespace IMSMacintosh {
class IMuseChannel_Macintosh;
class IMSMacSoundSystem;
struct ChanControlNode;
} // End of namespace IMSMacintosh
namespace Scumm {
class IMuseDriver_Macintosh final : public MidiDriver {
friend class IMSMacintosh::IMuseChannel_Macintosh;
public:
IMuseDriver_Macintosh(ScummEngine *vm, Audio::Mixer *mixer, byte gameID);
virtual ~IMuseDriver_Macintosh() override;
int open() override;
void close() override;
bool isOpen() const override { return _isOpen; }
uint32 property(int prop, uint32 param) override;
void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) override;
uint32 getBaseTempo() override { return _baseTempo; }
void send(uint32 b) override { error("%s():: Not implemented", __FUNCTION__); }
// Channel allocation functions
MidiChannel *allocateChannel() override;
MidiChannel *getPercussionChannel() override;
private:
void createChannels();
void releaseChannels();
bool _isOpen;
uint32 _quality;
uint32 _musicVolume;
uint32 _sfxVolume;
IMSMacintosh::IMSMacSoundSystem *_device;
IMSMacintosh::IMuseChannel_Macintosh **_imsParts;
IMSMacintosh::ChanControlNode **_channels;
byte _numChannels;
byte _numParts;
uint32 _baseTempo;
int8 _version;
};
} // End of namespace Scumm
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,117 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_IMUSE_DRV_MIDI_H
#define SCUMM_IMUSE_DRV_MIDI_H
#include "audio/mididrv.h"
namespace IMSMidi {
class IMuseChannel_Midi;
class IMuseChannel_MT32;
struct ChannelNode;
} // End of namespace IMSMidi
namespace Scumm {
class IMuseDriver_GMidi : public MidiDriver {
friend class IMSMidi::IMuseChannel_Midi;
public:
IMuseDriver_GMidi(MidiDriver::DeviceHandle dev, bool rolandGSMode, bool newSystem);
virtual ~IMuseDriver_GMidi() override;
int open() override;
void close() override;
// Just pass these through...
bool isOpen() const override { return _drv ? _drv->isOpen() : false; }
uint32 property(int prop, uint32 param) override { return _drv ? _drv->property(prop, param) : 0; }
void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) override { if (_drv) _drv->setTimerCallback(timerParam, timerProc); }
uint32 getBaseTempo() override { return _drv ? _drv->getBaseTempo() : 0; }
void send(uint32 b) override { if (_drv && trackMidiState(b)) _drv->send(b); };
void sysEx(const byte *msg, uint16 length) override { if (_drv) _drv->sysEx(msg, length); }
virtual void setPitchBendRange(byte channel, uint range) override { if (_drv) _drv->setPitchBendRange(channel, range); }
// Channel allocation functions
MidiChannel *allocateChannel() override;
MidiChannel *getPercussionChannel() override;
protected:
IMSMidi::IMuseChannel_Midi *getPart(int number);
virtual void createChannels();
virtual void createParts();
virtual void releaseChannels();
MidiDriver *_drv;
const bool _newSystem;
byte _numChannels;
byte _numVoices;
IMSMidi::IMuseChannel_Midi **_imsParts;
bool _noProgramTracking;
private:
virtual void initDevice();
void initRolandGSMode();
virtual void deinitDevice();
void setNoteFlag(byte chan, byte note) { if (_notesPlaying && chan < 16 && note < 128) _notesPlaying[note] |= (1 << chan); }
void clearNoteFlag(byte chan, byte note) { if (_notesPlaying && chan < 16 && note < 128) _notesPlaying[note] &= ~(1 << chan); }
bool queryNoteFlag(byte chan, byte note) const { return (_notesPlaying && chan < 16 && note < 128) ? _notesPlaying[note] & (1 << chan) : false; }
void setSustainFlag(byte chan, byte note) { if (_notesSustained && chan < 16 && note < 128) _notesSustained[note] |= (1 << chan); }
void clearSustainFlag(byte chan, byte note) { if (_notesSustained && chan < 16 && note < 128) _notesSustained[note] &= ~(1 << chan); }
bool querySustainFlag(byte chan, byte note) const { return (_notesSustained && chan < 16 && note < 128) ? _notesSustained[note] & (1 << chan) : false; }
bool trackMidiState(uint32 b);
const bool _gsMode;
IMSMidi::ChannelNode *_idleChain;
IMSMidi::ChannelNode *_activeChain;
uint16 *_notesPlaying;
uint16 *_notesSustained;
byte *_midiRegState;
};
class IMuseDriver_MT32 final : public IMuseDriver_GMidi {
friend class IMSMidi::IMuseChannel_MT32;
public:
IMuseDriver_MT32(MidiDriver::DeviceHandle dev, bool newSystem);
~IMuseDriver_MT32() override {}
private:
void initDevice() override;
void deinitDevice() override;
void createChannels() override;
void createParts() override;
void releaseChannels() override;
// Convenience function that allows to send the sysex message with the exact same arguments as they are used in the original drivers.
void sendMT32Sysex(uint32 addr, const byte *data, uint32 dataSize);
IMSMidi::ChannelNode *_hwRealChain;
const byte *_programsMapping;
};
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,840 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "scumm/imuse/drivers/pcspk.h"
#include "common/util.h"
namespace Scumm {
IMuseDriver_PCSpk::IMuseDriver_PCSpk(Audio::Mixer *mixer)
: MidiDriver_Emulated(mixer), _pcSpk(mixer->getOutputRate()) , _activeChannel(nullptr), _lastActiveChannel(nullptr), _lastActiveOut(0), _effectTimer(0), _randBase(1) {
memset(_channels, 0, sizeof(_channels));
}
IMuseDriver_PCSpk::~IMuseDriver_PCSpk() {
close();
}
int IMuseDriver_PCSpk::open() {
if (_isOpen)
return MERR_ALREADY_OPEN;
MidiDriver_Emulated::open();
for (uint i = 0; i < 6; ++i) {
delete _channels[i];
_channels[i] = new MidiChannel_PcSpk(this, i);
}
_activeChannel = nullptr;
_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 = nullptr;
_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 IMuseDriver_PCSpk::close() {
if (!_isOpen)
return;
_isOpen = false;
_mixer->stopHandle(_mixerSoundHandle);
for (uint i = 0; i < 6; ++i)
delete _channels[i];
}
void IMuseDriver_PCSpk::send(uint32 d) {
assert((d & 0x0F) < 6);
_channels[(d & 0x0F)]->send(d);
}
MidiChannel *IMuseDriver_PCSpk::allocateChannel() {
for (uint i = 0; i < 6; ++i) {
if (_channels[i]->allocate())
return _channels[i];
}
return nullptr;
}
void IMuseDriver_PCSpk::generateSamples(int16 *buf, int len) {
_pcSpk.readBuffer(buf, len);
}
void IMuseDriver_PCSpk::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 = nullptr;
_lastActiveOut = 0;
}
}
void IMuseDriver_PCSpk::updateNote() {
uint8 priority = 0;
_activeChannel = nullptr;
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 == nullptr || _activeChannel->_tl == 0) {
_pcSpk.stop();
_lastActiveChannel = nullptr;
_lastActiveOut = 0;
} else {
output(_activeChannel->_pitchBend + (_activeChannel->_out.note << 7));
}
}
void IMuseDriver_PCSpk::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;
}
}
IMuseDriver_PCSpk::MidiChannel_PcSpk::MidiChannel_PcSpk(IMuseDriver_PCSpk *owner, byte number) : MidiChannel(), _owner(owner), _number(number), _allocated(false),
_priority(0), _tl(0), _modWheel(0), _pitchBend(0), /*_programNr(0), */_sustain(0), _pitchBendFactor(2), _pitchBendTmp(0), _transpose(0), _detune(0) {
memset(&_out, 0, sizeof(_out));
memset(_instrument, 0, sizeof(_instrument));
}
bool IMuseDriver_PCSpk::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;
}
void IMuseDriver_PCSpk::MidiChannel_PcSpk::release() {
_out.active = 0;
_allocated = false;
_owner->updateNote();
}
void IMuseDriver_PCSpk::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 IMuseDriver_PCSpk::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 IMuseDriver_PCSpk::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(IMuseDriver_PCSpk::_outInstrumentData))
_out.instrument = _owner->_outInstrumentData + _instrument[4] * 256;
else
_out.instrument = nullptr;
_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 = nullptr;
_owner->_lastActiveOut = 0;
}
_owner->updateNote();
_out.unkC += IMuseDriver_PCSpk::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 IMuseDriver_PCSpk::MidiChannel_PcSpk::programChange(byte program) {
// Nothing to implement here, the iMuse code takes care of passing us the
// instrument data.
}
void IMuseDriver_PCSpk::MidiChannel_PcSpk::pitchBend(int16 bend) {
_pitchBendTmp = bend;
_pitchBend = (_transpose << 7) + ((_pitchBendTmp * _pitchBendFactor) >> 6) + _detune;
}
void IMuseDriver_PCSpk::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 = nullptr;
_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 IMuseDriver_PCSpk::MidiChannel_PcSpk::pitchBendFactor(byte value) {
_pitchBendFactor = value;
}
void IMuseDriver_PCSpk::MidiChannel_PcSpk::transpose(int8 value) {
_transpose = value;
_pitchBend = (_transpose << 7) + ((_pitchBendTmp * _pitchBendFactor) >> 6) + _detune;
}
void IMuseDriver_PCSpk::MidiChannel_PcSpk::detune(int16 value) {
_detune = (int8)value;
_pitchBend = (_transpose << 7) + ((_pitchBendTmp * _pitchBendFactor) >> 6) + _detune;
}
void IMuseDriver_PCSpk::MidiChannel_PcSpk::priority(byte value) {
_priority = value;
}
void IMuseDriver_PCSpk::MidiChannel_PcSpk::sysEx_customInstrument(uint32 type, const byte *instr, uint32 dataSize) {
if (type == 'SPK ' && instr && dataSize == sizeof(_instrument))
memcpy(_instrument, instr, sizeof(_instrument));
else if (type != 'SPK ')
warning("MidiChannel_PcSpk: Receiving '%c%c%c%c' instrument data. Probably loading a savegame with that sound setting", (type >> 24) & 0xFF, (type >> 16) & 0xFF, (type >> 8) & 0xFF, type & 0xFF);
}
uint8 IMuseDriver_PCSpk::getEffectModifier(uint16 level) {
uint8 base = level / 32;
uint8 index = level % 32;
if (index == 0)
return 0;
return (base * (index + 1)) >> 5;
}
int16 IMuseDriver_PCSpk::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 IMuseDriver_PCSpk::getRandScale(int16 input) {
if (_randBase & 1)
_randBase = (_randBase >> 1) ^ 0xB8;
else
_randBase >>= 1;
return (_randBase * input) >> 8;
}
void IMuseDriver_PCSpk::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 IMuseDriver_PCSpk::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 IMuseDriver_PCSpk::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 IMuseDriver_PCSpk::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 = nullptr;
break;
case 5:
env.modWheelState = (def.phase & 0xFF);
break;
case 6:
env.modWheelSensitivity = (def.phase & 0xFF);
break;
default:
break;
}
}
}
uint8 IMuseDriver_PCSpk::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 IMuseDriver_PCSpk::_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 IMuseDriver_PCSpk::_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 IMuseDriver_PCSpk::_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 IMuseDriver_PCSpk::_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 IMuseDriver_PCSpk::_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

View File

@@ -0,0 +1,166 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_IMUSE_PCSPK_H
#define SCUMM_IMUSE_PCSPK_H
#include "audio/softsynth/emumidi.h"
#include "audio/softsynth/pcspk.h"
namespace Scumm {
class IMuseDriver_PCSpk : public MidiDriver_Emulated {
public:
IMuseDriver_PCSpk(Audio::Mixer *mixer);
~IMuseDriver_PCSpk() override;
int open() override;
void close() override;
void send(uint32 d) override;
MidiChannel *allocateChannel() override;
MidiChannel *getPercussionChannel() override { return nullptr; }
bool isStereo() const override { return _pcSpk.isStereo(); }
int getRate() const override { return _pcSpk.getRate(); }
protected:
void generateSamples(int16 *buf, int len) override;
void onTimer() override;
private:
Audio::PCSpeakerStream _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;
};
class MidiChannel_PcSpk: public MidiChannel {
public:
MidiChannel_PcSpk(IMuseDriver_PCSpk *owner, byte number);
MidiDriver *device() override { return _owner; }
byte getNumber() override { return _number; }
void release() override;
void send(uint32 b) override;
void noteOff(byte note) override;
void noteOn(byte note, byte velocity) override;
void programChange(byte program) override;
void pitchBend(int16 bend) override;
void controlChange(byte control, byte value) override;
void pitchBendFactor(byte value) override;
void transpose(int8 value) override;
void detune(int16 value) override;
void priority(byte value) override;
void sysEx_customInstrument(uint32 type, const byte *instr, uint32 dataSize) override;
bool allocate();
bool _allocated;
OutputChannel _out;
uint8 _instrument[23];
uint8 _priority;
uint8 _tl;
uint8 _modWheel;
int16 _pitchBend;
private:
IMuseDriver_PCSpk *_owner;
const byte _number;
//uint8 _programNr;
uint8 _sustain;
uint8 _pitchBendFactor;
int16 _pitchBendTmp;
int8 _transpose;
int8 _detune;
};
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