Initial commit
This commit is contained in:
879
engines/scumm/imuse/drivers/amiga.cpp
Normal file
879
engines/scumm/imuse/drivers/amiga.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
84
engines/scumm/imuse/drivers/amiga.h
Normal file
84
engines/scumm/imuse/drivers/amiga.h
Normal 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
|
||||
1048
engines/scumm/imuse/drivers/fmtowns.cpp
Normal file
1048
engines/scumm/imuse/drivers/fmtowns.cpp
Normal file
File diff suppressed because it is too large
Load Diff
86
engines/scumm/imuse/drivers/fmtowns.h
Normal file
86
engines/scumm/imuse/drivers/fmtowns.h
Normal 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
|
||||
1491
engines/scumm/imuse/drivers/macintosh.cpp
Normal file
1491
engines/scumm/imuse/drivers/macintosh.cpp
Normal file
File diff suppressed because it is too large
Load Diff
80
engines/scumm/imuse/drivers/macintosh.h
Normal file
80
engines/scumm/imuse/drivers/macintosh.h
Normal 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
|
||||
1063
engines/scumm/imuse/drivers/midi.cpp
Normal file
1063
engines/scumm/imuse/drivers/midi.cpp
Normal file
File diff suppressed because it is too large
Load Diff
117
engines/scumm/imuse/drivers/midi.h
Normal file
117
engines/scumm/imuse/drivers/midi.h
Normal 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
|
||||
840
engines/scumm/imuse/drivers/pcspk.cpp
Normal file
840
engines/scumm/imuse/drivers/pcspk.cpp
Normal 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
|
||||
166
engines/scumm/imuse/drivers/pcspk.h
Normal file
166
engines/scumm/imuse/drivers/pcspk.h
Normal 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
|
||||
1784
engines/scumm/imuse/imuse.cpp
Normal file
1784
engines/scumm/imuse/imuse.cpp
Normal file
File diff suppressed because it is too large
Load Diff
84
engines/scumm/imuse/imuse.h
Normal file
84
engines/scumm/imuse/imuse.h
Normal 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 SCUMM_IMUSE_H
|
||||
#define SCUMM_IMUSE_H
|
||||
|
||||
#include "audio/mididrv.h"
|
||||
#include "common/scummsys.h"
|
||||
#include "common/serializer.h"
|
||||
#include "common/mutex.h"
|
||||
#include "scumm/music.h"
|
||||
|
||||
class MidiDriver;
|
||||
class OSystem;
|
||||
|
||||
namespace Scumm {
|
||||
|
||||
class IMuseInternal;
|
||||
class Player;
|
||||
class ScummEngine;
|
||||
|
||||
typedef void (*sysexfunc)(Player *, const byte *, uint16);
|
||||
|
||||
/**
|
||||
* iMuse implementation interface.
|
||||
* MusicEngine derivative for state-tracked, interactive,
|
||||
* persistent event-based music playback and control.
|
||||
* This class serves as an interface to actual implementations
|
||||
* so that client code is not exposed to the details of
|
||||
* any specific implementation.
|
||||
*/
|
||||
class IMuse : public MusicEngine {
|
||||
public:
|
||||
enum {
|
||||
PROP_TEMPO_BASE,
|
||||
PROP_LIMIT_PLAYERS,
|
||||
PROP_RECYCLE_PLAYERS,
|
||||
PROP_QUALITY,
|
||||
PROP_MUSICVOLUME,
|
||||
PROP_SFXVOLUME
|
||||
};
|
||||
|
||||
public:
|
||||
virtual void on_timer(MidiDriver *midi) = 0;
|
||||
virtual void pause(bool paused) = 0;
|
||||
virtual void saveLoadIMuse(Common::Serializer &ser, ScummEngine *scumm, bool fixAfterLoad = true) = 0;
|
||||
virtual bool get_sound_active(int sound) const = 0;
|
||||
virtual int32 doCommand(int numargs, int args[]) = 0;
|
||||
virtual int clear_queue() = 0;
|
||||
virtual uint32 property(int prop, uint32 value) = 0;
|
||||
virtual void addSysexHandler(byte mfgID, sysexfunc handler) = 0;
|
||||
|
||||
public:
|
||||
virtual void startSoundWithNoteOffset(int sound, int offset) = 0;
|
||||
|
||||
// MusicEngine base class methods. Only this one is implemented:
|
||||
void setQuality(int qual) override { property(PROP_QUALITY, qual); }
|
||||
|
||||
public:
|
||||
// Factory methods
|
||||
static IMuse *create(ScummEngine *vm, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver, MidiDriverFlags sndType, bool nativeMT32);
|
||||
};
|
||||
|
||||
} // End of namespace Scumm
|
||||
|
||||
#endif
|
||||
602
engines/scumm/imuse/imuse_internal.h
Normal file
602
engines/scumm/imuse/imuse_internal.h
Normal file
@@ -0,0 +1,602 @@
|
||||
/* 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_INTERNAL
|
||||
#define SCUMM_IMUSE_INTERNAL
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/serializer.h"
|
||||
#include "scumm/imuse/imuse.h"
|
||||
#include "scumm/imuse/instrument.h"
|
||||
#include "audio/mididrv.h"
|
||||
|
||||
class MidiParser;
|
||||
class OSystem;
|
||||
|
||||
namespace Scumm {
|
||||
|
||||
struct ParameterFader;
|
||||
struct DeferredCommand;
|
||||
struct ImTrigger;
|
||||
struct SustainingNotes;
|
||||
struct CommandQueue;
|
||||
struct IsNoteCmdData;
|
||||
class Player;
|
||||
struct Part;
|
||||
class IMuseInternal;
|
||||
class IMuseSysex_Scumm;
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//
|
||||
// Some constants
|
||||
//
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
#define TICKS_PER_BEAT 480
|
||||
|
||||
#define TRIGGER_ID 0
|
||||
#define COMMAND_ID 1
|
||||
|
||||
#define MUS_REDUCTION_TIMER_TICKS 16667 // 60 Hz
|
||||
|
||||
|
||||
////////////////////////////////////////
|
||||
//
|
||||
// Helper functions
|
||||
//
|
||||
////////////////////////////////////////
|
||||
|
||||
inline int clamp(int val, int min, int max) {
|
||||
if (val < min)
|
||||
return min;
|
||||
if (val > max)
|
||||
return max;
|
||||
return val;
|
||||
}
|
||||
|
||||
inline int transpose_clamp(int a, int b, int c) {
|
||||
if (b > a)
|
||||
a += (b - a + 11) / 12 * 12;
|
||||
if (c < a)
|
||||
a -= (a - c + 11) / 12 * 12;
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//
|
||||
// Entity declarations
|
||||
//
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
struct TimerCallbackInfo {
|
||||
IMuseInternal *imuse = nullptr;
|
||||
MidiDriver *driver = nullptr;
|
||||
};
|
||||
|
||||
struct HookDatas {
|
||||
byte _jump[2];
|
||||
byte _transpose;
|
||||
byte _part_onoff[16];
|
||||
byte _part_volume[16];
|
||||
byte _part_program[16];
|
||||
byte _part_transpose[16];
|
||||
|
||||
int query_param(int param, byte chan);
|
||||
int set(byte cls, byte value, byte chan);
|
||||
HookDatas() { reset(); }
|
||||
void reset() {
|
||||
_transpose = 0;
|
||||
memset(_jump, 0, sizeof(_jump));
|
||||
memset(_part_onoff, 0, sizeof(_part_onoff));
|
||||
memset(_part_volume, 0, sizeof(_part_volume));
|
||||
memset(_part_program, 0, sizeof(_part_program));
|
||||
memset(_part_transpose, 0, sizeof(_part_transpose));
|
||||
}
|
||||
};
|
||||
|
||||
struct ParameterFader {
|
||||
enum {
|
||||
pfVolume = 1,
|
||||
pfTranspose = 3,
|
||||
pfSpeed = 4
|
||||
};
|
||||
|
||||
int param;
|
||||
int8 dir;
|
||||
int16 incr;
|
||||
uint16 ifrac;
|
||||
uint16 irem;
|
||||
uint16 ttime;
|
||||
uint16 cntdwn;
|
||||
int16 state;
|
||||
|
||||
ParameterFader() : param(0), dir(0), incr(0), ifrac(0), irem(0), ttime(0), cntdwn(0), state(0) {}
|
||||
void init() { param = 0; }
|
||||
};
|
||||
|
||||
struct DeferredCommand {
|
||||
uint32 time_left;
|
||||
int a, b, c, d, e, f;
|
||||
DeferredCommand() { memset(this, 0, sizeof(DeferredCommand)); }
|
||||
};
|
||||
|
||||
struct ImTrigger {
|
||||
int sound;
|
||||
byte id;
|
||||
uint16 expire;
|
||||
int command[8];
|
||||
ImTrigger() { memset(this, 0, sizeof(ImTrigger)); }
|
||||
};
|
||||
|
||||
struct CommandQueue {
|
||||
uint16 array[8];
|
||||
CommandQueue() { memset(this, 0, sizeof(CommandQueue)); }
|
||||
};
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//
|
||||
// Player class definition
|
||||
//
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
class Player : public MidiDriver_BASE, public Common::Serializable {
|
||||
/*
|
||||
* External SysEx handler functions shall each be defined in
|
||||
* a separate file. This header file shall be included at the
|
||||
* top of the file immediately following this special #define:
|
||||
* #define SYSEX_CALLBACK_FUNCTION nameOfHandlerFunction
|
||||
*/
|
||||
#ifdef SYSEX_CALLBACK_FUNCTION
|
||||
friend void SYSEX_CALLBACK_FUNCTION(Player *, const byte *, uint16);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
// Moved from IMuseInternal.
|
||||
// This is only used by one player at a time.
|
||||
static uint16 _active_notes[128];
|
||||
|
||||
protected:
|
||||
enum ParserType {
|
||||
kParserTypeNone = 0,
|
||||
kParserTypeRO,
|
||||
kParserTypeXMI,
|
||||
kParserTypeSMF
|
||||
};
|
||||
|
||||
MidiDriver *_midi;
|
||||
MidiParser *_parser;
|
||||
ParserType _parserType;
|
||||
|
||||
Part *_parts;
|
||||
bool _active;
|
||||
bool _scanning;
|
||||
int _id;
|
||||
byte _priority;
|
||||
byte _volume;
|
||||
int8 _pan;
|
||||
int8 _transpose;
|
||||
int16 _detune;
|
||||
int _note_offset;
|
||||
byte _vol_eff;
|
||||
|
||||
uint _track_index;
|
||||
uint _loop_to_beat;
|
||||
uint _loop_from_beat;
|
||||
uint _loop_counter;
|
||||
uint _loop_to_tick;
|
||||
uint _loop_from_tick;
|
||||
byte _speed;
|
||||
bool _abort;
|
||||
|
||||
uint32 _transitionTimer;
|
||||
|
||||
// This does not get used by us! It is only
|
||||
// here for save/load purposes, and gets
|
||||
// passed on to the MidiParser during
|
||||
// fixAfterLoad().
|
||||
uint32 _music_tick;
|
||||
|
||||
HookDatas _hook;
|
||||
ParameterFader _parameterFaders[4];
|
||||
|
||||
bool _isMIDI;
|
||||
bool _isMT32;
|
||||
bool _supportsPercussion;
|
||||
|
||||
protected:
|
||||
// Player part
|
||||
void hook_clear();
|
||||
void uninit_parts();
|
||||
void part_set_transpose(uint8 chan, byte relative, int8 b);
|
||||
void maybe_jump(byte cmd, uint track, uint beat, uint tick);
|
||||
void maybe_set_transpose(byte *data);
|
||||
void maybe_part_onoff(byte *data);
|
||||
void maybe_set_volume(byte *data);
|
||||
void maybe_set_program(byte *data);
|
||||
void maybe_set_transpose_part(byte *data);
|
||||
void turn_off_pedals();
|
||||
int query_part_param(int param, byte chan);
|
||||
void turn_off_parts();
|
||||
void play_active_notes();
|
||||
|
||||
void transitionParameters();
|
||||
|
||||
static void decode_sysex_bytes(const byte *src, byte *dst, int len);
|
||||
|
||||
// Sequencer part
|
||||
int start_seq_sound(int sound, bool reset_vars = true);
|
||||
void loadStartParameters(int sound);
|
||||
|
||||
public:
|
||||
IMuseInternal *_se;
|
||||
uint _vol_chan;
|
||||
|
||||
public:
|
||||
Player();
|
||||
~Player() override;
|
||||
|
||||
int addParameterFader(int param, int target, int time);
|
||||
void clear();
|
||||
void clearLoop();
|
||||
void fixAfterLoad();
|
||||
Part *getActivePart(uint8 part);
|
||||
uint getBeatIndex();
|
||||
int16 getDetune() const { return _detune; }
|
||||
byte getEffectiveVolume() const { return _vol_eff; }
|
||||
int getID() const { return _id; }
|
||||
MidiDriver *getMidiDriver() const { return _midi; }
|
||||
int getParam(int param, byte chan);
|
||||
int8 getPan() const { return _pan; }
|
||||
Part *getPart(uint8 part);
|
||||
byte getPriority() const { return _priority; }
|
||||
uint getTicksPerBeat() const { return TICKS_PER_BEAT; }
|
||||
int8 getTranspose() const { return _transpose; }
|
||||
byte getVolume() const { return _volume; }
|
||||
bool isActive() const { return _active; }
|
||||
bool isFadingOut() const;
|
||||
bool isMIDI() const { return _isMIDI; }
|
||||
bool isMT32() const { return _isMT32; }
|
||||
bool jump(uint track, uint beat, uint tick);
|
||||
void onTimer();
|
||||
void removePart(Part *part);
|
||||
int scan(uint totrack, uint tobeat, uint totick);
|
||||
void saveLoadWithSerializer(Common::Serializer &ser) override;
|
||||
int setHook(byte cls, byte value, byte chan) { return _hook.set(cls, value, chan); }
|
||||
void setDetune(int detune);
|
||||
void setOffsetNote(int offset);
|
||||
bool setLoop(uint count, uint tobeat, uint totick, uint frombeat, uint fromtick);
|
||||
void setPan(int pan);
|
||||
void setPriority(int pri);
|
||||
void setSpeed(byte speed);
|
||||
int setTranspose(byte relative, int b);
|
||||
int setVolume(byte vol);
|
||||
bool startSound(int sound, MidiDriver *midi);
|
||||
int getMusicTimer() const;
|
||||
|
||||
public:
|
||||
// MidiDriver interface
|
||||
void send(uint32 b) override;
|
||||
void sysEx(const byte *msg, uint16 length) override;
|
||||
uint16 sysExNoDelay(const byte *msg, uint16 length) override;
|
||||
void metaEvent(byte type, const byte *data, uint16 length) override;
|
||||
};
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//
|
||||
// Part pseudo-class definition
|
||||
//
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
struct Part : public Common::Serializable {
|
||||
IMuseInternal *_se;
|
||||
int _slot;
|
||||
Part *_next, *_prev;
|
||||
MidiChannel *_mc;
|
||||
Player *_player;
|
||||
int16 _pitchbend;
|
||||
byte _pitchbend_factor;
|
||||
byte _volControlSensitivity;
|
||||
int8 _transpose, _transpose_eff;
|
||||
byte _vol, _vol_eff;
|
||||
int8 _detune;
|
||||
int16 _detune_eff;
|
||||
int8 _pan, _pan_eff;
|
||||
byte _polyphony;
|
||||
bool _on;
|
||||
byte _modwheel;
|
||||
bool _pedal;
|
||||
int8 _pri;
|
||||
byte _pri_eff;
|
||||
byte _chan;
|
||||
byte _effect_level;
|
||||
byte _chorus;
|
||||
byte _percussion;
|
||||
byte _bank;
|
||||
|
||||
// New abstract instrument definition
|
||||
Instrument _instrument;
|
||||
bool _unassigned_instrument; // For diagnostic reporting purposes only
|
||||
|
||||
// MidiChannel interface
|
||||
// (We don't currently derive from MidiChannel,
|
||||
// but if we ever do, this will make it easy.)
|
||||
void noteOff(byte note);
|
||||
void noteOn(byte note, byte velocity);
|
||||
void programChange(byte value);
|
||||
void pitchBend(int16 value);
|
||||
void modulationWheel(byte value);
|
||||
void volume(byte value);
|
||||
void volControlSensitivity(byte value);
|
||||
void pitchBendFactor(byte value);
|
||||
void sustain(bool value);
|
||||
void effectLevel(byte value);
|
||||
void chorusLevel(byte value);
|
||||
void allNotesOff();
|
||||
|
||||
void set_param(byte param, int value) { }
|
||||
void init(bool useNativeMT32);
|
||||
void setup(Player *player);
|
||||
void uninit();
|
||||
void off();
|
||||
void set_instrument(uint b);
|
||||
void set_instrument(byte *data);
|
||||
void load_global_instrument(byte b);
|
||||
|
||||
void set_transpose(int8 transpose, int8 clipRangeLow, int8 clipRangeHi);
|
||||
void set_detune(int8 detune);
|
||||
void set_pri(int8 pri);
|
||||
void set_pan(int8 pan);
|
||||
|
||||
void set_polyphony(byte val);
|
||||
void set_onoff(bool on);
|
||||
void fix_after_load();
|
||||
|
||||
void sendAll();
|
||||
bool clearToTransmit();
|
||||
|
||||
Part();
|
||||
|
||||
void saveLoadWithSerializer(Common::Serializer &ser) override;
|
||||
|
||||
private:
|
||||
void sendPitchBend();
|
||||
void sendVolume(int8 fadeModifier);
|
||||
void sendVolumeFade();
|
||||
void sendTranspose();
|
||||
void sendDetune();
|
||||
void sendPanPosition(uint8 value);
|
||||
void sendEffectLevel(uint8 value);
|
||||
void sendPolyphony();
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* SCUMM implementation of IMuse.
|
||||
* This class implements the IMuse mixin interface for the SCUMM environment.
|
||||
*/
|
||||
class IMuseInternal : public IMuse {
|
||||
friend class Player;
|
||||
friend struct Part;
|
||||
|
||||
/*
|
||||
* External SysEx handler functions shall each be defined in
|
||||
* a separate file. This header file shall be included at the
|
||||
* top of the file immediately following this special #define:
|
||||
* #define SYSEX_CALLBACK_FUNCTION nameOfHandlerFunction
|
||||
*/
|
||||
#ifdef SYSEX_CALLBACK_FUNCTION
|
||||
friend void SYSEX_CALLBACK_FUNCTION(Player *, const byte *, uint16);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
ScummEngine *_vm;
|
||||
const bool _native_mt32;
|
||||
const bool _newSystem;
|
||||
const bool _dynamicChanAllocation;
|
||||
const MidiDriverFlags _soundType;
|
||||
MidiDriver *_midi_adlib;
|
||||
MidiDriver *_midi_native;
|
||||
TimerCallbackInfo _timer_info_adlib;
|
||||
TimerCallbackInfo _timer_info_native;
|
||||
|
||||
const uint32 _game_id;
|
||||
|
||||
// Plug-in SysEx handling. Right now this only supports one
|
||||
// custom SysEx handler for the hardcoded IMUSE_SYSEX_ID
|
||||
// manufacturer code. TODO: Expand this to support multiple
|
||||
// SysEx handlers for client-specified manufacturer codes.
|
||||
sysexfunc _sysex;
|
||||
|
||||
Common::Mutex &_mutex;
|
||||
Common::Mutex _dummyMutex;
|
||||
|
||||
protected:
|
||||
bool _paused;
|
||||
bool _initialized;
|
||||
|
||||
int _tempoFactor;
|
||||
|
||||
int _player_limit; // Limits how many simultaneous music tracks are played
|
||||
bool _recycle_players; // Can we stop a player in order to start another one?
|
||||
|
||||
int _musicVolumeReductionTimer = 0; // 60 Hz
|
||||
|
||||
uint _queue_end, _queue_pos, _queue_sound;
|
||||
byte _queue_adding;
|
||||
|
||||
byte _queue_marker;
|
||||
byte _queue_cleared;
|
||||
byte _master_volume; // Master volume. 0-255
|
||||
byte _music_volume; // Music volume which can be reduced during speech. 0-255
|
||||
byte _music_volume_eff; // Global effective music volume. 0-255
|
||||
|
||||
uint16 _trigger_count;
|
||||
ImTrigger _snm_triggers[16]; // Sam & Max triggers
|
||||
uint16 _snm_trigger_index;
|
||||
|
||||
uint16 _channel_volume[8];
|
||||
uint16 _channel_volume_eff[8]; // No Save
|
||||
uint16 _volchan_table[8];
|
||||
|
||||
Player _players[8];
|
||||
Part _parts[32];
|
||||
|
||||
Instrument _global_instruments[32];
|
||||
CommandQueue _cmd_queue[64];
|
||||
DeferredCommand _deferredCommands[4];
|
||||
|
||||
// These are basically static vars in the original drivers
|
||||
struct RhyState {
|
||||
RhyState() : RhyState(127, 1, 0) {}
|
||||
RhyState(byte volume, byte polyphony, byte priority) : vol(volume), poly(polyphony), prio(priority) {}
|
||||
byte vol;
|
||||
byte poly;
|
||||
byte prio;
|
||||
} _rhyState;
|
||||
|
||||
protected:
|
||||
IMuseInternal(ScummEngine *vm, MidiDriverFlags sndType, bool nativeMT32);
|
||||
~IMuseInternal() override;
|
||||
|
||||
int initialize(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver);
|
||||
|
||||
static void midiTimerCallback(void *data);
|
||||
void on_timer(MidiDriver *midi) override;
|
||||
|
||||
enum ChunkType {
|
||||
kMThd = 1,
|
||||
kFORM = 2,
|
||||
kMDhd = 4, // Used in MI2 and INDY4. Contain certain start parameters (priority, volume, etc. ) for the player.
|
||||
kMDpg = 8 // These chunks exist in DOTT and SAMNMAX. They don't get processed, however.
|
||||
};
|
||||
|
||||
byte *findStartOfSound(int sound, int ct = (kMThd | kFORM));
|
||||
bool isMT32(int sound);
|
||||
bool isMIDI(int sound);
|
||||
bool supportsPercussion(int sound);
|
||||
int get_queue_sound_status(int sound) const;
|
||||
void handle_marker(uint id, byte data);
|
||||
int get_channel_volume(uint a);
|
||||
void initMidiDriver(TimerCallbackInfo *info);
|
||||
void init_players();
|
||||
void init_parts();
|
||||
void init_queue();
|
||||
|
||||
void sequencer_timers(MidiDriver *midi);
|
||||
|
||||
MidiDriver *getBestMidiDriver(int sound);
|
||||
Player *allocate_player(byte priority);
|
||||
Part *allocate_part(byte pri, MidiDriver *midi);
|
||||
|
||||
int32 ImSetTrigger(int sound, int id, int a, int b, int c, int d, int e, int f, int g, int h);
|
||||
int32 ImClearTrigger(int sound, int id);
|
||||
int32 ImFireAllTriggers(int sound);
|
||||
|
||||
void addDeferredCommand(int time, int a, int b, int c, int d, int e, int f);
|
||||
void handleDeferredCommands(MidiDriver *midi);
|
||||
|
||||
int enqueue_command(int a, int b, int c, int d, int e, int f, int g);
|
||||
int enqueue_trigger(int sound, int marker);
|
||||
int clear_queue() override;
|
||||
int query_queue(int param);
|
||||
Player *findActivePlayer(int id);
|
||||
|
||||
int get_volchan_entry(uint a);
|
||||
int set_volchan_entry(uint a, uint b);
|
||||
int set_channel_volume(uint chan, uint vol);
|
||||
void update_volumes();
|
||||
void musicVolumeReduction(MidiDriver *midi);
|
||||
|
||||
int set_volchan(int sound, int volchan);
|
||||
|
||||
void fix_parts_after_load();
|
||||
void fix_players_after_load(ScummEngine *scumm);
|
||||
int setImuseMasterVolume(uint vol);
|
||||
|
||||
MidiChannel *allocateChannel(MidiDriver *midi, byte prio);
|
||||
bool reassignChannelAndResumePart(MidiChannel *mc);
|
||||
void suspendPart(Part *part);
|
||||
void removeSuspendedPart(Part *part);
|
||||
void reallocateMidiChannels(MidiDriver *midi);
|
||||
void setGlobalInstrument(byte slot, byte *data);
|
||||
void copyGlobalInstrument(byte slot, Instrument *dest);
|
||||
bool isNativeMT32() { return _native_mt32; }
|
||||
|
||||
protected:
|
||||
Common::Array<Part*> _waitingPartsQueue;
|
||||
|
||||
protected:
|
||||
// Internal mutex-free versions of the IMuse and MusicEngine methods.
|
||||
bool startSound_internal(int sound, int offset = 0);
|
||||
int stopSound_internal(int sound);
|
||||
int stopAllSounds_internal();
|
||||
int getSoundStatus_internal(int sound, bool ignoreFadeouts) const;
|
||||
int32 doCommand_internal(int a, int b, int c, int d, int e, int f, int g, int h);
|
||||
int32 doCommand_internal(int numargs, int args[]);
|
||||
|
||||
public:
|
||||
// IMuse interface
|
||||
void pause(bool paused) override;
|
||||
void saveLoadIMuse(Common::Serializer &ser, ScummEngine *scumm, bool fixAfterLoad = true) override;
|
||||
bool get_sound_active(int sound) const override;
|
||||
int32 doCommand(int numargs, int args[]) override;
|
||||
uint32 property(int prop, uint32 value) override;
|
||||
void addSysexHandler(byte mfgID, sysexfunc handler) override;
|
||||
|
||||
public:
|
||||
void startSoundWithNoteOffset(int sound, int offset) override;
|
||||
|
||||
// MusicEngine interface
|
||||
void setMusicVolume(int vol) override;
|
||||
void setSfxVolume(int vol) override;
|
||||
void startSound(int sound) override;
|
||||
void stopSound(int sound) override;
|
||||
void stopAllSounds() override;
|
||||
int getSoundStatus(int sound) const override;
|
||||
int getMusicTimer() override;
|
||||
|
||||
protected:
|
||||
// Our normal volume control is high-level, i. e. it uses the imuse engine to generate the proper volume values and send these to the midi driver.
|
||||
// For older titles (like MI2 and INDY4) who never had music and sfx volume controls in the original interpreters, this works well only if the
|
||||
// engine can somehow distinguish between music and sound effects. It works for targets/platforms where this can be done by resource type, where
|
||||
// the sfx resources aren't even played through the imuse engine. The imuse engine can then just assume that everything it plays is music. For
|
||||
// MI2/INDY4 Macintosh it won't work like this, because both music and sound effects have the same resource type and are played through the imuse
|
||||
// engine. For these targets it works better to pass the volume values on to the driver where other methods of distinction may be available.
|
||||
// This isn't needed for SCUMM6, since these games don't have MIDI sound effects.
|
||||
const bool _lowLevelVolumeControl;
|
||||
|
||||
public:
|
||||
// Factory function
|
||||
static IMuseInternal *create(ScummEngine *vm, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver, MidiDriverFlags sndType, bool nativeMT32);
|
||||
};
|
||||
|
||||
} // End of namespace Scumm
|
||||
|
||||
#endif
|
||||
485
engines/scumm/imuse/imuse_part.cpp
Normal file
485
engines/scumm/imuse/imuse_part.cpp
Normal file
@@ -0,0 +1,485 @@
|
||||
/* 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 "common/debug.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/util.h"
|
||||
#include "scumm/scumm.h"
|
||||
#include "scumm/imuse/imuse_internal.h"
|
||||
|
||||
namespace Scumm {
|
||||
|
||||
////////////////////////////////////////
|
||||
//
|
||||
// IMuse Part implementation
|
||||
//
|
||||
////////////////////////////////////////
|
||||
|
||||
Part::Part() {
|
||||
_slot = 0;
|
||||
_next = nullptr;
|
||||
_prev = nullptr;
|
||||
_mc = nullptr;
|
||||
_player = nullptr;
|
||||
_pitchbend = 0;
|
||||
_pitchbend_factor = 0;
|
||||
_volControlSensitivity = 127;
|
||||
_transpose = 0;
|
||||
_transpose_eff = 0;
|
||||
_vol = 0;
|
||||
_vol_eff = 0;
|
||||
_detune = 0;
|
||||
_detune_eff = 0;
|
||||
_pan = 0;
|
||||
_pan_eff = 0;
|
||||
_polyphony = 0;
|
||||
_on = false;
|
||||
_modwheel = 0;
|
||||
_pedal = false;
|
||||
_pri = 0;
|
||||
_pri_eff = 0;
|
||||
_chan = 0;
|
||||
_effect_level = 0;
|
||||
_chorus = 0;
|
||||
_percussion = 0;
|
||||
_bank = 0;
|
||||
_unassigned_instrument = false;
|
||||
_se = nullptr;
|
||||
}
|
||||
|
||||
void Part::saveLoadWithSerializer(Common::Serializer &ser) {
|
||||
int num;
|
||||
if (ser.isSaving()) {
|
||||
num = (_next ? (_next - _se->_parts + 1) : 0);
|
||||
ser.syncAsUint16LE(num);
|
||||
|
||||
num = (_prev ? (_prev - _se->_parts + 1) : 0);
|
||||
ser.syncAsUint16LE(num);
|
||||
|
||||
num = (_player ? (_player - _se->_players + 1) : 0);
|
||||
ser.syncAsUint16LE(num);
|
||||
} else {
|
||||
ser.syncAsUint16LE(num);
|
||||
_next = (num ? &_se->_parts[num - 1] : nullptr);
|
||||
|
||||
ser.syncAsUint16LE(num);
|
||||
_prev = (num ? &_se->_parts[num - 1] : nullptr);
|
||||
|
||||
ser.syncAsUint16LE(num);
|
||||
_player = (num ? &_se->_players[num - 1] : nullptr);
|
||||
}
|
||||
|
||||
ser.syncAsSint16LE(_pitchbend, VER(8));
|
||||
ser.syncAsByte(_pitchbend_factor, VER(8));
|
||||
ser.syncAsSByte(_transpose, VER(8));
|
||||
ser.syncAsByte(_vol, VER(8));
|
||||
ser.syncAsSByte(_detune, VER(8));
|
||||
ser.syncAsSByte(_pan, VER(8));
|
||||
ser.syncAsByte(_on, VER(8));
|
||||
ser.syncAsByte(_modwheel, VER(8));
|
||||
ser.syncAsByte(_pedal, VER(8));
|
||||
ser.skip(1, VER(8), VER(16)); // _program
|
||||
ser.syncAsByte(_pri, VER(8));
|
||||
ser.syncAsByte(_chan, VER(8));
|
||||
ser.syncAsByte(_effect_level, VER(8));
|
||||
ser.syncAsByte(_chorus, VER(8));
|
||||
ser.syncAsByte(_percussion, VER(8));
|
||||
ser.syncAsByte(_bank, VER(8));
|
||||
ser.syncAsByte(_polyphony, VER(116));
|
||||
ser.syncAsByte(_volControlSensitivity, VER(116));
|
||||
}
|
||||
|
||||
void Part::set_detune(int8 detune) {
|
||||
// Sam&Max does not have detune except for the parameter faders, so the argument
|
||||
// here will always be 0 and the only relevant part will be the detune from the player.
|
||||
_detune_eff = _se->_newSystem ? _player->getDetune() : clamp((_detune = detune) + _player->getDetune(), -128, 127);
|
||||
sendDetune();
|
||||
}
|
||||
|
||||
void Part::pitchBend(int16 value) {
|
||||
_pitchbend = value;
|
||||
sendPitchBend();
|
||||
}
|
||||
|
||||
void Part::volume(byte value) {
|
||||
_vol = value;
|
||||
sendVolume(0);
|
||||
}
|
||||
|
||||
void Part::volControlSensitivity(byte value) {
|
||||
if (value > 127)
|
||||
return;
|
||||
_volControlSensitivity = value;
|
||||
sendVolume(0);
|
||||
}
|
||||
|
||||
void Part::set_pri(int8 pri) {
|
||||
_pri_eff = clamp((_pri = pri) + _player->getPriority(), 0, 255);
|
||||
if (_mc)
|
||||
_mc->priority(_pri_eff);
|
||||
}
|
||||
|
||||
void Part::set_pan(int8 pan) {
|
||||
_pan_eff = clamp((_pan = pan) + _player->getPan(), -64, 63);
|
||||
sendPanPosition(_pan_eff + 0x40);
|
||||
}
|
||||
|
||||
void Part::set_polyphony(byte val) {
|
||||
if (!_se->_newSystem)
|
||||
return;
|
||||
_polyphony = val;
|
||||
if (_mc)
|
||||
_mc->controlChange(17, val);
|
||||
}
|
||||
|
||||
void Part::set_transpose(int8 transpose, int8 clipRangeLow, int8 clipRangeHi) {
|
||||
if (_se->_game_id == GID_TENTACLE && (transpose > 24 || transpose < -24))
|
||||
return;
|
||||
|
||||
_transpose = transpose;
|
||||
// The Amiga versions have a signed/unsigned bug which makes the check for _transpose == -128 impossible. They actually check for
|
||||
// a value of 128 with a signed int8 (a signed int8 can never be 128). The playback depends on this being implemented exactly
|
||||
// like in the original driver. I found this bug with the WinUAE debugger. The DOS versions do not have that bug.
|
||||
_transpose_eff = (_se->_soundType != MDT_AMIGA && _transpose == -128) ? 0 : transpose_clamp(_transpose + _player->getTranspose(), clipRangeLow, clipRangeHi);
|
||||
sendTranspose();
|
||||
}
|
||||
|
||||
void Part::sustain(bool value) {
|
||||
_pedal = value;
|
||||
if (_mc)
|
||||
_mc->sustain(value);
|
||||
}
|
||||
|
||||
void Part::modulationWheel(byte value) {
|
||||
_modwheel = value;
|
||||
if (_mc)
|
||||
_mc->modulationWheel(value);
|
||||
}
|
||||
|
||||
void Part::chorusLevel(byte value) {
|
||||
_chorus = value;
|
||||
if (_mc)
|
||||
_mc->chorusLevel(value);
|
||||
}
|
||||
|
||||
void Part::effectLevel(byte value) {
|
||||
_effect_level = value;
|
||||
sendEffectLevel(value);
|
||||
}
|
||||
|
||||
void Part::fix_after_load() {
|
||||
int lim = (_se->_game_id == GID_TENTACLE || _se->_soundType == MDT_AMIGA|| _se->isNativeMT32()) ? 12 : 24;
|
||||
set_transpose(_transpose, -lim, lim);
|
||||
volume(_vol);
|
||||
set_detune(_detune);
|
||||
set_pri(_pri);
|
||||
set_pan(_pan);
|
||||
|
||||
if (!_se->_dynamicChanAllocation && !_mc && !_percussion) {
|
||||
_mc = _se->allocateChannel(_player->getMidiDriver(), _pri_eff);
|
||||
if (!_mc)
|
||||
_se->suspendPart(this);
|
||||
}
|
||||
|
||||
sendAll();
|
||||
}
|
||||
|
||||
void Part::pitchBendFactor(byte value) {
|
||||
if (value > 12)
|
||||
return;
|
||||
pitchBend(0);
|
||||
_pitchbend_factor = value;
|
||||
if (_mc)
|
||||
_mc->pitchBendFactor(value);
|
||||
}
|
||||
|
||||
void Part::set_onoff(bool on) {
|
||||
if (_on != on) {
|
||||
_on = on;
|
||||
if (!on) {
|
||||
if (!_se->_dynamicChanAllocation) {
|
||||
if (_mc) {
|
||||
_mc->sustain(false);
|
||||
_mc->allNotesOff();
|
||||
}
|
||||
} else {
|
||||
off();
|
||||
}
|
||||
}
|
||||
if (!_percussion)
|
||||
_player->_se->reallocateMidiChannels(_player->getMidiDriver());
|
||||
}
|
||||
}
|
||||
|
||||
void Part::set_instrument(byte *data) {
|
||||
if (_se->_soundType == MDT_PCSPK)
|
||||
_instrument.pcspk(data);
|
||||
else
|
||||
_instrument.adlib(data);
|
||||
|
||||
if (clearToTransmit())
|
||||
_instrument.send(_mc);
|
||||
}
|
||||
|
||||
void Part::load_global_instrument(byte slot) {
|
||||
_player->_se->copyGlobalInstrument(slot, &_instrument);
|
||||
if (clearToTransmit())
|
||||
_instrument.send(_mc);
|
||||
}
|
||||
|
||||
void Part::noteOn(byte note, byte velocity) {
|
||||
if (!_on)
|
||||
return;
|
||||
|
||||
MidiChannel *mc = _mc;
|
||||
|
||||
// DEBUG
|
||||
if (_unassigned_instrument && !_percussion) {
|
||||
_unassigned_instrument = false;
|
||||
if (!_instrument.isValid()) {
|
||||
debug(0, "[%02d] No instrument specified", (int)_chan);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mc && _instrument.isValid()) {
|
||||
mc->noteOn(note, velocity);
|
||||
} else if (_percussion) {
|
||||
mc = _player->getMidiDriver()->getPercussionChannel();
|
||||
if (!mc)
|
||||
return;
|
||||
|
||||
if (_vol_eff != _se->_rhyState.vol)
|
||||
mc->volume(_vol_eff);
|
||||
|
||||
if (_se->_newSystem) {
|
||||
if (_pri_eff != _se->_rhyState.prio)
|
||||
mc->priority(_pri_eff);
|
||||
if (_polyphony != _se->_rhyState.poly)
|
||||
mc->controlChange(17, _polyphony);
|
||||
|
||||
} else if ((note < 35) && (!_player->_se->isNativeMT32())) {
|
||||
note = Instrument::_gmRhythmMap[note];
|
||||
}
|
||||
|
||||
_se->_rhyState = IMuseInternal::RhyState(_vol_eff, _polyphony, _pri_eff);
|
||||
|
||||
mc->noteOn(note, velocity);
|
||||
}
|
||||
}
|
||||
|
||||
void Part::noteOff(byte note) {
|
||||
if (!_on)
|
||||
return;
|
||||
|
||||
MidiChannel *mc = _mc;
|
||||
if (mc) {
|
||||
mc->noteOff(note);
|
||||
} else if (_percussion) {
|
||||
mc = _player->getMidiDriver()->getPercussionChannel();
|
||||
if (mc)
|
||||
mc->noteOff(note);
|
||||
}
|
||||
}
|
||||
|
||||
void Part::init(bool useNativeMT32) {
|
||||
_player = nullptr;
|
||||
_next = nullptr;
|
||||
_prev = nullptr;
|
||||
_mc = nullptr;
|
||||
_instrument.setNativeMT32Mode(useNativeMT32);
|
||||
}
|
||||
|
||||
void Part::setup(Player *player) {
|
||||
_player = player;
|
||||
|
||||
_percussion = (player->isMIDI() && _chan == 9); // true;
|
||||
_on = true;
|
||||
_pri_eff = player->getPriority();
|
||||
_pri = 0;
|
||||
_vol = 127;
|
||||
_vol_eff = player->getEffectiveVolume();
|
||||
_pan = clamp(player->getPan(), -64, 63);
|
||||
_transpose_eff = player->getTranspose();
|
||||
_transpose = 0;
|
||||
_detune = 0;
|
||||
_detune_eff = player->getDetune();
|
||||
_pitchbend_factor = 2;
|
||||
_volControlSensitivity = 127;
|
||||
_polyphony = 1;
|
||||
_pitchbend = 0;
|
||||
_effect_level = player->_se->isNativeMT32() ? 127 : 64;
|
||||
_instrument.clear();
|
||||
_unassigned_instrument = true;
|
||||
_chorus = 0;
|
||||
_modwheel = 0;
|
||||
_bank = 0;
|
||||
_pedal = false;
|
||||
_mc = nullptr;
|
||||
}
|
||||
|
||||
void Part::uninit() {
|
||||
if (!_player)
|
||||
return;
|
||||
off();
|
||||
_player->removePart(this);
|
||||
_se->removeSuspendedPart(this);
|
||||
_player = nullptr;
|
||||
}
|
||||
|
||||
void Part::off() {
|
||||
if (_mc) {
|
||||
_mc->sustain(false);
|
||||
_mc->allNotesOff();
|
||||
if (!_se->reassignChannelAndResumePart(_mc))
|
||||
_mc->release();
|
||||
_mc = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool Part::clearToTransmit() {
|
||||
if (_mc)
|
||||
return true;
|
||||
if (_instrument.isValid())
|
||||
_player->_se->reallocateMidiChannels(_player->getMidiDriver());
|
||||
return false;
|
||||
}
|
||||
|
||||
void Part::sendAll() {
|
||||
if (!clearToTransmit())
|
||||
return;
|
||||
|
||||
_mc->pitchBendFactor(_pitchbend_factor);
|
||||
sendTranspose();
|
||||
sendDetune();
|
||||
sendPitchBend();
|
||||
_mc->volume(_vol_eff);
|
||||
_mc->sustain(_pedal);
|
||||
_mc->modulationWheel(_modwheel);
|
||||
sendPanPosition(_pan_eff + 0x40);
|
||||
sendPolyphony();
|
||||
|
||||
if (_instrument.isValid())
|
||||
_instrument.send(_mc);
|
||||
|
||||
// We need to send the effect level after setting up the instrument
|
||||
// otherwise the reverb setting for MT-32 will be overwritten.
|
||||
sendEffectLevel(_effect_level);
|
||||
|
||||
_mc->chorusLevel(_chorus);
|
||||
_mc->priority(_pri_eff);
|
||||
}
|
||||
|
||||
void Part::sendPitchBend() {
|
||||
if (_se->_newSystem && !_pitchbend_factor) {
|
||||
sendVolumeFade();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_mc)
|
||||
_mc->pitchBend(_pitchbend);
|
||||
}
|
||||
|
||||
void Part::sendVolume(int8 fadeModifier) {
|
||||
uint16 vol = (_vol + fadeModifier + 1) * _player->getEffectiveVolume();
|
||||
|
||||
if (_se->_newSystem)
|
||||
vol = (vol * (_volControlSensitivity + 1)) >> 7;
|
||||
|
||||
_vol_eff = vol >> 7;
|
||||
|
||||
if (_mc)
|
||||
_mc->volume(_vol_eff);
|
||||
}
|
||||
|
||||
void Part::sendVolumeFade() {
|
||||
int16 fadeModifier = ((((_pitchbend >= 0) ? 127 - _vol : _vol) + 1) * _pitchbend) >> 7;
|
||||
sendVolume(fadeModifier);
|
||||
}
|
||||
|
||||
void Part::sendTranspose() {
|
||||
if (!_mc)
|
||||
return;
|
||||
|
||||
_mc->transpose(_transpose_eff);
|
||||
}
|
||||
|
||||
void Part::sendDetune() {
|
||||
if (!_mc)
|
||||
return;
|
||||
|
||||
_mc->detune(_detune_eff);
|
||||
}
|
||||
|
||||
void Part::programChange(byte value) {
|
||||
_bank = 0;
|
||||
_instrument.program(value, 0, _player->isMT32());
|
||||
if (clearToTransmit())
|
||||
_instrument.send(_mc);
|
||||
}
|
||||
|
||||
void Part::set_instrument(uint b) {
|
||||
_bank = (byte)(b >> 8);
|
||||
|
||||
// Indy4 and Monkey2 Macintosh versions always use the second bank for sound effects here.
|
||||
if (_se->_soundType == MDT_MACINTOSH && (_se->_game_id == GID_MONKEY2 || _se->_game_id == GID_INDY4))
|
||||
_bank = 1;
|
||||
|
||||
_instrument.program((byte)b, _bank, _player->isMT32());
|
||||
|
||||
if (clearToTransmit())
|
||||
_instrument.send(_mc);
|
||||
}
|
||||
|
||||
void Part::allNotesOff() {
|
||||
if (!_mc)
|
||||
return;
|
||||
_mc->allNotesOff();
|
||||
}
|
||||
|
||||
void Part::sendPanPosition(uint8 value) {
|
||||
if (!_mc)
|
||||
return;
|
||||
|
||||
// As described in bug report #1849 "MI2: Minor problems in native MT-32 mode"
|
||||
// the original iMuse MT-32 driver did revert the panning. So we do the same
|
||||
// here in our code to have correctly panned sound output.
|
||||
if (_player->_se->isNativeMT32())
|
||||
value = 127 - value;
|
||||
|
||||
_mc->panPosition(value);
|
||||
}
|
||||
|
||||
void Part::sendEffectLevel(uint8 value) {
|
||||
if (!_mc)
|
||||
return;
|
||||
_mc->effectLevel(value);
|
||||
}
|
||||
|
||||
void Part::sendPolyphony() {
|
||||
if (!_mc || !_se->_newSystem)
|
||||
return;
|
||||
_mc->controlChange(17, _polyphony);
|
||||
}
|
||||
|
||||
} // End of namespace Scumm
|
||||
1203
engines/scumm/imuse/imuse_player.cpp
Normal file
1203
engines/scumm/imuse/imuse_player.cpp
Normal file
File diff suppressed because it is too large
Load Diff
520
engines/scumm/imuse/instrument.cpp
Normal file
520
engines/scumm/imuse/instrument.cpp
Normal file
@@ -0,0 +1,520 @@
|
||||
/* 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/scumm.h"
|
||||
#include "scumm/imuse/instrument.h"
|
||||
#include "audio/mididrv.h"
|
||||
|
||||
namespace Scumm {
|
||||
|
||||
static struct {
|
||||
const char *name;
|
||||
byte program;
|
||||
}
|
||||
|
||||
roland_to_gm_map[] = {
|
||||
// Monkey Island 2 instruments
|
||||
// TODO: Complete
|
||||
{ "badspit ", 62 },
|
||||
{ "Big Drum ", 116 },
|
||||
{ "burp ", 58 },
|
||||
// { "dinkfall ", ??? },
|
||||
// { "Fire Pit ", ??? },
|
||||
{ "foghorn ", 60 },
|
||||
{ "glop ", 39 },
|
||||
// { "jacob's la", ??? },
|
||||
{ "LeshBass ", 33 },
|
||||
// { "lowsnort ", ??? },
|
||||
{ "ML explosn", 127 },
|
||||
{ "ReggaeBass", 32 },
|
||||
// { "rope fall ", ??? },
|
||||
{ "rumble ", 89 },
|
||||
{ "SdTrk Bend", 97 },
|
||||
// { "snort ", ??? },
|
||||
{ "spitting ", 62 },
|
||||
{ "Swell 1 ", 95 },
|
||||
{ "Swell 2 ", 95 },
|
||||
{ "thnderclap", 127 }
|
||||
|
||||
// Fate of Atlantis instruments
|
||||
// TODO: Build
|
||||
// { "*aah! ", ??? },
|
||||
// { "*ooh! ", ??? },
|
||||
// { "*ShotFar4 ", ??? },
|
||||
// { "*splash3 ", ??? },
|
||||
// { "*torpedo5 ", ??? },
|
||||
// { "*whip3 ", ??? },
|
||||
// { "*woodknock", ??? },
|
||||
// { "35 lavabub", ??? },
|
||||
// { "49 bzzt! ", ??? },
|
||||
// { "applause ", ??? },
|
||||
// { "Arabongo ", ??? },
|
||||
// { "Big Drum ", ??? }, // DUPLICATE (todo: confirm)
|
||||
// { "bodythud1 ", ??? },
|
||||
// { "boneKLOK2 ", ??? },
|
||||
// { "boom10 ", ??? },
|
||||
// { "boom11 ", ??? },
|
||||
// { "boom15 ", ??? },
|
||||
// { "boxclik1a ", ??? },
|
||||
// { "brassbonk3", ??? },
|
||||
// { "carstart ", ??? },
|
||||
// { "cb tpt 2 ", ??? },
|
||||
// { "cell door ", ??? },
|
||||
// { "chains ", ??? },
|
||||
// { "crash ", ??? },
|
||||
// { "crsrt/idl3", ??? },
|
||||
// { "Fire Pit ", ??? }, // DUPLICATE (todo: confirm)
|
||||
// { "Fzooom ", ??? },
|
||||
// { "Fzooom 2 ", ??? },
|
||||
// { "ghostwhosh", ??? },
|
||||
// { "glasssmash", ??? },
|
||||
// { "gloop2 ", ??? },
|
||||
// { "gunShotNea", ??? },
|
||||
// { "idoorclse ", ??? },
|
||||
// { "knife ", ??? },
|
||||
// { "lavacmbl4 ", ??? },
|
||||
// { "Mellow Str", ??? },
|
||||
// { "mtlheater1", ??? },
|
||||
// { "pachinko5 ", ??? },
|
||||
// { "Ping1 ", ??? },
|
||||
// { "rockcrunch", ??? },
|
||||
// { "rumble ", ??? }, // DUPLICATE (todo: confirm)
|
||||
// { "runngwatr ", ??? },
|
||||
// { "scrape2 ", ??? },
|
||||
// { "snakeHiss ", ??? },
|
||||
// { "snort ", ??? }, // DUPLICATE (todo: confirm)
|
||||
// { "spindle4 ", ??? },
|
||||
// { "splash2 ", ??? },
|
||||
// { "squirel ", ??? },
|
||||
// { "steam3 ", ??? },
|
||||
// { "stonwheel6", ??? },
|
||||
// { "street ", ??? },
|
||||
// { "trickle4 ", ??? }
|
||||
};
|
||||
|
||||
// This emulates the percussion bank setup LEC used with the MT-32,
|
||||
// where notes 24 - 34 were assigned instruments without reverb.
|
||||
// It also fixes problems on GS devices that map sounds to these
|
||||
// notes by default.
|
||||
const byte Instrument::_gmRhythmMap[35] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 36, 37, 38, 39, 40, 41, 66, 47,
|
||||
65, 48, 56
|
||||
};
|
||||
|
||||
class Instrument_Program : public InstrumentInternal {
|
||||
private:
|
||||
byte _program;
|
||||
byte _bank;
|
||||
bool _soundTypeMT32;
|
||||
bool _nativeMT32Device;
|
||||
|
||||
public:
|
||||
Instrument_Program(byte program, byte bank, bool soundTypeMT32, bool nativeMT32Device);
|
||||
Instrument_Program(Common::Serializer &s, bool nativeMT32Device);
|
||||
void saveLoadWithSerializer(Common::Serializer &s) override;
|
||||
void send(MidiChannel *mc) override;
|
||||
void copy_to(Instrument *dest) override { dest->program(_program, _bank, _soundTypeMT32); }
|
||||
bool is_valid() override {
|
||||
return (_program < 128) &&
|
||||
((_nativeMT32Device == _soundTypeMT32) || (_nativeMT32Device
|
||||
? (MidiDriver::_gmToMt32[_program] < 128)
|
||||
: (MidiDriver::_mt32ToGm[_program] < 128)));
|
||||
}
|
||||
};
|
||||
|
||||
class Instrument_AdLib : public InstrumentInternal {
|
||||
private:
|
||||
|
||||
#include "common/pack-start.h" // START STRUCT PACKING
|
||||
|
||||
struct AdLibInstrument {
|
||||
byte flags_1;
|
||||
byte oplvl_1;
|
||||
byte atdec_1;
|
||||
byte sustrel_1;
|
||||
byte waveform_1;
|
||||
byte flags_2;
|
||||
byte oplvl_2;
|
||||
byte atdec_2;
|
||||
byte sustrel_2;
|
||||
byte waveform_2;
|
||||
byte feedback;
|
||||
byte flags_a;
|
||||
struct {
|
||||
byte a, b, c, d, e, f, g, h;
|
||||
} extra_a;
|
||||
byte flags_b;
|
||||
struct {
|
||||
byte a, b, c, d, e, f, g, h;
|
||||
} extra_b;
|
||||
byte duration;
|
||||
} PACKED_STRUCT;
|
||||
|
||||
#include "common/pack-end.h" // END STRUCT PACKING
|
||||
|
||||
AdLibInstrument _instrument;
|
||||
|
||||
public:
|
||||
Instrument_AdLib(const byte *data);
|
||||
Instrument_AdLib(Common::Serializer &s);
|
||||
void saveLoadWithSerializer(Common::Serializer &s) override;
|
||||
void send(MidiChannel *mc) override;
|
||||
void copy_to(Instrument *dest) override { dest->adlib((byte *)&_instrument); }
|
||||
bool is_valid() override { return true; }
|
||||
};
|
||||
|
||||
class Instrument_Roland : public InstrumentInternal {
|
||||
private:
|
||||
|
||||
#include "common/pack-start.h" // START STRUCT PACKING
|
||||
|
||||
struct RolandInstrument {
|
||||
byte roland_id;
|
||||
byte device_id;
|
||||
byte model_id;
|
||||
byte command;
|
||||
byte address[3];
|
||||
struct {
|
||||
byte name[10];
|
||||
byte partial_struct12;
|
||||
byte partial_struct34;
|
||||
byte partial_mute;
|
||||
byte env_mode;
|
||||
} common;
|
||||
struct {
|
||||
byte wg_pitch_coarse;
|
||||
byte wg_pitch_fine;
|
||||
byte wg_pitch_keyfollow;
|
||||
byte wg_pitch_bender_sw;
|
||||
byte wg_waveform_pcm_bank;
|
||||
byte wg_pcm_wave_num;
|
||||
byte wg_pulse_width;
|
||||
byte wg_pw_velo_sens;
|
||||
byte p_env_depth;
|
||||
byte p_evn_velo_sens;
|
||||
byte p_env_time_keyf;
|
||||
byte p_env_time[4];
|
||||
byte p_env_level[3];
|
||||
byte p_env_sustain_level;
|
||||
byte end_level;
|
||||
byte p_lfo_rate;
|
||||
byte p_lfo_depth;
|
||||
byte p_lfo_mod_sens;
|
||||
byte tvf_cutoff_freq;
|
||||
byte tvf_resonance;
|
||||
byte tvf_keyfollow;
|
||||
byte tvf_bias_point_dir;
|
||||
byte tvf_bias_level;
|
||||
byte tvf_env_depth;
|
||||
byte tvf_env_velo_sens;
|
||||
byte tvf_env_depth_keyf;
|
||||
byte tvf_env_time_keyf;
|
||||
byte tvf_env_time[5];
|
||||
byte tvf_env_level[3];
|
||||
byte tvf_env_sustain_level;
|
||||
byte tva_level;
|
||||
byte tva_velo_sens;
|
||||
byte tva_bias_point_1;
|
||||
byte tva_bias_level_1;
|
||||
byte tva_bias_point_2;
|
||||
byte tva_bias_level_2;
|
||||
byte tva_env_time_keyf;
|
||||
byte tva_env_time_v_follow;
|
||||
byte tva_env_time[5];
|
||||
byte tva_env_level[3];
|
||||
byte tva_env_sustain_level;
|
||||
} partial[4];
|
||||
byte checksum;
|
||||
} PACKED_STRUCT;
|
||||
|
||||
#include "common/pack-end.h" // END STRUCT PACKING
|
||||
|
||||
RolandInstrument _instrument;
|
||||
|
||||
char _instrument_name[11];
|
||||
bool _nativeMT32Device;
|
||||
|
||||
uint8 getEquivalentGM();
|
||||
|
||||
public:
|
||||
Instrument_Roland(const byte *data, bool nativeMT32Device);
|
||||
Instrument_Roland(Common::Serializer &s, bool nativeMT32Device);
|
||||
void saveLoadWithSerializer(Common::Serializer &s) override;
|
||||
void send(MidiChannel *mc) override;
|
||||
void copy_to(Instrument *dest) override { dest->roland((byte *)&_instrument); }
|
||||
bool is_valid() override { return (_nativeMT32Device ? true : (_instrument_name[0] != '\0')); }
|
||||
};
|
||||
|
||||
class Instrument_PcSpk : public InstrumentInternal {
|
||||
public:
|
||||
Instrument_PcSpk(const byte *data);
|
||||
Instrument_PcSpk(Common::Serializer &s);
|
||||
void saveLoadWithSerializer(Common::Serializer &s) override;
|
||||
void send(MidiChannel *mc) override;
|
||||
void copy_to(Instrument *dest) override { dest->pcspk((byte *)&_instrument); }
|
||||
bool is_valid() override { return true; }
|
||||
|
||||
private:
|
||||
byte _instrument[23];
|
||||
};
|
||||
|
||||
////////////////////////////////////////
|
||||
//
|
||||
// Instrument class members
|
||||
//
|
||||
////////////////////////////////////////
|
||||
|
||||
void Instrument::clear() {
|
||||
delete _instrument;
|
||||
_instrument = nullptr;
|
||||
_type = itNone;
|
||||
}
|
||||
|
||||
void Instrument::program(byte prog, byte bank, bool mt32SoundType) {
|
||||
clear();
|
||||
if (prog > 127)
|
||||
return;
|
||||
_type = itProgram;
|
||||
_instrument = new Instrument_Program(prog, bank, mt32SoundType, _nativeMT32Device);
|
||||
}
|
||||
|
||||
void Instrument::adlib(const byte *instrument) {
|
||||
clear();
|
||||
if (!instrument)
|
||||
return;
|
||||
_type = itAdLib;
|
||||
_instrument = new Instrument_AdLib(instrument);
|
||||
}
|
||||
|
||||
void Instrument::roland(const byte *instrument) {
|
||||
clear();
|
||||
if (!instrument)
|
||||
return;
|
||||
_type = itRoland;
|
||||
_instrument = new Instrument_Roland(instrument, _nativeMT32Device);
|
||||
}
|
||||
|
||||
void Instrument::pcspk(const byte *instrument) {
|
||||
clear();
|
||||
if (!instrument)
|
||||
return;
|
||||
_type = itPcSpk;
|
||||
_instrument = new Instrument_PcSpk(instrument);
|
||||
}
|
||||
|
||||
void Instrument::saveLoadWithSerializer(Common::Serializer &s) {
|
||||
if (s.isSaving()) {
|
||||
s.syncAsByte(_type);
|
||||
if (_instrument)
|
||||
_instrument->saveLoadWithSerializer(s);
|
||||
} else {
|
||||
clear();
|
||||
s.syncAsByte(_type);
|
||||
switch (_type) {
|
||||
case itNone:
|
||||
break;
|
||||
case itProgram:
|
||||
_instrument = new Instrument_Program(s, _nativeMT32Device);
|
||||
break;
|
||||
case itAdLib:
|
||||
_instrument = new Instrument_AdLib(s);
|
||||
break;
|
||||
case itRoland:
|
||||
_instrument = new Instrument_Roland(s, _nativeMT32Device);
|
||||
break;
|
||||
case itPcSpk:
|
||||
_instrument = new Instrument_PcSpk(s);
|
||||
break;
|
||||
case itMacDeprecated: {
|
||||
byte prog = 255;
|
||||
s.syncAsByte(prog);
|
||||
_instrument = new Instrument_Program(prog, 1, false, false);
|
||||
} break;
|
||||
default:
|
||||
warning("No known instrument classification #%d", (int)_type);
|
||||
_type = itNone;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
//
|
||||
// Instrument_Program class members
|
||||
//
|
||||
////////////////////////////////////////
|
||||
|
||||
Instrument_Program::Instrument_Program(byte program, byte bank, bool soundTypeMT32, bool nativeMT32Device) :
|
||||
_program(program),
|
||||
_bank(bank),
|
||||
_soundTypeMT32(soundTypeMT32),
|
||||
_nativeMT32Device(nativeMT32Device) {
|
||||
if (program > 127)
|
||||
_program = 255;
|
||||
}
|
||||
|
||||
Instrument_Program::Instrument_Program(Common::Serializer &s, bool nativeMT32Device) :
|
||||
_nativeMT32Device(nativeMT32Device) {
|
||||
_program = 255;
|
||||
_bank = 0;
|
||||
_soundTypeMT32 = false;
|
||||
if (!s.isSaving())
|
||||
saveLoadWithSerializer(s);
|
||||
}
|
||||
|
||||
void Instrument_Program::saveLoadWithSerializer(Common::Serializer &s) {
|
||||
s.syncAsByte(_program);
|
||||
s.syncAsByte(_bank, VER(123));
|
||||
if (s.isSaving()) {
|
||||
s.syncAsByte(_soundTypeMT32);
|
||||
s.syncAsByte(_nativeMT32Device);
|
||||
} else {
|
||||
byte tmp;
|
||||
s.syncAsByte(tmp);
|
||||
_soundTypeMT32 = (tmp > 0);
|
||||
s.syncAsByte(tmp, VER(122));
|
||||
_nativeMT32Device = (tmp > 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Instrument_Program::send(MidiChannel *mc) {
|
||||
if (_program > 127)
|
||||
return;
|
||||
|
||||
byte program = _program;
|
||||
if (!_nativeMT32Device && _soundTypeMT32)
|
||||
program = MidiDriver::_mt32ToGm[program];
|
||||
|
||||
if (_bank)
|
||||
mc->bankSelect(_bank);
|
||||
if (program < 128)
|
||||
mc->programChange(program);
|
||||
if (_bank)
|
||||
mc->bankSelect(0);
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
//
|
||||
// Instrument_AdLib class members
|
||||
//
|
||||
////////////////////////////////////////
|
||||
|
||||
Instrument_AdLib::Instrument_AdLib(const byte *data) {
|
||||
memcpy(&_instrument, data, sizeof(_instrument));
|
||||
}
|
||||
|
||||
Instrument_AdLib::Instrument_AdLib(Common::Serializer &s) {
|
||||
if (!s.isSaving())
|
||||
saveLoadWithSerializer(s);
|
||||
else
|
||||
memset(&_instrument, 0, sizeof(_instrument));
|
||||
}
|
||||
|
||||
void Instrument_AdLib::saveLoadWithSerializer(Common::Serializer &s) {
|
||||
s.syncBytes((byte *)(&_instrument), sizeof(_instrument));
|
||||
}
|
||||
|
||||
void Instrument_AdLib::send(MidiChannel *mc) {
|
||||
mc->sysEx_customInstrument('ADL ', (byte *)&_instrument, sizeof(_instrument));
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
//
|
||||
// Instrument_Roland class members
|
||||
//
|
||||
////////////////////////////////////////
|
||||
|
||||
Instrument_Roland::Instrument_Roland(const byte *data, bool nativeMT32Device) : _nativeMT32Device(nativeMT32Device) {
|
||||
memcpy(&_instrument, data, sizeof(_instrument));
|
||||
memcpy(&_instrument_name, &_instrument.common.name, sizeof(_instrument.common.name));
|
||||
_instrument_name[10] = '\0';
|
||||
if (!_nativeMT32Device && getEquivalentGM() >= 128) {
|
||||
debug(0, "MT-32 instrument \"%s\" not supported yet", _instrument_name);
|
||||
_instrument_name[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
Instrument_Roland::Instrument_Roland(Common::Serializer &s, bool nativeMT32Device) : _nativeMT32Device(nativeMT32Device) {
|
||||
_instrument_name[0] = '\0';
|
||||
if (!s.isSaving())
|
||||
saveLoadWithSerializer(s);
|
||||
else
|
||||
memset(&_instrument, 0, sizeof(_instrument));
|
||||
}
|
||||
|
||||
void Instrument_Roland::saveLoadWithSerializer(Common::Serializer &s) {
|
||||
s.syncBytes((byte *)(&_instrument), sizeof(_instrument));
|
||||
if (!s.isSaving()) {
|
||||
memcpy(&_instrument_name, &_instrument.common.name, sizeof(_instrument.common.name));
|
||||
_instrument_name[10] = '\0';
|
||||
if (!_nativeMT32Device && getEquivalentGM() >= 128) {
|
||||
debug(2, "MT-32 custom instrument \"%s\" not supported", _instrument_name);
|
||||
_instrument_name[0] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Instrument_Roland::send(MidiChannel *mc) {
|
||||
if (_nativeMT32Device) {
|
||||
mc->sysEx_customInstrument('ROL ', (byte *)&_instrument, sizeof(_instrument));
|
||||
} else {
|
||||
// Convert to a GM program change.
|
||||
byte program = getEquivalentGM();
|
||||
if (program < 128)
|
||||
mc->programChange(program);
|
||||
}
|
||||
}
|
||||
|
||||
uint8 Instrument_Roland::getEquivalentGM() {
|
||||
byte i;
|
||||
for (i = 0; i != ARRAYSIZE(roland_to_gm_map); ++i) {
|
||||
if (!memcmp(roland_to_gm_map[i].name, _instrument.common.name, 10))
|
||||
return roland_to_gm_map[i].program;
|
||||
}
|
||||
return 255;
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
//
|
||||
// Instrument_PcSpk class members
|
||||
//
|
||||
////////////////////////////////////////
|
||||
|
||||
Instrument_PcSpk::Instrument_PcSpk(const byte *data) {
|
||||
memcpy(_instrument, data, sizeof(_instrument));
|
||||
}
|
||||
|
||||
Instrument_PcSpk::Instrument_PcSpk(Common::Serializer &s) {
|
||||
if (!s.isSaving())
|
||||
saveLoadWithSerializer(s);
|
||||
else
|
||||
memset(_instrument, 0, sizeof(_instrument));
|
||||
}
|
||||
|
||||
void Instrument_PcSpk::saveLoadWithSerializer(Common::Serializer &s) {
|
||||
s.syncBytes(_instrument, sizeof(_instrument));
|
||||
}
|
||||
|
||||
void Instrument_PcSpk::send(MidiChannel *mc) {
|
||||
mc->sysEx_customInstrument('SPK ', (byte *)&_instrument, sizeof(_instrument));
|
||||
}
|
||||
|
||||
} // End of namespace Scumm
|
||||
89
engines/scumm/imuse/instrument.h
Normal file
89
engines/scumm/imuse/instrument.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/* 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_INSTRUMENT_H
|
||||
#define SCUMM_IMUSE_INSTRUMENT_H
|
||||
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/serializer.h"
|
||||
|
||||
class MidiChannel;
|
||||
|
||||
namespace Scumm {
|
||||
|
||||
class Instrument;
|
||||
|
||||
class InstrumentInternal : public Common::Serializable {
|
||||
public:
|
||||
~InstrumentInternal() override {}
|
||||
virtual void send(MidiChannel *mc) = 0;
|
||||
virtual void copy_to(Instrument *dest) = 0;
|
||||
virtual bool is_valid() = 0;
|
||||
};
|
||||
|
||||
class Instrument : public Common::Serializable {
|
||||
private:
|
||||
byte _type;
|
||||
InstrumentInternal *_instrument;
|
||||
|
||||
public:
|
||||
enum {
|
||||
itNone = 0,
|
||||
itProgram = 1,
|
||||
itAdLib = 2,
|
||||
itRoland = 3,
|
||||
itPcSpk = 4,
|
||||
itMacDeprecated = 5
|
||||
};
|
||||
|
||||
Instrument() : _type(0), _instrument(0), _nativeMT32Device(false) { }
|
||||
~Instrument() override { delete _instrument; }
|
||||
void setNativeMT32Mode(bool isNativeMT32) { _nativeMT32Device = isNativeMT32; }
|
||||
static const byte _gmRhythmMap[35];
|
||||
|
||||
void clear();
|
||||
void copy_to(Instrument *dest) {
|
||||
if (_instrument)
|
||||
_instrument->copy_to(dest);
|
||||
else
|
||||
dest->clear();
|
||||
}
|
||||
|
||||
void program(byte program, byte bank, bool mt32SoundType);
|
||||
void adlib(const byte *instrument);
|
||||
void roland(const byte *instrument);
|
||||
void pcspk(const byte *instrument);
|
||||
|
||||
byte getType() { return _type; }
|
||||
bool isValid() { return (_instrument ? _instrument->is_valid() : false); }
|
||||
void saveLoadWithSerializer(Common::Serializer &s) override;
|
||||
void send(MidiChannel *mc) {
|
||||
if (_instrument)
|
||||
_instrument->send(mc);
|
||||
}
|
||||
|
||||
bool _nativeMT32Device;
|
||||
};
|
||||
|
||||
} // End of namespace Scumm
|
||||
|
||||
#endif
|
||||
37
engines/scumm/imuse/sysex.h
Normal file
37
engines/scumm/imuse/sysex.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/* 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_SYSEX_H
|
||||
#define SCUMM_IMUSE_SYSEX_H
|
||||
|
||||
|
||||
#include "common/util.h"
|
||||
|
||||
namespace Scumm {
|
||||
|
||||
class Player;
|
||||
|
||||
extern void sysexHandler_Scumm(Player *, const byte *, uint16);
|
||||
extern void sysexHandler_SamNMax(Player *, const byte *, uint16);
|
||||
|
||||
} // End of namespace Scumm
|
||||
|
||||
#endif
|
||||
76
engines/scumm/imuse/sysex_samnmax.cpp
Normal file
76
engines/scumm/imuse/sysex_samnmax.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
/* 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 "common/endian.h"
|
||||
#include "common/util.h"
|
||||
|
||||
/*
|
||||
* SysEx command handlers must have full access to the
|
||||
* internal IMuse implementation classes. Before including
|
||||
* the relevant header file, two things must happen:
|
||||
* 1. A function declaration must be made.
|
||||
* 2. The following #define must be established:
|
||||
* #define SYSEX_CALLBACK_FUNCTION functionName
|
||||
*/
|
||||
#define SYSEX_CALLBACK_FUNCTION sysexHandler_SamNMax
|
||||
#include "scumm/imuse/imuse_internal.h"
|
||||
|
||||
namespace Scumm {
|
||||
|
||||
extern void sysexHandler_Scumm(Player *, const byte *, uint16);
|
||||
|
||||
void sysexHandler_SamNMax(Player *player, const byte *msg, uint16 len) {
|
||||
byte a;
|
||||
|
||||
IMuseInternal *se = player->_se;
|
||||
const byte *p = msg;
|
||||
|
||||
switch (*p++) {
|
||||
case 0:
|
||||
// Trigger Event
|
||||
// Triggers are set by doCommand(ImSetTrigger).
|
||||
// When a SysEx marker is encountered whose sound
|
||||
// ID and marker ID match what was set by ImSetTrigger,
|
||||
// something magical is supposed to happen....
|
||||
for (a = 0; a < ARRAYSIZE(se->_snm_triggers); ++a) {
|
||||
if (se->_snm_triggers[a].sound == player->_id &&
|
||||
se->_snm_triggers[a].id == *p) {
|
||||
se->_snm_triggers[a].sound = se->_snm_triggers[a].id = 0;
|
||||
se->doCommand(8, se->_snm_triggers[a].command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// maybe_jump.
|
||||
if (player->_scanning)
|
||||
break;
|
||||
player->maybe_jump(p[0], p[1] - 1, (READ_BE_UINT16(p + 2) - 1) * 4 + p[4], ((p[5] * TICKS_PER_BEAT) >> 2) + p[6]);
|
||||
break;
|
||||
|
||||
default:
|
||||
sysexHandler_Scumm(player, msg, len);
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Scumm
|
||||
223
engines/scumm/imuse/sysex_scumm.cpp
Normal file
223
engines/scumm/imuse/sysex_scumm.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
/* 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 "common/endian.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/util.h"
|
||||
|
||||
/*
|
||||
* SysEx command handlers must have full access to the
|
||||
* internal IMuse implementation classes. Before including
|
||||
* the relevant header file, two things must happen:
|
||||
* 1. A function declaration must be made.
|
||||
* 2. The following #define must be established:
|
||||
* #define SYSEX_CALLBACK_FUNCTION functionName
|
||||
*/
|
||||
#define SYSEX_CALLBACK_FUNCTION sysexHandler_Scumm
|
||||
#include "scumm/imuse/imuse_internal.h"
|
||||
|
||||
namespace Scumm {
|
||||
|
||||
void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) {
|
||||
Part *part;
|
||||
byte a;
|
||||
byte buf[128];
|
||||
|
||||
IMuseInternal *se = player->_se;
|
||||
const byte *p = msg;
|
||||
|
||||
byte code = 0;
|
||||
switch (code = *p++) {
|
||||
case 0:
|
||||
// Allocate new part.
|
||||
// There are 8 bytes (after decoding!) of useful information here.
|
||||
// Here is what we know about them so far:
|
||||
// BYTE 0: Channel #
|
||||
// BYTE 1: BIT 01(0x01): Part on?(1 = yes)
|
||||
// BIT 02(0x02): Reverb? (1 = yes) [bug #1849]
|
||||
// BYTE 2: Priority adjustment
|
||||
// BYTE 3: Volume [guessing]
|
||||
// BYTE 4: Pan [bug #1849]
|
||||
// BYTE 5: BIT 8(0x80): Percussion?(1 = yes) [guessed?]
|
||||
// BYTE 5: Transpose, if set to 0x80(=-1) it means no transpose
|
||||
// BYTE 6: Detune
|
||||
// BYTE 7: Pitchbend factor [bug #1849]
|
||||
// BYTE 8: Program
|
||||
|
||||
part = player->getPart(p[0] & 0x0F);
|
||||
player->decode_sysex_bytes(p + 1, buf + 1, len - 1);
|
||||
if (part) {
|
||||
part->set_onoff(buf[1] & 0x01);
|
||||
part->effectLevel((buf[1] & 0x02) ? 127 : 0);
|
||||
part->set_pri(buf[2]);
|
||||
part->volume(buf[3]);
|
||||
part->set_pan(buf[4]);
|
||||
part->_percussion = player->_supportsPercussion ? ((buf[5] & 0x80) > 0) : false;
|
||||
// The original MI2 and INDY4 drivers always use -12/12 boundaries here, even for the
|
||||
// AdLib and PC Speaker drivers which use -24/24 in other locations. DOTT does not
|
||||
// have/use this sysex code any more, but even at other locations it always uses -12/12.
|
||||
part->set_transpose(buf[5], -12, 12);
|
||||
part->set_detune(buf[6]);
|
||||
part->pitchBendFactor(buf[7]);
|
||||
if (part->_percussion) {
|
||||
if (part->_mc)
|
||||
part->off();
|
||||
} else {
|
||||
if (player->_isMIDI) {
|
||||
// Even in cases where a program does not seem to be specified,
|
||||
// i.e. bytes 15 and 16 are 0, we send a program change because
|
||||
// 0 is a valid program number. MI2 tests show that in such
|
||||
// cases, a regular program change message always seems to follow
|
||||
// anyway.
|
||||
part->_instrument.program(buf[8], 0, player->_isMT32);
|
||||
} else {
|
||||
// Like the original we set up the instrument data of the
|
||||
// specified program here too. In case the global
|
||||
// instrument data is not loaded already, this will take
|
||||
// care of setting a default instrument too.
|
||||
se->copyGlobalInstrument(buf[8], &part->_instrument);
|
||||
}
|
||||
// The newer reallocateMidiChannels() method can fail to assign a
|
||||
// hardware channel here (bug #14618: Inaccurate fades in INDY4,
|
||||
// "Test 1"). So instead, we implement the less dynamic alloacation
|
||||
// method of the early version drivers here.
|
||||
if (!part->_mc) {
|
||||
part->_mc = se->allocateChannel(player->_midi, part->_pri_eff);
|
||||
if (!part->_mc)
|
||||
se->suspendPart(part);
|
||||
}
|
||||
part->sendAll();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// Shut down a part. [Bug #1849, comments]
|
||||
part = player->getPart(p[0]);
|
||||
if (part != nullptr)
|
||||
part->uninit();
|
||||
break;
|
||||
|
||||
case 2: // Start of song. Ignore for now.
|
||||
if (!se->_dynamicChanAllocation)
|
||||
player->uninit_parts();
|
||||
break;
|
||||
|
||||
case 16: // AdLib instrument definition(Part)
|
||||
a = *p++ & 0x0F;
|
||||
++p; // Skip hardware type
|
||||
part = player->getPart(a);
|
||||
if (part) {
|
||||
if (len == 62 || len == 48) {
|
||||
player->decode_sysex_bytes(p, buf, len - 2);
|
||||
part->set_instrument((byte *)buf);
|
||||
} else {
|
||||
part->programChange(254); // Must be invalid, but not 255 (which is reserved)
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 17: // AdLib instrument definition(Global)
|
||||
p += 2; // Skip hardware type and... whatever came right before it
|
||||
a = *p++;
|
||||
player->decode_sysex_bytes(p, buf, len - 3);
|
||||
if (len == 63 || len == 49)
|
||||
se->setGlobalInstrument(a, buf);
|
||||
break;
|
||||
|
||||
case 33: // Parameter adjust
|
||||
a = *p++ & 0x0F;
|
||||
++p; // Skip hardware type
|
||||
player->decode_sysex_bytes(p, buf, len - 2);
|
||||
part = player->getPart(a);
|
||||
if (part)
|
||||
part->set_param(READ_BE_UINT16(buf), READ_BE_UINT16(buf + 2));
|
||||
break;
|
||||
|
||||
case 48: // Hook - jump
|
||||
if (player->_scanning)
|
||||
break;
|
||||
player->decode_sysex_bytes(p + 1, buf, len - 1);
|
||||
player->maybe_jump(buf[0], READ_BE_UINT16(buf + 1), READ_BE_UINT16(buf + 3), READ_BE_UINT16(buf + 5));
|
||||
break;
|
||||
|
||||
case 49: // Hook - global transpose
|
||||
player->decode_sysex_bytes(p + 1, buf, len - 1);
|
||||
player->maybe_set_transpose(buf);
|
||||
break;
|
||||
|
||||
case 50: // Hook - part on/off
|
||||
buf[0] = *p++ & 0x0F;
|
||||
player->decode_sysex_bytes(p, buf + 1, len - 1);
|
||||
player->maybe_part_onoff(buf);
|
||||
break;
|
||||
|
||||
case 51: // Hook - set volume
|
||||
buf[0] = *p++ & 0x0F;
|
||||
player->decode_sysex_bytes(p, buf + 1, len - 1);
|
||||
player->maybe_set_volume(buf);
|
||||
break;
|
||||
|
||||
case 52: // Hook - set program
|
||||
buf[0] = *p++ & 0x0F;
|
||||
player->decode_sysex_bytes(p, buf + 1, len - 1);
|
||||
player->maybe_set_program(buf);
|
||||
break;
|
||||
|
||||
case 53: // Hook - set transpose
|
||||
buf[0] = *p++ & 0x0F;
|
||||
player->decode_sysex_bytes(p, buf + 1, len - 1);
|
||||
player->maybe_set_transpose_part(buf);
|
||||
break;
|
||||
|
||||
case 64: // Marker
|
||||
p++;
|
||||
len--;
|
||||
while (len--) {
|
||||
se->handle_marker(player->_id, *p++);
|
||||
}
|
||||
break;
|
||||
|
||||
case 80: // Loop
|
||||
player->decode_sysex_bytes(p + 1, buf, len - 1);
|
||||
player->setLoop(READ_BE_UINT16(buf), READ_BE_UINT16(buf + 2),
|
||||
READ_BE_UINT16(buf + 4), READ_BE_UINT16(buf + 6),
|
||||
READ_BE_UINT16(buf + 8));
|
||||
break;
|
||||
|
||||
case 81: // End loop
|
||||
player->clearLoop();
|
||||
break;
|
||||
|
||||
case 96: // Set instrument
|
||||
part = player->getPart(p[0] & 0x0F);
|
||||
a = (p[1] & 0x0F) << 12 | (p[2] & 0x0F) << 8 | (p[3] & 0x0F) << 4 | (p[4] & 0x0F);
|
||||
if (part)
|
||||
part->set_instrument(a);
|
||||
break;
|
||||
|
||||
default:
|
||||
error("Unknown SysEx command %d", (int)code);
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Scumm
|
||||
Reference in New Issue
Block a user