aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/sci/sound/drivers/cms.cpp1396
-rw-r--r--engines/sci/sound/music.cpp5
2 files changed, 960 insertions, 441 deletions
diff --git a/engines/sci/sound/drivers/cms.cpp b/engines/sci/sound/drivers/cms.cpp
index 8b92432cb9..750de85229 100644
--- a/engines/sci/sound/drivers/cms.cpp
+++ b/engines/sci/sound/drivers/cms.cpp
@@ -33,14 +33,154 @@
namespace Sci {
-// FIXME: We don't seem to be sending the polyphony init data, so disable this for now
-#define CMS_DISABLE_VOICE_MAPPING
+class MidiDriver_CMS;
+
+class CMSVoice {
+public:
+ CMSVoice(uint8 id, MidiDriver_CMS *driver, CMSEmulator *cms, SciSpan<const uint8>& patchData);
+ virtual ~CMSVoice() {}
+
+ virtual void noteOn(int note, int velocity) = 0;
+ virtual void noteOff() = 0;
+ virtual void stop() = 0;
+ virtual void programChange(int program) = 0;
+ virtual void pitchWheel() {}
+
+ virtual void update() = 0;
+
+ virtual void reset() {}
+ virtual void setPanMask(uint8) {}
+
+ uint8 _assign;
+ uint8 _note;
+ bool _sustained;
+ uint16 _duration;
+ uint16 _releaseDuration;
+ CMSVoice *_secondaryVoice;
+
+protected:
+ void sendFrequency();
+ void cmsWrite(uint8 reg, uint8 val);
+
+ CMSEmulator *_cms;
+ MidiDriver_CMS *_driver;
+ SciSpan<const uint8> _patchData;
+
+ const uint8 _id;
+ const uint8 _regOffset;
+ const uint8 _portOffset;
+
+ static uint8 _octaveRegs[6];
+ static const int _frequencyTable[48];
+
+private:
+ virtual void recalculateFrequency(uint8 &freq, uint8 &octave) = 0;
+};
+
+class CMSVoice_V0 : public CMSVoice {
+public:
+ CMSVoice_V0(uint8 id, MidiDriver_CMS *driver, CMSEmulator *cms, SciSpan<const uint8>& patchData);
+ virtual ~CMSVoice_V0() {}
+
+ void noteOn(int note, int);
+ void noteOff();
+ void stop();
+ void programChange(int program);
+
+ void update();
+
+ void reset();
+ void setPanMask(uint8 mask);
+
+private:
+ void recalculateFrequency(uint8 &frequency, uint8 &octave);
+ void recalculateEnvelopeLevels();
+ void selectEnvelope(int id);
+
+ enum EnvelopeState {
+ kReady = 0,
+ kRestart = 1,
+ kAttack = 2,
+ kDecay = 3,
+ kSustain = 4,
+ kRelease = 5
+ };
+
+ EnvelopeState _envState;
+ uint8 _envAR;
+ uint8 _envTL;
+ uint8 _envDR;
+ uint8 _envSL;
+ uint8 _envRR;
+ uint8 _envSLI;
+ uint8 _envPAC;
+ uint8 _envPA;
+
+ uint8 _envNote;
+ uint8 _envSSL;
+ uint8 _panMask;
+ uint8 _strMask;
+
+ int8 _transFreq;
+ int8 _transOct;
+
+ bool _vbrOn;
+ uint8 _vbrSteps;
+ uint8 _vbrState;
+ int8 _vbrMod;
+ int8 _vbrCur;
+ int16 _vbrPhase;
+
+ int _currentLevel;
+ bool _updateCMS;
+
+ const bool _isSecondary;
+
+ static const uint8 _volumeTable[176];
+ static const uint8 _pitchWheelTable[65];
+};
+
+class CMSVoice_V1 : public CMSVoice {
+public:
+ CMSVoice_V1(uint8 id, MidiDriver_CMS *driver, CMSEmulator *cms, SciSpan<const uint8>& patchData);
+ virtual ~CMSVoice_V1() {}
+
+ void noteOn(int note, int velocity);
+ void noteOff();
+ void stop();
+ void programChange(int program);
+ void pitchWheel();
+
+ void update();
+
+private:
+ void recalculateFrequency(uint8 &frequency, uint8 &octave);
+
+ void updateVoiceAmplitude();
+ void setupVoiceAmplitude();
+
+ SciSpan<const uint8> _patchDataCur;
+ uint8 _velocity;
+ uint8 _patchDataIndex;
+ uint8 _amplitudeTimer;
+ uint8 _amplitudeModifier;
+ bool _release;
+
+ static const int _velocityTable[32];
+};
class MidiDriver_CMS : public MidiDriver_Emulated {
public:
- MidiDriver_CMS(Audio::Mixer *mixer, ResourceManager *resMan)
- : MidiDriver_Emulated(mixer), _resMan(resMan), _cms(0), _rate(0), _playSwitch(true), _masterVolume(0) {
- }
+ enum {
+ MIDI_PROP_CHANNEL_VOLUME = 1,
+ MIDI_PROP_CHANNEL_PITCHWHEEL = 2,
+ MIDI_PROP_CHANNEL_PANPOS = 3,
+ MIDI_PROP_PLAYSWITCH = 4
+ };
+
+public:
+ MidiDriver_CMS(Audio::Mixer *mixer, ResourceManager *resMan, SciVersion version);
+ ~MidiDriver_CMS();
int open();
void close();
@@ -48,105 +188,98 @@ public:
void send(uint32 b);
uint32 property(int prop, uint32 param);
+ void initTrack(SciSpan<const byte>& header);
+
+ void onTimer();
+
MidiChannel *allocateChannel() { return 0; }
MidiChannel *getPercussionChannel() { return 0; }
bool isStereo() const { return true; }
int getRate() const { return _rate; }
- void playSwitch(bool play);
private:
+ void noteOn(int channelNr, int note, int velocity);
+ void noteOff(int channelNr, int note);
+ void controlChange(int channelNr, int control, int value);
+ void programChange(int channelNr, int value);
+ void pitchWheel(int channelNr, int value);
+
+ void voiceMapping(int channelNr, int value);
+ void bindVoices(int channelNr, int voices, bool bindSecondary, bool doProgramChange);
+ void unbindVoices(int channelNr, int voices, bool bindSecondary);
+ void donateVoices(bool bindSecondary);
+ int findVoice(int channelNr, int note);
+ int findVoiceBasic(int channelNr);
+
+ void writeToChip(int chip, int address, int data);
void generateSamples(int16 *buffer, int len);
- ResourceManager *_resMan;
- CMSEmulator *_cms;
-
- void writeToChip1(int address, int data);
- void writeToChip2(int address, int data);
-
- int32 _samplesPerCallback;
- int32 _samplesPerCallbackRemainder;
- int32 _samplesTillCallback;
- int32 _samplesTillCallbackRemainder;
-
- int _rate;
- bool _playSwitch;
- uint16 _masterVolume;
-
- Common::SpanOwner<SciSpan<uint8> > _patchData;
-
struct Channel {
- Channel()
- : patch(0), volume(0), pan(0x40), hold(0), extraVoices(0),
- pitchWheel(0x2000), pitchModifier(0), pitchAdditive(false),
- lastVoiceUsed(0) {
- }
-
- uint8 patch;
+ Channel() : program(0), volume(0), pan(0x40), hold(0), missingVoices(0), lastVoiceUsed(0), pitchWheel(0x2000), isValid(true) {}
+ uint8 program;
uint8 volume;
uint8 pan;
uint8 hold;
- uint8 extraVoices;
- uint16 pitchWheel;
- uint8 pitchModifier;
- bool pitchAdditive;
+ uint8 missingVoices;
uint8 lastVoiceUsed;
+ uint16 pitchWheel;
+ bool isValid;
};
Channel _channel[16];
+ CMSVoice *_voice[12];
- struct Voice {
- Voice() : channel(0xFF), note(0xFF), sustained(0xFF), ticks(0),
- turnOffTicks(0), patchDataPtr(), patchDataIndex(0),
- amplitudeTimer(0), amplitudeModifier(0), turnOff(false),
- velocity(0) {
- }
+ const int _numVoicesPrimary;
+ const int _numVoicesSecondary;
- uint8 channel;
- uint8 note;
- uint8 sustained;
- uint16 ticks;
- uint16 turnOffTicks;
- SciSpan<uint8> patchDataPtr;
- uint8 patchDataIndex;
- uint8 amplitudeTimer;
- uint8 amplitudeModifier;
- bool turnOff;
- uint8 velocity;
- };
+ CMSEmulator *_cms;
+ ResourceManager *_resMan;
+ Common::SpanOwner<SciSpan<const uint8> > _patchData;
- Voice _voice[12];
+ bool _playSwitch;
+ uint16 _masterVolume;
+
+ const int _actualTimerInterval;
+ const int _reqTimerInterval;
+ int _updateTimer;
+ int _rate;
- void voiceOn(int voice, int note, int velocity);
- void voiceOff(int voice);
+ SciVersion _version;
+};
- void noteSend(int voice);
+CMSVoice::CMSVoice(uint8 id, MidiDriver_CMS* driver, CMSEmulator *cms, SciSpan<const uint8>& patchData) : _id(id), _regOffset(id > 5 ? id - 6 : id), _portOffset(id > 5 ? 2 : 0),
+ _driver(driver), _cms(cms), _assign(0xFF), _note(0xFF), _sustained(false), _duration(0), _releaseDuration(0), _secondaryVoice(0), _patchData(patchData) {
+ assert(_id < 12);
+ _octaveRegs[_id >> 1] = 0;
+}
- void noteOn(int channel, int note, int velocity);
- void noteOff(int channel, int note);
- void controlChange(int channel, int control, int value);
- void pitchWheel(int channel, int value);
+void CMSVoice::sendFrequency() {
+ uint8 frequency = 0;
+ uint8 octave = 0;
- void voiceMapping(int channel, int value);
- void bindVoices(int channel, int voices);
- void unbindVoices(int channel, int voices);
- void donateVoices();
- int findVoice(int channel);
+ recalculateFrequency(frequency, octave);
- int findVoiceBasic(int channel);
+ uint8 octaveData = _octaveRegs[_id >> 1];
+ octaveData = (_id & 1) ? (octaveData & 0x0F) | (octave << 4) : (octaveData & 0xF0) | octave;
- void updateVoiceAmplitude(int voice);
- void setupVoiceAmplitude(int voice);
+ cmsWrite(8 + _regOffset, frequency);
+ cmsWrite(0x10 + (_regOffset >> 1), octaveData);
+}
- uint8 _octaveRegs[2][3];
+void CMSVoice::cmsWrite(uint8 reg, uint8 val) {
+ _cms->portWrite(0x221 + _portOffset, reg);
+ _cms->portWrite(0x220 + _portOffset, val);
- static const int _timerFreq = 60;
+ if (reg >= 16 && reg <= 18)
+ _octaveRegs[_id >> 1] = val;
+}
- static const int _frequencyTable[];
- static const int _velocityTable[];
+uint8 CMSVoice::_octaveRegs[6] = {
+ 0, 0, 0, 0, 0, 0
};
-const int MidiDriver_CMS::_frequencyTable[] = {
+const int CMSVoice::_frequencyTable[48] = {
3, 10, 17, 24,
31, 38, 46, 51,
58, 64, 71, 77,
@@ -161,13 +294,438 @@ const int MidiDriver_CMS::_frequencyTable[] = {
242, 246, 250, 253
};
-const int MidiDriver_CMS::_velocityTable[] = {
+CMSVoice_V0::CMSVoice_V0(uint8 id, MidiDriver_CMS* driver, CMSEmulator *cms, SciSpan<const uint8>& patchData) : CMSVoice(id, driver, cms, patchData), _envState(kReady), _currentLevel(0), _strMask(0),
+ _envAR(0), _envTL(0), _envDR(0), _envSL(0), _envRR(0), _envSLI(0), _vbrOn(false), _vbrSteps(0), _vbrState(0), _vbrMod(0), _vbrCur(0), _isSecondary(id > 7),
+ _vbrPhase(0), _transOct(0), _transFreq(0), _envPAC(0), _envPA(0), _panMask(_id & 1 ? 0xF0 : 0x0F), _envSSL(0), _envNote(0xFF), _updateCMS(false) {
+}
+
+void CMSVoice_V0::noteOn(int note, int) {
+ if (!_driver->property(MidiDriver_CMS::MIDI_PROP_PLAYSWITCH, 0xFFFF) || !_envTL)
+ return;
+
+ _note = note;
+ _envNote = note + 3;
+ _envState = kRestart;
+ _vbrPhase = 0;
+ _vbrCur = _vbrMod;
+ _vbrState = _vbrSteps & 0x0F;
+ _envPAC = _envPA;
+
+ if (_secondaryVoice)
+ _secondaryVoice->noteOn(note, 127);
+}
+
+void CMSVoice_V0::noteOff() {
+ if (!_driver->property(MidiDriver_CMS::MIDI_PROP_PLAYSWITCH, 0xFFFF) || !_envTL)
+ return;
+
+ _note = 0xFF;
+ _envState = kRelease;
+ if (_secondaryVoice)
+ _secondaryVoice->noteOff();
+}
+
+void CMSVoice_V0::stop() {
+ _note = 0xFF;
+ if (_envState != kReady)
+ _envState = kRelease;
+ if (_secondaryVoice)
+ _secondaryVoice->stop();
+}
+
+void CMSVoice_V0::programChange(int program) {
+ assert(program < 128);
+ if (program == 127) {
+ // This seems to replace the start of track offset with the current position so that 0xFC (kEndOfTrack)
+ // midi events would not reset the track to the start, but to the current position instead. This cannot
+ // be handled here. All versions of the SCI0 driver that I have seen so far do this. Still, I somehow
+ // doubt that it will ever come up, but let's see...
+ warning("CMSVoice_V0::programChange(): Unhandled program change 127");
+ return;
+ }
+
+ SciSpan<const uint8> data = _patchData.subspan(128 + (_patchData.getUint8At(program) << 3));
+ uint8 pos = _isSecondary ? 3 : 0;
+
+ selectEnvelope(data.getUint8At(pos++));
+
+ if (_isSecondary) {
+ _envSSL = data.getUint8At(pos++);
+ // This decides whether the secondary voice has the same or the opposite pan position as the primary voice.
+ _panMask = _strMask ^ -(_envSSL & 1);
+ }
+
+ _transOct = data.getInt8At(pos++);
+ _transFreq = data.getInt8At(pos++);
+
+ if (_isSecondary)
+ _envPA = data.getUint8At(pos++);
+
+ if (_secondaryVoice) {
+ assert(!_isSecondary);
+ if (data.getUint8At(pos) == 0xFF) {
+ _secondaryVoice->stop();
+ _secondaryVoice->_assign = 0xFF;
+ _secondaryVoice = 0;
+ } else {
+ _secondaryVoice->setPanMask(_panMask);
+ _secondaryVoice->programChange(program);
+ }
+ }
+}
+
+void CMSVoice_V0::update() {
+ if (_updateCMS) {
+ sendFrequency();
+ cmsWrite(_regOffset, ((_currentLevel & 0xF0) | (_currentLevel >> 4)) & _panMask);
+ _updateCMS = false;
+ }
+
+ recalculateEnvelopeLevels();
+
+ switch (_envState) {
+ case kReady:
+ _envNote = 0xFF;
+ return;
+
+ case kRestart:
+ if (_envPAC) {
+ --_envPAC;
+ break;
+ } else {
+ _currentLevel = ((_currentLevel >> 1) > _envAR) ? ((_currentLevel >> 1) - _envAR) : 0;
+ //_currentLevel = ((_currentLevel >> 1) > (int8)_envAR) ? ((_currentLevel >> 1) - _envAR1) & 0xFF : (_envAR - _envAR1) & 0xFF;
+ _envState = kAttack;
+ }
+ // fall through
+
+ case kAttack:
+ _currentLevel = _currentLevel + _envAR;
+ if (_currentLevel > _envTL || _currentLevel > 0xFF) {
+ _currentLevel = _envTL;
+ _envState = kDecay;
+ }
+ break;
+
+ case kDecay:
+ _currentLevel -= _envDR;
+ if (_currentLevel <= _envSL) {
+ if (_currentLevel < 0)
+ _currentLevel = 0;
+ _envState = kSustain;
+ }
+ break;
+
+ case kSustain:
+ _currentLevel = _envSL;
+ break;
+
+ case kRelease:
+ _currentLevel -= _envRR;
+ if (_currentLevel < 0) {
+ _currentLevel = 0;
+ _envState = kReady;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (_vbrOn && _envState != kRestart) {
+ _vbrPhase += _vbrCur;
+ if (!--_vbrState) {
+ _vbrCur = -_vbrCur;
+ _vbrState = (_vbrSteps & 0x0F) << 1;
+ }
+ }
+
+ _updateCMS = true;
+ ++_duration;
+}
+
+void CMSVoice_V0::reset() {
+ _envState = kReady;
+ _secondaryVoice = 0;
+ _assign = _note = _envNote = 0xFF;
+ _panMask = _id & 1 ? 0xF0 : 0x0F;
+ _envTL = 0;
+ _currentLevel = 0;
+ _duration = 0;
+ _envPA = 0;
+ _transFreq = _transOct = 0;
+ selectEnvelope(3);
+}
+
+void CMSVoice_V0::setPanMask(uint8 mask) {
+ _strMask = mask;
+}
+
+void CMSVoice_V0::recalculateFrequency(uint8 &freq, uint8 &octave) {
+ if (_assign == 0xFF || _envNote == 0xFF)
+ return;
+
+ uint8 note = _envNote % 12;
+ octave = CLIP<int>(_envNote / 12 - 2, 0, 7);
+
+ int16 pbVal = (_driver->property(MidiDriver_CMS::MIDI_PROP_CHANNEL_PITCHWHEEL, _assign) & 0x7FFF) - 0x2000;
+ int16 pbEff = _pitchWheelTable[ABS(pbVal) >> 7] * ((pbVal < 0) ? -1 : 1);
+ int frequency = note * 4 + pbEff;
+
+ if (frequency < 0) {
+ if (octave) {
+ frequency += 48;
+ --octave;
+ } else {
+ frequency = 0;
+ }
+ } else if (frequency >= 48) {
+ if (octave < 7) {
+ frequency -= 48;
+ ++octave;
+ } else {
+ frequency = 47;
+ }
+ }
+
+ octave = CLIP<int8>(octave + _transOct, 0, 7);
+ frequency = _frequencyTable[frequency & 0xFF] + _transFreq + _vbrPhase;
+
+ if (frequency > 255) {
+ frequency &= 0xFF;
+ octave++;
+ } else if (frequency < 0) {
+ frequency &= 0xFF;
+ octave--;
+ }
+
+ octave = CLIP<int8>(octave, 0, 7);
+ freq = frequency;
+}
+
+void CMSVoice_V0::recalculateEnvelopeLevels() {
+ uint8 chanVol = _driver->property(MidiDriver_CMS::MIDI_PROP_CHANNEL_VOLUME, _assign);
+
+ if (_envTL && _isSecondary) {
+ int volIndexTLS = (chanVol >> 4) | (_envSSL & 0xF0);
+ assert(volIndexTLS < ARRAYSIZE(_volumeTable));
+ _envTL = _volumeTable[volIndexTLS];
+ } else if (_envTL) {
+ _envTL = chanVol;
+ }
+
+ int volIndexSL = (_envSLI << 4) + (_envTL >> 4);
+ assert(volIndexSL < ARRAYSIZE(_volumeTable));
+ _envSL = _volumeTable[volIndexSL];
+}
+
+void CMSVoice_V0::selectEnvelope(int id) {
+ SciSpan<const uint8> in = _patchData.subspan(512 + ((id & 0x1F) << 3));
+ _envAR = *in++;
+ _envTL = *in++;
+ _envDR = *in++;
+ _envSLI = *in++;
+ _envRR = *in++;
+ /*unused*/in++;
+ _vbrMod = *in++;
+ _vbrSteps = *in++;
+ _vbrOn = _vbrMod;
+ _vbrCur = _vbrMod;
+ _vbrState = _vbrSteps & 0x0F;
+ _vbrPhase = 0;
+}
+
+const uint8 CMSVoice_V0::_volumeTable[176] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x20, 0x20, 0x20, 0x30, 0x30, 0x30, 0x30, 0x40, 0x40,
+ 0x00, 0x00, 0x00, 0x10, 0x10, 0x20, 0x20, 0x20, 0x30, 0x30, 0x40, 0x40, 0x40, 0x50, 0x50, 0x60,
+ 0x00, 0x00, 0x10, 0x10, 0x20, 0x20, 0x30, 0x30, 0x40, 0x40, 0x50, 0x50, 0x60, 0x60, 0x70, 0x70,
+ 0x00, 0x00, 0x10, 0x10, 0x20, 0x30, 0x30, 0x40, 0x40, 0x50, 0x60, 0x60, 0x70, 0x70, 0x80, 0x90,
+ 0x00, 0x00, 0x10, 0x20, 0x20, 0x30, 0x40, 0x40, 0x50, 0x60, 0x70, 0x70, 0x80, 0x90, 0x90, 0xa0,
+ 0x00, 0x00, 0x10, 0x20, 0x30, 0x40, 0x40, 0x50, 0x60, 0x70, 0x80, 0x80, 0x90, 0xa0, 0xb0, 0xc0,
+ 0x00, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0x90, 0xa0, 0xb0, 0xc0, 0xd0,
+ 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0
+};
+
+const uint8 CMSVoice_V0::_pitchWheelTable[65] = {
+ 0x00, 0x01, 0x02, 0x02, 0x03, 0x04, 0x05, 0x05,
+ 0x06, 0x07, 0x08, 0x08, 0x09, 0x0a, 0x0b, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0e, 0x0f, 0x10, 0x11, 0x11,
+ 0x12, 0x13, 0x14, 0x14, 0x15, 0x16, 0x17, 0x17,
+ 0x18, 0x19, 0x1a, 0x1a, 0x1b, 0x1c, 0x1d, 0x1d,
+ 0x1e, 0x1f, 0x20, 0x20, 0x21, 0x22, 0x23, 0x23,
+ 0x24, 0x25, 0x26, 0x26, 0x27, 0x28, 0x29, 0x29,
+ 0x2a, 0x2b, 0x2c, 0x2c, 0x2d, 0x2e, 0x2f, 0x2f,
+ 0x30
+};
+
+CMSVoice_V1::CMSVoice_V1(uint8 id, MidiDriver_CMS* driver, CMSEmulator *cms, SciSpan<const uint8>& patchData) : CMSVoice(id, driver, cms, patchData), _velocity(0), _patchDataIndex(0),
+ _amplitudeTimer(0), _amplitudeModifier(0), _release(false) {
+}
+
+void CMSVoice_V1::noteOn(int note, int velocity) {
+ _note = note;
+ _release = false;
+ _patchDataIndex = 0;
+ _amplitudeTimer = 0;
+ _duration = 0;
+ _releaseDuration = 0;
+ _velocity = velocity ? _velocityTable[velocity >> 3] : 0;
+ sendFrequency();
+}
+
+void CMSVoice_V1::noteOff() {
+ _release = true;
+}
+
+void CMSVoice_V1::stop() {
+ _velocity = 0;
+ _note = 0xFF;
+ _sustained = false;
+ _release = false;
+ _patchDataIndex = 0;
+ _amplitudeTimer = 0;
+ _amplitudeModifier = 0;
+ _duration = 0;
+ _releaseDuration = 0;
+
+ setupVoiceAmplitude();
+}
+
+void CMSVoice_V1::programChange(int program) {
+ _patchDataCur = _patchData.subspan(_patchData.getUint16LEAt(program << 1));
+}
+
+void CMSVoice_V1::pitchWheel() {
+ sendFrequency();
+}
+
+void CMSVoice_V1::update() {
+ if (_note == 0xFF)
+ return;
+
+ if (_release)
+ ++_releaseDuration;
+ ++_duration;
+
+ updateVoiceAmplitude();
+ setupVoiceAmplitude();
+}
+
+void CMSVoice_V1::recalculateFrequency(uint8 &freq, uint8 &octave) {
+ assert(_assign != 0xFF);
+
+ int frequency = (CLIP<int>(_note, 21, 116) - 21) * 4;
+ int16 pw = _driver->property(MidiDriver_CMS::MIDI_PROP_CHANNEL_PITCHWHEEL, _assign);
+ int modifier = (pw < 0x2000) ? (0x2000 - pw) / 170 : ((pw > 0x2000) ? (pw - 0x2000) / 170 : 0);
+
+ if (modifier) {
+ if (pw < 0x2000) {
+ if (frequency > modifier)
+ frequency -= modifier;
+ else
+ frequency = 0;
+ } else {
+ int tempFrequency = 384 - frequency;
+ if (modifier < tempFrequency)
+ frequency += modifier;
+ else
+ frequency = 383;
+ }
+ }
+
+ octave = 0;
+ while (frequency >= 48) {
+ frequency -= 48;
+ ++octave;
+ }
+
+ freq = _frequencyTable[frequency & 0xFF];
+}
+
+void CMSVoice_V1::updateVoiceAmplitude() {
+ if (_amplitudeTimer != 0 && _amplitudeTimer != 254) {
+ --_amplitudeTimer;
+ return;
+ } else if (_amplitudeTimer == 254) {
+ if (!_release)
+ return;
+ _amplitudeTimer = 0;
+ }
+
+ int nextDataIndex = _patchDataIndex;
+ uint8 timerData = 0;
+ uint8 amplitudeData = _patchDataCur[nextDataIndex];
+
+ if (amplitudeData == 255) {
+ timerData = amplitudeData = 0;
+ stop();
+ } else {
+ timerData = _patchDataCur[nextDataIndex + 1];
+ nextDataIndex += 2;
+ }
+
+ _patchDataIndex = nextDataIndex;
+ _amplitudeTimer = timerData;
+ _amplitudeModifier = amplitudeData;
+}
+
+void CMSVoice_V1::setupVoiceAmplitude() {
+ assert(_assign != 0xFF);
+ uint amplitude = 0;
+ uint8 chanVolume = _driver->property(MidiDriver_CMS::MIDI_PROP_CHANNEL_VOLUME, _assign);
+ uint8 masterVolume = _driver->property(MIDI_PROP_MASTER_VOLUME, 0xFFFF);
+
+ if (chanVolume && _velocity && _amplitudeModifier && masterVolume) {
+ amplitude = chanVolume * _velocity;
+ amplitude /= 0x0F;
+ amplitude *= _amplitudeModifier;
+ amplitude /= 0x0F;
+ amplitude *= masterVolume;
+ amplitude /= 0x0F;
+
+ if (!amplitude)
+ ++amplitude;
+ }
+
+ uint8 amplitudeData = 0;
+ int pan = _driver->property(MidiDriver_CMS::MIDI_PROP_CHANNEL_PANPOS, _assign) >> 2;
+ if (pan >= 16) {
+ amplitudeData = (amplitude * (31 - pan) / 0x0F) & 0x0F;
+ amplitudeData |= (amplitude << 4);
+ } else {
+ amplitudeData = (amplitude * pan / 0x0F) & 0x0F;
+ amplitudeData <<= 4;
+ amplitudeData |= amplitude;
+ }
+
+ if (!_driver->property(MidiDriver_CMS::MIDI_PROP_PLAYSWITCH, 0xFFFF))
+ amplitudeData = 0;
+
+ cmsWrite(_regOffset, amplitudeData);
+}
+
+const int CMSVoice_V1::_velocityTable[32] = {
1, 3, 6, 8, 9, 10, 11, 12,
12, 13, 13, 14, 14, 14, 15, 15,
0, 1, 2, 2, 3, 4, 4, 5,
6, 6, 7, 8, 8, 9, 10, 10
};
+MidiDriver_CMS::MidiDriver_CMS(Audio::Mixer* mixer, ResourceManager* resMan, SciVersion version) : MidiDriver_Emulated(mixer), _resMan(resMan),
+ _version(version), _cms(0), _rate(0), _playSwitch(true), _masterVolume(0), _numVoicesPrimary(version > SCI_VERSION_0_LATE ? 12 : 8),
+ _actualTimerInterval(1000000 / _baseFreq), _reqTimerInterval(1000000/60), _numVoicesSecondary(version > SCI_VERSION_0_LATE ? 0 : 4) {
+ memset(_voice, 0, sizeof(_voice));
+ _updateTimer = _reqTimerInterval;
+}
+
+MidiDriver_CMS::~MidiDriver_CMS() {
+ for (int i = 0; i < 12; ++i)
+ delete _voice[i];
+}
+
int MidiDriver_CMS::open() {
if (_cms)
return MERR_ALREADY_OPEN;
@@ -176,36 +734,42 @@ int MidiDriver_CMS::open() {
Resource *res = _resMan->findResource(ResourceId(kResourceTypePatch, 101), false);
if (!res)
return -1;
+
+ _patchData->allocateFromSpan(_version < SCI_VERSION_1_EARLY ? res->subspan(30) : *res);
- _patchData->allocateFromSpan(*res);
-
+ _rate = _mixer->getOutputRate();
+ _cms = new CMSEmulator(_rate);
+ assert(_cms);
+
for (uint i = 0; i < ARRAYSIZE(_channel); ++i)
_channel[i] = Channel();
- for (uint i = 0; i < ARRAYSIZE(_voice); ++i)
- _voice[i] = Voice();
-
- _rate = _mixer->getOutputRate();
- _cms = new CMSEmulator(_rate);
- assert(_cms);
+ for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
+ if (_version < SCI_VERSION_1_EARLY)
+ _voice[i] = new CMSVoice_V0(i, this, _cms, *_patchData);
+ else
+ _voice[i] = new CMSVoice_V1(i, this, _cms, *_patchData);
+ }
+
_playSwitch = true;
_masterVolume = 0;
for (int i = 0; i < 31; ++i) {
- writeToChip1(i, 0);
- writeToChip2(i, 0);
+ writeToChip(0, i, 0);
+ writeToChip(1, i, 0);
}
- writeToChip1(0x14, 0xFF);
- writeToChip2(0x14, 0xFF);
+ // Enable frequency for all channels
+ writeToChip(0, 0x14, _version < SCI_VERSION_1_EARLY ? 0x3F : 0xFF);
+ writeToChip(1, 0x14, _version < SCI_VERSION_1_EARLY ? 0x3F : 0xFF);
- writeToChip1(0x1C, 1);
- writeToChip2(0x1C, 1);
+ // Sync and reset generators
+ writeToChip(0, 0x1C, 2);
+ writeToChip(1, 0x1C, 2);
- _samplesPerCallback = getRate() / _timerFreq;
- _samplesPerCallbackRemainder = getRate() % _timerFreq;
- _samplesTillCallback = 0;
- _samplesTillCallbackRemainder = 0;
+ // Enable all channels
+ writeToChip(0, 0x1C, 1);
+ writeToChip(1, 0x1C, 1);
int retVal = MidiDriver_Emulated::open();
if (retVal != 0)
@@ -229,6 +793,10 @@ void MidiDriver_CMS::send(uint32 b) {
const uint8 op1 = (b >> 8) & 0xff;
const uint8 op2 = (b >> 16) & 0xff;
+ // This is a SCI0 only feature. For SCI1 we simply set all channels to valid by default so that this check will always pass.
+ if (!_channel[channel].isValid)
+ return;
+
switch (command) {
case 0x80:
noteOff(channel, op1);
@@ -243,7 +811,7 @@ void MidiDriver_CMS::send(uint32 b) {
break;
case 0xC0:
- _channel[channel].patch = op1;
+ programChange(channel, op1);
break;
case 0xE0:
@@ -261,215 +829,140 @@ uint32 MidiDriver_CMS::property(int prop, uint32 param) {
if (param != 0xffff)
_masterVolume = param;
return _masterVolume;
-
+ case MIDI_PROP_PLAYSWITCH:
+ if (param != 0xffff)
+ _playSwitch = param ? true : false;
+ return _playSwitch ? 1 : 0;
+ case MIDI_PROP_CHANNEL_VOLUME:
+ return (param < 16) ? _channel[param].volume : 0;
+ case MIDI_PROP_CHANNEL_PITCHWHEEL:
+ return (param < 16) ? _channel[param].pitchWheel : 0;
+ case MIDI_PROP_CHANNEL_PANPOS:
+ return (param < 16) ? _channel[param].pan : 0;
default:
return MidiDriver_Emulated::property(prop, param);
}
}
-void MidiDriver_CMS::playSwitch(bool play) {
- _playSwitch = play;
-}
-
-void MidiDriver_CMS::writeToChip1(int address, int data) {
- _cms->portWrite(0x221, address);
- _cms->portWrite(0x220, data);
-
- if (address >= 16 && address <= 18)
- _octaveRegs[0][address - 16] = data;
-}
-
-void MidiDriver_CMS::writeToChip2(int address, int data) {
- _cms->portWrite(0x223, address);
- _cms->portWrite(0x222, data);
-
- if (address >= 16 && address <= 18)
- _octaveRegs[1][address - 16] = data;
-}
+void MidiDriver_CMS::initTrack(SciSpan<const byte>& header) {
+ if (!_isOpen || _version > SCI_VERSION_0_LATE)
+ return;
-void MidiDriver_CMS::voiceOn(int voiceNr, int note, int velocity) {
- Voice &voice = _voice[voiceNr];
- voice.note = note;
- voice.turnOff = false;
- voice.patchDataIndex = 0;
- voice.amplitudeTimer = 0;
- voice.ticks = 0;
- voice.turnOffTicks = 0;
- voice.patchDataPtr = _patchData->subspan(_patchData->getUint16LEAt(_channel[voice.channel].patch * 2));
- if (velocity)
- velocity = _velocityTable[(velocity >> 3)];
- voice.velocity = velocity;
- noteSend(voiceNr);
-}
+ uint8 readPos = 0;
+ uint8 caps = header.getInt8At(readPos++);
+ int numChan = (caps == 2) ? 15 : 16;
+ if (caps != 0 && caps != 2)
+ return;
-void MidiDriver_CMS::voiceOff(int voiceNr) {
- Voice &voice = _voice[voiceNr];
- voice.velocity = 0;
- voice.note = 0xFF;
- voice.sustained = 0;
- voice.turnOff = false;
- voice.patchDataIndex = 0;
- voice.amplitudeTimer = 0;
- voice.amplitudeModifier = 0;
- voice.ticks = 0;
- voice.turnOffTicks = 0;
+ for (int i = 0; i < 12; ++i)
+ _voice[i]->reset();
- setupVoiceAmplitude(voiceNr);
-}
+ for (int i = 0; i < 16; ++i) {
+ _channel[i].isValid = false;
+ _channel[i].volume = 180;
+ _channel[i].pitchWheel = 0x2000;
+ _channel[i].pan = 0;
-void MidiDriver_CMS::noteSend(int voiceNr) {
- Voice &voice = _voice[voiceNr];
+ if (i >= numChan)
+ continue;
- int frequency = (CLIP<int>(voice.note, 21, 116) - 21) * 4;
- if (_channel[voice.channel].pitchModifier) {
- int modifier = _channel[voice.channel].pitchModifier;
+ uint8 num = header.getInt8At(readPos++) & 0x0F;
+ uint8 flags = header.getInt8At(readPos++);
- if (!_channel[voice.channel].pitchAdditive) {
- if (frequency > modifier)
- frequency -= modifier;
- else
- frequency = 0;
- } else {
- int tempFrequency = 384 - frequency;
- if (modifier < tempFrequency)
- frequency += modifier;
- else
- frequency = 383;
- }
- }
+ if (num == 15 || !(flags & 4))
+ continue;
- int chipNumber = 0;
- if (voiceNr >= 6) {
- voiceNr -= 6;
- chipNumber = 1;
- }
+ // Another strange thing about this driver... All channels to be used have to be marked as valid here
+ // or they will be blocked in send(). Even control change voice mapping won't be accessible. This means
+ // that a num == 0 setting could even make sense here, since it will mark that channel as valid for
+ // later use (e.g. assigning voices via control change voice mapping).
+ _channel[i].isValid = true;
+ if (num == 0)
+ continue;
- int octave = 0;
- while (frequency >= 48) {
- frequency -= 48;
- ++octave;
+ // This weird driver will assign a second voice if the number of requested voices is exactly 1.
+ // The secondary voice is configured differently (has its own instrument patch data) and it
+ // is controlled through the primary voice. It will not receive its own separate commands. The
+ // main purpose seems providing stereo channels with 2 discrete voices for the left and right
+ // speaker output. However, the instrument patch can also turn this around so that both voices
+ // use the same panning. What an awesome concept...
+ bindVoices(i, num, num == 1, false);
}
+}
- frequency = _frequencyTable[frequency];
-
- if (chipNumber == 1)
- writeToChip2(8 + voiceNr, frequency);
- else
- writeToChip1(8 + voiceNr, frequency);
-
- uint8 octaveData = _octaveRegs[chipNumber][voiceNr >> 1];
-
- if (voiceNr & 1) {
- octaveData &= 0x0F;
- octaveData |= (octave << 4);
- } else {
- octaveData &= 0xF0;
- octaveData |= octave;
+void MidiDriver_CMS::onTimer() {
+ for (_updateTimer -= _actualTimerInterval; _updateTimer <= 0; _updateTimer += _reqTimerInterval) {
+ for (uint i = 0; i < ARRAYSIZE(_voice); ++i)
+ _voice[i]->update();
}
-
- if (chipNumber == 1)
- writeToChip2(0x10 + (voiceNr >> 1), octaveData);
- else
- writeToChip1(0x10 + (voiceNr >> 1), octaveData);
}
-void MidiDriver_CMS::noteOn(int channel, int note, int velocity) {
+void MidiDriver_CMS::noteOn(int channelNr, int note, int velocity) {
if (note < 21 || note > 116)
return;
if (velocity == 0) {
- noteOff(channel, note);
+ noteOff(channelNr, note);
return;
}
- for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
- if (_voice[i].channel == channel && _voice[i].note == note) {
- _voice[i].sustained = 0;
- voiceOff(i);
- voiceOn(i, note, velocity);
+ for (int i = 0; i < _numVoicesPrimary; ++i) {
+ if (_voice[i]->_assign == channelNr && _voice[i]->_note == note) {
+ if (_version > SCI_VERSION_0_LATE) {
+ _voice[i]->stop();
+ _voice[i]->programChange(_channel[channelNr].program);
+ }
+ _voice[i]->noteOn(note, velocity);
return;
}
}
#ifdef CMS_DISABLE_VOICE_MAPPING
- int voice = findVoiceBasic(channel);
+ int id = findVoiceBasic(channelNr);
#else
- int voice = findVoice(channel);
+ int id = findVoice(channelNr, note);
#endif
- if (voice != -1)
- voiceOn(voice, note, velocity);
-}
-
-int MidiDriver_CMS::findVoiceBasic(int channel) {
- int voice = -1;
- int oldestVoice = -1;
- int oldestAge = -1;
-
- // Try to find a voice assigned to this channel that is free (round-robin)
- for (int i = 0; i < ARRAYSIZE(_voice); i++) {
- int v = (_channel[channel].lastVoiceUsed + i + 1) % ARRAYSIZE(_voice);
-
- if (_voice[v].note == 0xFF) {
- voice = v;
- break;
- }
-
- // We also keep track of the oldest note in case the search fails
- if (_voice[v].ticks > oldestAge) {
- oldestAge = _voice[v].ticks;
- oldestVoice = v;
- }
+ if (id != -1) {
+ if (_version > SCI_VERSION_0_LATE)
+ _voice[id]->programChange(_channel[channelNr].program);
+ _voice[id]->noteOn(note, velocity);
}
-
- if (voice == -1) {
- if (oldestVoice >= 0) {
- voiceOff(oldestVoice);
- voice = oldestVoice;
- } else {
- return -1;
- }
- }
-
- _voice[voice].channel = channel;
- _channel[channel].lastVoiceUsed = voice;
- return voice;
}
-void MidiDriver_CMS::noteOff(int channel, int note) {
+void MidiDriver_CMS::noteOff(int channelNr, int note) {
for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
- if (_voice[i].channel == channel && _voice[i].note == note) {
- if (_channel[channel].hold != 0)
- _voice[i].sustained = true;
+ if (_voice[i]->_assign == channelNr && _voice[i]->_note == note) {
+ if (_channel[channelNr].hold != 0)
+ _voice[i]->_sustained = true;
else
- _voice[i].turnOff = true;
+ _voice[i]->noteOff();
}
}
}
-void MidiDriver_CMS::controlChange(int channel, int control, int value) {
+void MidiDriver_CMS::controlChange(int channelNr, int control, int value) {
+ // The original SCI0 CMS drivers do not have Midi control 123. I support it nonetheless,
+ // since our current music engine seems to want to have it and it does not cause problems either.
+ if (_version < SCI_VERSION_1_EARLY && (control == 10 || control == 64))
+ return;
+
switch (control) {
case 7:
- if (value) {
- value >>= 3;
- if (!value)
- ++value;
- }
-
- _channel[channel].volume = value;
+ _channel[channelNr].volume = (_version < SCI_VERSION_1_EARLY) ? MAX<uint8>((value & 0x78) << 1, 0x40) : (value ? MAX<uint8>(value >> 3, 1) : 0);
break;
case 10:
- _channel[channel].pan = value;
+ _channel[channelNr].pan = value;
break;
case 64:
- _channel[channel].hold = value;
+ _channel[channelNr].hold = value;
if (!value) {
- for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
- if (_voice[i].channel == channel && _voice[i].sustained) {
- _voice[i].sustained = 0;
- _voice[i].turnOff = true;
+ for (int i = 0; i < _numVoicesPrimary; ++i) {
+ if (_voice[i]->_assign == channelNr && _voice[i]->_sustained) {
+ _voice[i]->_sustained = false;
+ _voice[i]->noteOff();
}
}
}
@@ -477,14 +970,14 @@ void MidiDriver_CMS::controlChange(int channel, int control, int value) {
case 75:
#ifndef CMS_DISABLE_VOICE_MAPPING
- voiceMapping(channel, value);
+ voiceMapping(channelNr, value);
#endif
break;
case 123:
for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
- if (_voice[i].channel == channel && _voice[i].note != 0xFF)
- voiceOff(i);
+ if (_voice[i]->_assign == channelNr && _voice[i]->_note != 0xFF)
+ _voice[i]->stop();
}
break;
@@ -493,80 +986,103 @@ void MidiDriver_CMS::controlChange(int channel, int control, int value) {
}
}
-void MidiDriver_CMS::pitchWheel(int channelNr, int value) {
- Channel &channel = _channel[channelNr];
- channel.pitchWheel = value;
- channel.pitchAdditive = false;
- channel.pitchModifier = 0;
+void MidiDriver_CMS::programChange(int channelNr, int value) {
+ _channel[channelNr].program = value;
+ if (_version > SCI_VERSION_0_LATE)
+ return;
- if (value < 0x2000) {
- channel.pitchModifier = (0x2000 - value) / 170;
- } else if (value > 0x2000) {
- channel.pitchModifier = (value - 0x2000) / 170;
- channel.pitchAdditive = true;
+ for (int i = 0; i < _numVoicesPrimary; ++i) {
+ if (_voice[i]->_assign == channelNr)
+ _voice[i]->programChange(value);
}
+}
- for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
- if (_voice[i].channel == channelNr && _voice[i].note != 0xFF)
- noteSend(i);
+void MidiDriver_CMS::pitchWheel(int channelNr, int value) {
+ _channel[channelNr].pitchWheel = value;
+ for (int i = 0; i < _numVoicesPrimary; ++i) {
+ if (_voice[i]->_assign == channelNr && _voice[i]->_note != 0xFF)
+ _voice[i]->pitchWheel();
}
}
void MidiDriver_CMS::voiceMapping(int channelNr, int value) {
int curVoices = 0;
- for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
- if (_voice[i].channel == channelNr)
+ for (int i = 0; i < _numVoicesPrimary; ++i) {
+ if (_voice[i]->_assign == channelNr)
++curVoices;
}
- curVoices += _channel[channelNr].extraVoices;
-
- if (curVoices == value) {
- return;
- } else if (curVoices < value) {
- bindVoices(channelNr, value - curVoices);
- } else {
- unbindVoices(channelNr, curVoices - value);
- donateVoices();
- }
+ curVoices += _channel[channelNr].missingVoices;
+
+ if (curVoices < value) {
+ bindVoices(channelNr, value - curVoices, curVoices == 0 && value == 1, true);
+ } else if (curVoices > value) {
+ unbindVoices(channelNr, curVoices - value, value == 1);
+ donateVoices(value == 1);
+ }/*else if (_version < SCI_VERSION_1_EARLY && value == 1) {
+ // The purpose of these lines would be to fill up missing secondary voices.
+ // I have commented them out, since the original driver doesn't do that either.
+ unbindVoices(channelNr, 1, true);
+ bindVoices(channelNr, 1, true, true);
+ }*/
}
-void MidiDriver_CMS::bindVoices(int channel, int voices) {
- for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
- if (_voice[i].channel == 0xFF)
+void MidiDriver_CMS::bindVoices(int channelNr, int voices, bool bindSecondary, bool doProgramChange) {
+ int secondary = bindSecondary ? _numVoicesSecondary : 0;
+
+ for (int i = 0; i < _numVoicesPrimary; ++i) {
+ if (_voice[i]->_assign != 0xFF)
continue;
- Voice &voice = _voice[i];
- voice.channel = channel;
+ _voice[i]->_assign = channelNr;
+ if (_voice[i]->_note != 0xFF)
+ _voice[i]->stop();
+
+ for (int ii = _numVoicesPrimary; ii < _numVoicesPrimary + secondary; ++ii) {
+ if (_voice[ii]->_assign != 0xFF)
+ continue;
- if (voice.note != 0xFF)
- voiceOff(i);
+ _voice[ii]->_assign = channelNr;
+ _voice[i]->_secondaryVoice = _voice[ii];
+
+ break;
+ }
+
+ // This will also release the secondary voice binding immediately if the current patch does
+ // not require such an extra channel. This condition will not be checked when called from initTrack().
+ if (doProgramChange)
+ _voice[i]->programChange(_channel[channelNr].program);
--voices;
if (voices == 0)
break;
}
- _channel[channel].extraVoices += voices;
-
- // The original called "PatchChange" here, since this just
- // copies the value of _channel[channel].patch to itself
- // it is left out here though.
+ _channel[channelNr].missingVoices += voices;
}
-void MidiDriver_CMS::unbindVoices(int channelNr, int voices) {
+void MidiDriver_CMS::unbindVoices(int channelNr, int voices, bool bindSecondary) {
+ int secondary = bindSecondary ? _numVoicesSecondary : 0;
Channel &channel = _channel[channelNr];
- if (channel.extraVoices >= voices) {
- channel.extraVoices -= voices;
+ if (channel.missingVoices >= voices) {
+ channel.missingVoices -= voices;
} else {
- voices -= channel.extraVoices;
- channel.extraVoices = 0;
+ voices -= channel.missingVoices;
+ channel.missingVoices = 0;
+
+ for (int i = 0; i < _numVoicesPrimary; ++i) {
+ if (_voice[i]->_assign == channelNr && _voice[i]->_note == 0xFF) {
+ _voice[i]->_assign = 0xFF;
+
+ CMSVoice *sec = _voice[i]->_secondaryVoice;
+ if (sec) {
+ sec->stop();
+ sec->_assign = 0xFF;
+ _voice[i]->_secondaryVoice = 0;
+ }
- for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
- if (_voice[i].channel == channelNr
- && _voice[i].note == 0xFF) {
--voices;
if (voices == 0)
return;
@@ -577,15 +1093,15 @@ void MidiDriver_CMS::unbindVoices(int channelNr, int voices) {
uint16 voiceTime = 0;
uint voiceNr = 0;
- for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
- if (_voice[i].channel != channelNr)
+ for (int i = 0; i < _numVoicesPrimary; ++i) {
+ if (_voice[i]->_assign != channelNr)
continue;
- uint16 curTime = _voice[i].turnOffTicks;
+ uint16 curTime = _voice[i]->_releaseDuration;
if (curTime)
curTime += 0x8000;
else
- curTime = _voice[i].ticks;
+ curTime = _voice[i]->_duration;
if (curTime >= voiceTime) {
voiceNr = i;
@@ -593,72 +1109,112 @@ void MidiDriver_CMS::unbindVoices(int channelNr, int voices) {
}
}
- _voice[voiceNr].sustained = 0;
- voiceOff(voiceNr);
- _voice[voiceNr].channel = 0xFF;
+ _voice[voiceNr]->_sustained = false;
+ _voice[voiceNr]->stop();
+ _voice[voiceNr]->_assign = 0xFF;
+
+ CMSVoice *sec = _voice[voiceNr]->_secondaryVoice;
+ if (sec) {
+ sec->stop();
+ sec->_assign = 0xFF;
+ _voice[voiceNr]->_secondaryVoice = 0;
+ }
+
--voices;
} while (voices != 0);
}
+
+ for (int i = _numVoicesPrimary; i < _numVoicesPrimary + secondary; ++i) {
+ if (_voice[i]->_assign != 0xFF)
+ continue;
+
+ _voice[i]->_assign = channelNr;
+ if (_voice[i]->_note != 0xFF)
+ _voice[i]->stop();
+
+ for (int ii = 0; ii < _numVoicesPrimary; ++ii) {
+ if (_voice[ii]->_assign != channelNr)
+ continue;
+ _voice[ii]->_secondaryVoice = _voice[i];
+ // This will release the secondary binding immediately if the current patch does not require such an extra channel.
+ _voice[ii]->programChange(_channel[channelNr].program);
+ break;
+ }
+
+ if (_voice[i]->_assign == channelNr && _voice[i]->_note != 0xFF)
+ _voice[i]->stop();
+ break;
+ }
}
-void MidiDriver_CMS::donateVoices() {
+void MidiDriver_CMS::donateVoices(bool bindSecondary) {
int freeVoices = 0;
- for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
- if (_voice[i].channel == 0xFF)
+ for (int i = 0; i < _numVoicesPrimary; ++i) {
+ if (_voice[i]->_assign == 0xFF)
++freeVoices;
}
if (!freeVoices)
return;
- for (uint i = 0; i < ARRAYSIZE(_channel); ++i) {
+ for (int i = 0; i < ARRAYSIZE(_channel); ++i) {
Channel &channel = _channel[i];
- if (!channel.extraVoices) {
+ if (!channel.missingVoices) {
continue;
- } else if (channel.extraVoices < freeVoices) {
- freeVoices -= channel.extraVoices;
- channel.extraVoices = 0;
- bindVoices(i, channel.extraVoices);
+ } else if (channel.missingVoices < freeVoices) {
+ freeVoices -= channel.missingVoices;
+ int missing = channel.missingVoices;
+ channel.missingVoices = 0;
+ bindVoices(i, missing, bindSecondary, true);
} else {
- channel.extraVoices -= freeVoices;
- bindVoices(i, freeVoices);
+ channel.missingVoices -= freeVoices;
+ bindVoices(i, freeVoices, bindSecondary, true);
return;
}
}
}
-int MidiDriver_CMS::findVoice(int channelNr) {
+int MidiDriver_CMS::findVoice(int channelNr, int note) {
Channel &channel = _channel[channelNr];
int voiceNr = channel.lastVoiceUsed;
-
int newVoice = 0;
+ int newVoiceAltSCI0 = (_version > SCI_VERSION_0_LATE) ? -2 : -1;
uint16 newVoiceTime = 0;
bool loopDone = false;
do {
++voiceNr;
- if (voiceNr == 12)
+ if (voiceNr == _numVoicesPrimary)
voiceNr = 0;
- Voice &voice = _voice[voiceNr];
-
if (voiceNr == channel.lastVoiceUsed)
loopDone = true;
- if (voice.channel == channelNr) {
- if (voice.note == 0xFF) {
- channel.lastVoiceUsed = voiceNr;
+ if (_voice[voiceNr]->_assign == channelNr) {
+ if (_voice[voiceNr]->_note == 0xFF) {
+ channel.lastVoiceUsed = (_version > SCI_VERSION_0_LATE) ? voiceNr : _numVoicesPrimary - 1;
return voiceNr;
}
- uint16 curTime = voice.turnOffTicks;
+ int cnt = 1;
+ for (int i = voiceNr + 1; i < _numVoicesPrimary; ++i) {
+ if (_voice[i]->_assign == channelNr)
+ ++cnt;
+ }
+
+ // The SCI0 driver will (before resorting to the "note age test") simply return the first
+ // assigned voice as long as there are no other (primary) voices assigned to the midi part.
+ if (cnt == 1 && newVoiceAltSCI0 == -1)
+ newVoiceAltSCI0 = voiceNr;
+
+ uint16 curTime = _voice[voiceNr]->_releaseDuration;
if (curTime)
curTime += 0x8000;
else
- curTime = voice.ticks;
+ curTime = _voice[voiceNr]->_duration;
if (curTime >= newVoiceTime) {
newVoice = voiceNr;
@@ -667,147 +1223,109 @@ int MidiDriver_CMS::findVoice(int channelNr) {
}
} while (!loopDone);
+ if (newVoiceAltSCI0 >= 0)
+ return newVoiceAltSCI0;
+
if (newVoiceTime > 0) {
voiceNr = newVoice;
- _voice[voiceNr].sustained = 0;
- voiceOff(voiceNr);
- channel.lastVoiceUsed = voiceNr;
- return voiceNr;
- } else {
- return -1;
- }
-}
-
-void MidiDriver_CMS::updateVoiceAmplitude(int voiceNr) {
- Voice &voice = _voice[voiceNr];
+ channel.lastVoiceUsed = _numVoicesPrimary - 1;
- if (voice.amplitudeTimer != 0 && voice.amplitudeTimer != 254) {
- --voice.amplitudeTimer;
- return;
- } else if (voice.amplitudeTimer == 254) {
- if (!voice.turnOff)
- return;
-
- voice.amplitudeTimer = 0;
- }
-
- int nextDataIndex = voice.patchDataIndex;
- uint8 timerData = 0;
- uint8 amplitudeData = voice.patchDataPtr[nextDataIndex];
+ if (_version > SCI_VERSION_0_LATE) {
+ _voice[voiceNr]->stop();
+ channel.lastVoiceUsed = voiceNr;
+ }
- if (amplitudeData == 255) {
- timerData = amplitudeData = 0;
- voiceOff(voiceNr);
- } else {
- timerData = voice.patchDataPtr[nextDataIndex + 1];
- nextDataIndex += 2;
+ return voiceNr;
}
-
- voice.patchDataIndex = nextDataIndex;
- voice.amplitudeTimer = timerData;
- voice.amplitudeModifier = amplitudeData;
+
+ return -1;
}
-void MidiDriver_CMS::setupVoiceAmplitude(int voiceNr) {
- Voice &voice = _voice[voiceNr];
- uint amplitude = 0;
+int MidiDriver_CMS::findVoiceBasic(int channelNr) {
+ int voice = -1;
+ int oldestVoice = -1;
+ int oldestAge = -1;
- if (_channel[voice.channel].volume && voice.velocity
- && voice.amplitudeModifier && _masterVolume) {
- amplitude = _channel[voice.channel].volume * voice.velocity;
- amplitude /= 0x0F;
- amplitude *= voice.amplitudeModifier;
- amplitude /= 0x0F;
- amplitude *= _masterVolume;
- amplitude /= 0x0F;
+ // Try to find a voice assigned to this channel that is free (round-robin)
+ for (int i = 0; i < _numVoicesPrimary; i++) {
+ int v = (_channel[channelNr].lastVoiceUsed + i + 1) % _numVoicesPrimary;
- if (!amplitude)
- ++amplitude;
+ if (_voice[v]->_note == 0xFF) {
+ voice = v;
+ break;
+ }
+
+ // We also keep track of the oldest note in case the search fails
+ if (_voice[v]->_duration > oldestAge) {
+ oldestAge = _voice[v]->_duration;
+ oldestVoice = v;
+ }
}
- uint8 amplitudeData = 0;
- int pan = _channel[voice.channel].pan >> 2;
- if (pan >= 16) {
- amplitudeData = (amplitude * (31 - pan) / 0x0F) & 0x0F;
- amplitudeData |= (amplitude << 4);
- } else {
- amplitudeData = (amplitude * pan / 0x0F) & 0x0F;
- amplitudeData <<= 4;
- amplitudeData |= amplitude;
+ if (voice == -1) {
+ if (oldestVoice >= 0) {
+ _voice[oldestVoice]->stop();
+ voice = oldestVoice;
+ } else {
+ return -1;
+ }
}
- if (!_playSwitch)
- amplitudeData = 0;
+ _voice[voice]->_assign = channelNr;
+ _channel[channelNr].lastVoiceUsed = (_version > SCI_VERSION_0_LATE) ? voice : 0;
+ return voice;
+}
- if (voiceNr >= 6)
- writeToChip2(voiceNr - 6, amplitudeData);
- else
- writeToChip1(voiceNr, amplitudeData);
+void MidiDriver_CMS::writeToChip(int chip, int address, int data) {
+ assert(chip == 0 || chip == 1);
+ _cms->portWrite(0x221 + (chip << 1), address);
+ _cms->portWrite(0x220 + (chip << 1), data);
}
void MidiDriver_CMS::generateSamples(int16 *buffer, int len) {
- while (len) {
- if (!_samplesTillCallback) {
- for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
- if (_voice[i].note == 0xFF)
- continue;
-
- ++_voice[i].ticks;
- if (_voice[i].turnOff)
- ++_voice[i].turnOffTicks;
-
- updateVoiceAmplitude(i);
- setupVoiceAmplitude(i);
- }
-
- _samplesTillCallback = _samplesPerCallback;
- _samplesTillCallbackRemainder += _samplesPerCallbackRemainder;
- if (_samplesTillCallbackRemainder >= _timerFreq) {
- _samplesTillCallback++;
- _samplesTillCallbackRemainder -= _timerFreq;
- }
- }
-
- int32 render = MIN<int32>(len, _samplesTillCallback);
- len -= render;
- _samplesTillCallback -= render;
- _cms->readBuffer(buffer, render);
- buffer += render * 2;
- }
+ _cms->readBuffer(buffer, len);
}
-
class MidiPlayer_CMS : public MidiPlayer {
public:
- MidiPlayer_CMS(SciVersion version) : MidiPlayer(version) {
- }
-
- int open(ResourceManager *resMan) {
- if (_driver)
- return MidiDriver::MERR_ALREADY_OPEN;
+ MidiPlayer_CMS(SciVersion version) : MidiPlayer(version) {}
- _driver = new MidiDriver_CMS(g_system->getMixer(), resMan);
- int driverRetVal = _driver->open();
- if (driverRetVal != 0)
- return driverRetVal;
-
- return 0;
- }
+ int open(ResourceManager *resMan);
+ void close();
- void close() {
- _driver->setTimerCallback(0, 0);
- _driver->close();
- delete _driver;
- _driver = nullptr;
- }
+ void initTrack(SciSpan<const byte>& header);
bool hasRhythmChannel() const { return false; }
- byte getPlayId() const { return 9; }
+ byte getPlayId() const { return _version > SCI_VERSION_0_LATE ? 9 : 4; }
int getPolyphony() const { return 12; }
- void playSwitch(bool play) { static_cast<MidiDriver_CMS *>(_driver)->playSwitch(play); }
+ void playSwitch(bool play) { _driver->property(MidiDriver_CMS::MIDI_PROP_PLAYSWITCH, play ? 1 : 0); }
};
+int MidiPlayer_CMS::open(ResourceManager *resMan) {
+ if (_driver)
+ return MidiDriver::MERR_ALREADY_OPEN;
+
+ _driver = new MidiDriver_CMS(g_system->getMixer(), resMan, _version);
+ int driverRetVal = _driver->open();
+ if (driverRetVal != 0)
+ return driverRetVal;
+
+ return 0;
+}
+
+void MidiPlayer_CMS::close() {
+ _driver->setTimerCallback(0, 0);
+ _driver->close();
+ delete _driver;
+ _driver = nullptr;
+}
+
+void MidiPlayer_CMS::initTrack(SciSpan<const byte>& header) {
+ if (_driver)
+ static_cast<MidiDriver_CMS*>(_driver)->initTrack(header);
+}
+
MidiPlayer *MidiPlayer_CMS_create(SciVersion version) {
return new MidiPlayer_CMS(version);
}
diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp
index 5977cf0177..dc62acb395 100644
--- a/engines/sci/sound/music.cpp
+++ b/engines/sci/sound/music.cpp
@@ -84,8 +84,9 @@ void SciMusic::init() {
if (g_sci->_features->useAltWinGMSound())
deviceFlags |= MDT_PREFER_GM;
- // Currently our CMS implementation only supports SCI1(.1)
- if (getSciVersion() >= SCI_VERSION_1_EGA_ONLY && getSciVersion() <= SCI_VERSION_1_1)
+ // SCI_VERSION_0_EARLY games apparently don't support the CMS. At least there
+ // is no patch resource 101 and I also haven't seen any CMS driver file so far.
+ if (getSciVersion() > SCI_VERSION_0_EARLY && getSciVersion() <= SCI_VERSION_1_1)
deviceFlags |= MDT_CMS;
if (g_sci->getPlatform() == Common::kPlatformFMTowns) {