aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/cine/script_fw.cpp24
-rw-r--r--engines/kyra/sound/drivers/audiomaster2.cpp10
-rw-r--r--engines/sci/engine/kscripts.cpp4
-rw-r--r--engines/sci/engine/script_patches.cpp60
-rw-r--r--engines/sci/sound/drivers/cms.cpp1462
-rw-r--r--engines/sci/sound/drivers/fb01.cpp140
-rw-r--r--engines/sci/sound/drivers/pc9801.cpp1
-rw-r--r--engines/sci/sound/music.cpp5
8 files changed, 1251 insertions, 455 deletions
diff --git a/engines/cine/script_fw.cpp b/engines/cine/script_fw.cpp
index 86eb709d5a..9692c54b7b 100644
--- a/engines/cine/script_fw.cpp
+++ b/engines/cine/script_fw.cpp
@@ -707,7 +707,29 @@ int FWScript::execute() {
if (_script._size) {
while (!ret) {
- _line = _pos;
+
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ if (_pos == 0x0f9a) {
+ if (!scumm_stricmp(currentPrcName, "AUTO00.PRC"))
+ g_cine->_globalVars[200] = g_cine->_objectTable[235].costume;
+ }
+ } else {
+ if (_pos == 0x2c) {
+ if (g_cine->_objectTable[1].x == 0x0131) {
+ if (!scumm_stricmp(currentPrcName, "TOTO.PRC"))
+ g_cine->_objectTable[2].x = 0x02;
+ }
+ } /*else if (!scumm_stricmp(currentPrcName, "CODE2.PRC")) {
+ if (_pos == 1) {
+ //globalVars[0] = objectTable[scriptElement->scriptPtr[6]].frame;
+ } else if (_pos == 504) {
+ //_currentScriptElement->localVars[1] = _currentScriptElement->localVars[2] = 0;
+ //globalVars[251] = 0;
+ g_cine->_globalVars[251] = g_cine->_globalVars[251];
+ }
+ }*/
+ }
+
byte opcode = getNextByte();
OpFunc handler = _info->opcodeHandler(opcode);
diff --git a/engines/kyra/sound/drivers/audiomaster2.cpp b/engines/kyra/sound/drivers/audiomaster2.cpp
index 261c813b0f..a4f7a3e661 100644
--- a/engines/kyra/sound/drivers/audiomaster2.cpp
+++ b/engines/kyra/sound/drivers/audiomaster2.cpp
@@ -892,6 +892,8 @@ void AudioMaster2ResourceManager::initResource(SoundResource *resource) {
if (!resource)
return;
+ Common::StackLock lock(_mutex);
+
SoundResource *res = retrieveFromChain(resource->getName());
// The driver does not replace resources with the same name, but disposes the new resource instead.
// So these names seem to be considered "globally unique".
@@ -1284,7 +1286,7 @@ void AudioMaster2Internal::fadeOut(int delay) {
}
bool AudioMaster2Internal::isFading() {
- return _io->isFading();
+ return _ready ? _io->isFading() : false;
}
void AudioMaster2Internal::setMusicVolume(int volume) {
@@ -1313,18 +1315,18 @@ void AudioMaster2Internal::resetCounter() {
}
int AudioMaster2Internal::getPlayDuration() {
- return _durationCounter;
+ return _ready ? _durationCounter : 0;
}
void AudioMaster2Internal::sync(SoundResource *res) {
if (!_ready || !res)
return;
+ Common::StackLock lock(_mutex);
+
if (res->getType() != 1)
return;
- Common::StackLock lock(_mutex);
-
SoundResourceSMUS *smus = static_cast<SoundResourceSMUS*>(res);
_io->_tempo = smus->getTempo();
smus->setSync(_io->_sync);
diff --git a/engines/sci/engine/kscripts.cpp b/engines/sci/engine/kscripts.cpp
index 61b0f16cae..5bdbb4fb46 100644
--- a/engines/sci/engine/kscripts.cpp
+++ b/engines/sci/engine/kscripts.cpp
@@ -257,6 +257,10 @@ reg_t kDisposeClone(EngineState *s, int argc, reg_t *argv) {
// Returns script dispatch address index in the supplied script
reg_t kScriptID(EngineState *s, int argc, reg_t *argv) {
int script = argv[0].toUint16();
+
+ if (Sci::g_sci->getGameId() == GID_KQ4 && script == 701)
+ script--;
+
uint16 index = (argc > 1) ? argv[1].toUint16() : 0;
if (argv[0].getSegment())
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 0b328d5d97..8a92a64d27 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -5379,6 +5379,24 @@ static const uint16 laurabow1PatchLeftStairsLockupFix[] = {
PATCH_END
};
+// Copy Protection
+static const uint16 laurabow1SignatureCp[] = {
+ SIG_MAGICDWORD,
+ 0x30, 0x48, 0x00, 0x8f, 0x01, 0x8d, 0x00, 0x35, 0x04, 0x06,
+ 0x93, 0x05, 0x1e, 0x30, 0x36, 0x00, 0x8f, 0x02, 0x8d, 0x00,
+ SIG_END
+};
+
+static const uint16 laurabow1PatchCp[] = {
+ 0x39, 0x00, 0xab, 0x00, 0x39, 0x00, 0xab, 0x01, 0x39, 0x00,
+ 0xab, 0x35, 0x39, 0x27, 0xaf, 0x01, 0x39, 0x27, 0xaf, 0x02,
+ PATCH_GETORIGINALBYTE(0), PATCH_GETORIGINALBYTE(1), PATCH_GETORIGINALBYTE(2),
+ PATCH_GETORIGINALBYTE(3), PATCH_GETORIGINALBYTE(4), PATCH_GETORIGINALBYTE(5),
+ PATCH_GETORIGINALBYTE(6), PATCH_GETORIGINALBYTE(7), PATCH_GETORIGINALBYTE(8),
+ PATCH_GETORIGINALBYTE(9), PATCH_GETORIGINALBYTE(10),
+ PATCH_END
+};
+
// script, description, signature patch
static const SciScriptPatcherEntry laurabow1Signatures[] = {
{ true, 4, "easter egg view fix", 1, laurabow1SignatureEasterEggViewFix, laurabow1PatchEasterEggViewFix },
@@ -5391,6 +5409,7 @@ static const SciScriptPatcherEntry laurabow1Signatures[] = {
{ true, 58, "chapel candles persistence", 1, laurabow1SignatureChapelCandlesPersistence, laurabow1PatchChapelCandlesPersistence },
{ true, 236, "tell Lilly about Gertie blocking fix 1/2", 1, laurabow1SignatureTellLillyAboutGerieBlockingFix1, laurabow1PatchTellLillyAboutGertieBlockingFix1 },
{ true, 236, "tell Lilly about Gertie blocking fix 2/2", 1, laurabow1SignatureTellLillyAboutGerieBlockingFix2, laurabow1PatchTellLillyAboutGertieBlockingFix2 },
+ { true, 414, "copy protection", 1, laurabow1SignatureCp, laurabow1PatchCp },
{ true, 998, "obstacle collision lockups fix", 1, laurabow1SignatureObstacleCollisionLockupsFix, laurabow1PatchObstacleCollisionLockupsFix },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -14644,6 +14663,44 @@ static const SciScriptPatcherEntry torinSignatures[] = {
#endif
+
+// Copy Protection
+static const uint16 pq2EnSignatureCp[] = {
+ SIG_MAGICDWORD,
+ 0x35, 0x07, 0x12, 0xa5,
+ SIG_ADDTOOFFSET(67),
+ 0x30, 0xcf, 0x00, 0x35,
+ SIG_END
+};
+
+static const uint16 pq2EnPatchCp[] = {
+ 0x35, 0x00, 0x12, 0xa5,
+ PATCH_ADDTOOFFSET(67),
+ 0x30, 0x00, 0x00, 0x35,
+ PATCH_END
+};
+
+static const uint16 pq2JpSignatureCp[] = {
+ SIG_MAGICDWORD,
+ 0x35, 0x07, 0x12, 0xa5,
+ SIG_ADDTOOFFSET(75),
+ 0x30, 0xcf, 0x00, 0x35,
+ SIG_END
+};
+
+static const uint16 pq2JpPatchCp[] = {
+ 0x35, 0x00, 0x12, 0xa5,
+ PATCH_ADDTOOFFSET(75),
+ 0x30, 0x00, 0x00, 0x35,
+ PATCH_END
+};
+
+static const SciScriptPatcherEntry pq2Signatures[] = {
+ { true, 701, "copy protection", 1, pq2EnSignatureCp, pq2EnPatchCp },
+ { true, 701, "copy protection", 1, pq2JpSignatureCp, pq2JpPatchCp },
+ SCI_SIGNATUREENTRY_TERMINATOR
+};
+
// =================================================================================
ScriptPatcher::ScriptPatcher() {
@@ -15178,6 +15235,9 @@ void ScriptPatcher::processScript(uint16 scriptNr, SciSpan<byte> scriptData) {
case GID_PQ1:
signatureTable = pq1vgaSignatures;
break;
+ case GID_PQ2:
+ signatureTable = pq2Signatures;
+ break;
case GID_PQ3:
signatureTable = pq3Signatures;
break;
diff --git a/engines/sci/sound/drivers/cms.cpp b/engines/sci/sound/drivers/cms.cpp
index 8b92432cb9..908b0ef596 100644
--- a/engines/sci/sound/drivers/cms.cpp
+++ b/engines/sci/sound/drivers/cms.cpp
@@ -33,14 +33,157 @@
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 recalculateEvelopeLevels();
+ 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;
+
+ static uint8 _envAR1;
+
+ 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 _envelopeDataTable[256];
+ 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 +191,102 @@ 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;
+
+ bool _playSwitch;
+ uint16 _masterVolume;
+
+ const int _actualTimerInterval;
+ const int _reqTimerInterval;
+ int _updateTimer;
+ int _rate;
- Voice _voice[12];
+ SciVersion _version;
+};
- void voiceOn(int voice, int note, int velocity);
- void voiceOff(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 noteSend(int voice);
+CMSVoice::~CMSVoice() {
- 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 voiceMapping(int channel, int value);
- void bindVoices(int channel, int voices);
- void unbindVoices(int channel, int voices);
- void donateVoices();
- int findVoice(int channel);
+void CMSVoice::sendFrequency() {
+ uint8 frequency = 0;
+ uint8 octave = 0;
- int findVoiceBasic(int channel);
+ recalculateFrequency(frequency, octave);
- void updateVoiceAmplitude(int voice);
- void setupVoiceAmplitude(int voice);
+ uint8 octaveData = _octaveRegs[_id >> 1];
+ octaveData = (_id & 1) ? (octaveData & 0x0F) | (octave << 4) : (octaveData & 0xF0) | octave;
- uint8 _octaveRegs[2][3];
+ cmsWrite(8 + _regOffset, frequency);
+ cmsWrite(0x10 + (_regOffset >> 1), octaveData);
+}
- static const int _timerFreq = 60;
+void CMSVoice::cmsWrite(uint8 reg, uint8 val) {
+ _cms->portWrite(0x221 + _portOffset, reg);
+ _cms->portWrite(0x220 + _portOffset, val);
- static const int _frequencyTable[];
- static const int _velocityTable[];
+ if (reg >= 16 && reg <= 18)
+ _octaveRegs[_id >> 1] = val;
+}
+
+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 +301,495 @@ 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) {
+}
+
+CMSVoice_V0::~CMSVoice_V0() {
+}
+
+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;
+
+ //debug("NOTEON: Voice: %02d, Note: %02d, AR: 0x%02x, TL: 0x%02x, DR: 0x%02x, SLI: 0x%02x, RR: 0x%02x, VBR: 0x%02x", _id, note, _envAR, _envTL, _envDR, _envSLI, _envRR, _vbrMod);
+
+ if (_secondaryVoice)
+ _secondaryVoice->noteOn(note, 127);
+}
+
+void CMSVoice_V0::noteOff() {
+ if (!_driver->property(MidiDriver_CMS::MIDI_PROP_PLAYSWITCH, 0xFFFF) || !_envTL)
+ return;
+
+ //debug("NOTEOFF: Voice: %02d", _id);
+
+ _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++);
+
+ debug("CMSVoice_V0::programChange: Voice: %02d, Envelope: %02d, TransOct: %02d, TransFreq: %02d", _id, data[_isSecondary ? 3 : 0], _transOct, _transFreq);
+
+ 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;
+ }
+
+ recalculateEvelopeLevels();
+
+ switch (_envState) {
+ case kReady:
+ _envNote = 0xFF;
+ return;
+
+ case kRestart:
+ if (_envPAC) {
+ --_envPAC;
+ break;
+ } else {
+ //if ((_currentLevel >> 1) > _envAR)
+ _currentLevel = ((_currentLevel >> 1) > (int8)_envAR) ? ((_currentLevel >> 1) - _envAR1) & 0xFF : (_envAR - _envAR1) & 0xFF;
+ //_currentLevel = (uint8)MIN<int8>(0, (_currentLevel >> 1) - _envAR);
+ _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 pw = (_driver->property(MidiDriver_CMS::MIDI_PROP_CHANNEL_PITCHWHEEL, _assign) & 0x7FFF) - 0x2000;
+ int16 sg = (pw < 0) ? -1 : 0;
+ pw = (pw ^ sg) - sg;
+ pw = ((pw >> 7) & 1) + (pw >> 8);
+ assert(pw < ARRAYSIZE(_pitchWheelTable));
+ pw = (_pitchWheelTable[pw] ^ sg) - sg;
+
+ int frequency = note * 4 + pw;
+
+ 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::recalculateEvelopeLevels() {
+ 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];
+}
+
+uint8 CMSVoice_V0::_envAR1 = 0;
+
+void CMSVoice_V0::selectEnvelope(int id) {
+ const uint8 *in = &_envelopeDataTable[(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;
+ if (_id == 1)
+ _envAR1 = _envAR;
+}
+
+const uint8 CMSVoice_V0::_envelopeDataTable[256] = {
+ 0xff, 0xff, 0x02, 0x05, 0x08, 0x00, 0x01, 0x02,
+ 0xff, 0xff, 0x40, 0x02, 0x01, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0x08, 0x02, 0x02, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0x10, 0x02, 0x02, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0x18, 0x02, 0x01, 0x00, 0x00, 0x00,
+ 0x20, 0xff, 0x01, 0x06, 0x08, 0x00, 0x00, 0x00,
+ 0x20, 0xff, 0x08, 0x02, 0x03, 0x00, 0x00, 0x00,
+ 0x20, 0xe0, 0x02, 0x05, 0x02, 0x00, 0x00, 0x00,
+ 0x10, 0xe0, 0x10, 0x03, 0x02, 0x00, 0x00, 0x00,
+ 0x20, 0xe0, 0x08, 0x03, 0x03, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0x04, 0x06, 0x08, 0x00, 0x01, 0x02,
+ 0xff, 0xff, 0x04, 0x02, 0x04, 0x00, 0x01, 0x02,
+ 0xff, 0xff, 0x08, 0x02, 0x04, 0x00, 0x01, 0x02,
+ 0xff, 0xff, 0x10, 0x02, 0x04, 0x00, 0x01, 0x02,
+ 0xa0, 0xff, 0x0c, 0x03, 0x08, 0x00, 0x03, 0x01,
+ 0x20, 0xff, 0x01, 0x06, 0x0c, 0x00, 0x01, 0x02,
+ 0x20, 0xff, 0x08, 0x02, 0x04, 0x00, 0x01, 0x02,
+ 0x20, 0xd0, 0x02, 0x05, 0x02, 0x00, 0x01, 0x02,
+ 0x10, 0xff, 0x20, 0x05, 0x04, 0x00, 0x02, 0x02,
+ 0x08, 0xc0, 0x10, 0x04, 0x04, 0x00, 0x02, 0x02,
+ 0xc8, 0xff, 0x02, 0x05, 0x10, 0x00, 0x01, 0x02,
+ 0xff, 0xff, 0x04, 0x04, 0x10, 0x00, 0x01, 0x02,
+ 0xff, 0xff, 0x08, 0x03, 0x08, 0x00, 0x01, 0x02,
+ 0xff, 0xff, 0x0c, 0x02, 0x08, 0x00, 0x01, 0x02,
+ 0x19, 0xc4, 0x04, 0x04, 0x08, 0x00, 0x02, 0x02,
+ 0x10, 0xff, 0x01, 0x06, 0x08, 0x00, 0x04, 0x02,
+ 0x0a, 0xff, 0x01, 0x06, 0x08, 0x00, 0x05, 0x01,
+ 0x10, 0xff, 0x0a, 0x01, 0x02, 0x00, 0x05, 0x02,
+ 0xff, 0xff, 0x0a, 0x02, 0x08, 0x00, 0x05, 0x01,
+ 0xff, 0xff, 0x03, 0x03, 0x08, 0x00, 0x02, 0x01,
+ 0xff, 0xff, 0x08, 0x01, 0x04, 0x00, 0x0c, 0x02,
+ 0xff, 0xff, 0x10, 0x02, 0x04, 0x00, 0x0f, 0x0a
+};
+
+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) {
+}
+
+CMSVoice_V1::~CMSVoice_V1() {
+}
+
+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(1000000000 /(_baseFreq*1000)), _reqTimerInterval(1000000000/60000), _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 +798,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 +857,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 +875,7 @@ void MidiDriver_CMS::send(uint32 b) {
break;
case 0xC0:
- _channel[channel].patch = op1;
+ programChange(channel, op1);
break;
case 0xE0:
@@ -261,215 +893,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 (voice == -1) {
- if (oldestVoice >= 0) {
- voiceOff(oldestVoice);
- voice = oldestVoice;
- } else {
- return -1;
- }
+ if (id != -1) {
+ if (_version > SCI_VERSION_0_LATE)
+ _voice[id]->programChange(_channel[channelNr].program);
+ _voice[id]->noteOn(note, velocity);
}
-
- _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 +1034,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 +1050,105 @@ 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;
+ //debug("===== MidiDriver_CMS: hardware channel %02d is assigned to device channel %02d (primary voice) =======", i, channelNr);
+ _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;
+
+ _voice[ii]->_assign = channelNr;
+ _voice[i]->_secondaryVoice = _voice[ii];
+
+ //debug("===== MidiDriver_CMS: hardware channel %02d is assigned to device channel %02d (secondary voice) =====", ii, channelNr);
+ break;
+ }
- if (voice.note != 0xFF)
- voiceOff(i);
+ // 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 +1159,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 +1175,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 +1289,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];
-
- if (voice.amplitudeTimer != 0 && voice.amplitudeTimer != 254) {
- --voice.amplitudeTimer;
- return;
- } else if (voice.amplitudeTimer == 254) {
- if (!voice.turnOff)
- return;
-
- voice.amplitudeTimer = 0;
- }
+ channel.lastVoiceUsed = _numVoicesPrimary - 1;
- 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/drivers/fb01.cpp b/engines/sci/sound/drivers/fb01.cpp
index 3f3f581ee2..4713b2346b 100644
--- a/engines/sci/sound/drivers/fb01.cpp
+++ b/engines/sci/sound/drivers/fb01.cpp
@@ -55,6 +55,7 @@ public:
int open(ResourceManager *resMan);
void close();
+ void initTrack(SciSpan<const byte>& header);
void send(uint32 b);
void sysEx(const byte *msg, uint16 length);
bool hasRhythmChannel() const { return false; }
@@ -74,6 +75,7 @@ private:
void setSystemParam(byte sysChan, byte param, byte value);
void sendVoiceData(byte instrument, const SciSpan<const byte> &data);
void sendBanks(const SciSpan<const byte> &data);
+ void sendDisplayString(int bank);
void storeVoiceData(byte instrument, byte bank, byte index);
void initVoices();
@@ -102,6 +104,7 @@ private:
struct Voice {
int8 channel; // MIDI channel that this voice is assigned to or -1
+ uint8 poly; // Number of hardware voices
int8 note; // Currently playing MIDI note or -1
int bank; // Current bank setting or -1
int patch; // Currently playing patch or -1
@@ -109,11 +112,13 @@ private:
bool isSustained; // Flag indicating a note that is being sustained by the hold pedal
uint16 age; // Age of the current note
- Voice() : channel(-1), note(-1), bank(-1), patch(-1), velocity(0), isSustained(false), age(0) { }
+ Voice() : channel(-1), note(-1), bank(-1), patch(-1), velocity(0), isSustained(false), age(0), poly(0) { }
};
bool _playSwitch;
int _masterVolume;
+ int _numParts;
+ bool _isOpen;
Channel _channels[16];
Voice _voices[kVoices];
@@ -126,7 +131,8 @@ private:
byte _sysExBuf[kMaxSysExSize];
};
-MidiPlayer_Fb01::MidiPlayer_Fb01(SciVersion version) : MidiPlayer(version), _playSwitch(true), _masterVolume(15), _timerParam(NULL), _timerProc(NULL) {
+MidiPlayer_Fb01::MidiPlayer_Fb01(SciVersion version) : MidiPlayer(version), _playSwitch(true), _masterVolume(15), _timerParam(NULL), _timerProc(NULL),
+ _numParts(version == SCI_VERSION_0_LATE ? 0 : kVoices), _isOpen(false) {
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI);
_driver = MidiDriver::createMidi(dev);
@@ -264,16 +270,22 @@ int MidiPlayer_Fb01::findVoice(int channel) {
}
void MidiPlayer_Fb01::sendToChannel(byte channel, byte command, byte op1, byte op2) {
- for (int i = 0; i < kVoices; i++) {
+ for (int i = 0; i < _numParts; i++) {
// Send command to all voices assigned to this channel
if (_voices[i].channel == channel)
- _driver->send(command | i, op1, op2);
+ _driver->send(command | (_version == SCI_VERSION_0_LATE ? channel : i), op1, op2);
}
}
void MidiPlayer_Fb01::setPatch(int channel, int patch) {
int bank = 0;
+ if (_version == SCI_VERSION_0_LATE && channel == 15) {
+ // The original driver has some parsing related handling for program 127.
+ // We can't handle that here.
+ return;
+ }
+
_channels[channel].patch = patch;
if (patch >= 48) {
@@ -281,13 +293,13 @@ void MidiPlayer_Fb01::setPatch(int channel, int patch) {
bank = 1;
}
- for (int voice = 0; voice < kVoices; voice++) {
+ for (int voice = 0; voice < _numParts; voice++) {
if (_voices[voice].channel == channel) {
if (_voices[voice].bank != bank) {
_voices[voice].bank = bank;
setVoiceParam(voice, 4, bank);
}
- _driver->send(0xc0 | voice, patch, 0);
+ _driver->send(0xc0 | (_version == SCI_VERSION_0_LATE ? channel : voice), patch, 0);
}
}
}
@@ -342,6 +354,22 @@ void MidiPlayer_Fb01::noteOn(int channel, int note, int velocity) {
}
void MidiPlayer_Fb01::controlChange(int channel, int control, int value) {
+ if (_version == SCI_VERSION_0_LATE) {
+ // The SCI specific 0x4B control is the only one that gets filtered here.
+ // We also ignore the control channel 15.
+ if (control == 0x4B) {
+ for (int i = 0; i < _numParts; ++i) {
+ if (_voices[i].channel == channel && _voices[i].poly != value) {
+ _voices[i].poly = value;
+ setVoiceParam(i, 0, value);
+ }
+ }
+ } else if (channel != 15) {
+ sendToChannel(channel, 0xb0, control, value);
+ }
+ return;
+ }
+
switch (control) {
case 0x07: {
_channels[channel].volume = value;
@@ -385,6 +413,16 @@ void MidiPlayer_Fb01::send(uint32 b) {
byte op1 = (b >> 8) & 0x7f;
byte op2 = (b >> 16) & 0x7f;
+ if (_version == SCI_VERSION_0_LATE && command != 0xB0 && command != 0xC0) {
+ // Since the voice mapping takes place inside the hardware, most messages
+ // are simply passed through. Channel 15 is never assigned to a part but is
+ // used for certain parsing related events which we cannot handle here.
+ // Just making sure that no nonsense is sent to the device...
+ if (channel != 15)
+ sendToChannel(channel, command, op1, op2);
+ return;
+ }
+
switch (command) {
case 0x80:
noteOff(channel, op1);
@@ -447,23 +485,40 @@ void MidiPlayer_Fb01::sendBanks(const SciSpan<const byte> &data) {
if (data.size() < 3072)
error("Failed to read FB-01 patch");
+ int first = (_version == SCI_VERSION_0_LATE) ? 1 : 0;
+ sendDisplayString(0);
+
// SSCI sends bank dumps containing 48 instruments at once. We cannot do that
// due to the limited maximum SysEx length. Instead we send the instruments
// one by one and store them in the banks.
- for (int i = 0; i < 48; i++) {
+ for (int i = first; i < 48; i++) {
sendVoiceData(0, data.subspan(i * 64));
storeVoiceData(0, 0, i);
}
// Send second bank if available
if (data.size() >= 6146 && data.getUint16BEAt(3072) == 0xabcd) {
- for (int i = 0; i < 48; i++) {
+ sendDisplayString(1);
+ for (int i = first; i < 48; i++) {
sendVoiceData(0, data.subspan(3074 + i * 64));
storeVoiceData(0, 1, i);
}
}
}
+void MidiPlayer_Fb01::sendDisplayString(int bank) {
+ if (_version != SCI_VERSION_0_LATE)
+ return;
+
+ Common::String sierraStr = Common::String::format("SIERRA %d", bank + 1);
+ byte buf[64];
+ memset(buf, 0, 64);
+ Common::strlcpy((char*)buf, sierraStr.c_str(), sierraStr.size());
+ SciSpan<const byte> displayStr(buf, 64);
+
+ sendVoiceData(0, displayStr);
+}
+
int MidiPlayer_Fb01::open(ResourceManager *resMan) {
assert(resMan != NULL);
@@ -474,7 +529,8 @@ int MidiPlayer_Fb01::open(ResourceManager *resMan) {
}
// Set system channel to 0. We send this command over all 16 system channels
- for (int i = 0; i < 16; i++)
+ int n = (_version == SCI_VERSION_0_LATE) ? 1 : 16;
+ for (int i = 0; i < n; i++)
setSystemParam(i, 0x20, 0);
// Turn off memory protection
@@ -525,6 +581,8 @@ int MidiPlayer_Fb01::open(ResourceManager *resMan) {
// Set master volume
setSystemParam(0, 0x24, 0x7f);
+ _isOpen = true;
+
return 0;
}
@@ -532,6 +590,65 @@ void MidiPlayer_Fb01::close() {
_driver->close();
}
+void MidiPlayer_Fb01::initTrack(SciSpan<const byte>& header) {
+ // I haven't seen any SCI0_EARLY variant of this driver. Skip this for now...
+ if (!_isOpen || _version != SCI_VERSION_0_LATE)
+ return;
+
+ uint8 readPos = 0;
+ uint8 caps = header.getInt8At(readPos++);
+ if (caps != 0 && caps != 2)
+ return;
+
+ for (int i = 0; i < 8; ++i)
+ _voices[i] = Voice();
+
+ _numParts = 0;
+ for (int i = 0; i < 16; ++i) {
+ uint8 num = header.getInt8At(readPos++) & 0x7F;
+ uint8 flags = header.getInt8At(readPos++);
+
+ if (flags & 0x02) {
+ _voices[_numParts].channel = i;
+ _voices[_numParts].poly = num;
+ _numParts++;
+ }
+ }
+
+ for (int i = 0; i < _numParts; ++i)
+ setVoiceParam(i, 1, _voices[i].channel);
+
+ // From here this is more or less a copy of initVoices() with some modifications which are relevant for the
+ // correct assignment of hardware channels. TODO: Maybe merge this somehow. I'd have to see a SCI1 driver for
+ // that, though. Right now, this is only about fixing SCI_0_LATE...
+ int i = 2;
+ _sysExBuf[i++] = 0x70;
+
+ for (int j = 0; j < 16; j++) {
+ _sysExBuf[i++] = 0x70 | j;
+ _sysExBuf[i++] = 0x00;
+ _sysExBuf[i++] = 0x00;
+ }
+
+ for (int j = 0; j < _numParts; ++j) {
+ _sysExBuf[i] = _sysExBuf[i + 3] = _sysExBuf[i + 6] = _sysExBuf[i + 9] = _sysExBuf[i + 12] = _voices[j].channel | 0x70;
+ _sysExBuf[i + 1] = 0;
+ _sysExBuf[i + 2] = _voices[j].poly;
+ _sysExBuf[i + 4] = 2;
+ _sysExBuf[i + 5] = 127;
+ _sysExBuf[i + 7] = 3;
+ _sysExBuf[i + 8] = 0;
+ _sysExBuf[i + 10] = 4;
+ _sysExBuf[i + 11] = 0;
+ _sysExBuf[i + 13] = 5;
+ _sysExBuf[i + 14] = 10;
+ i += 15;
+ }
+
+ sysEx(_sysExBuf, i);
+ setSystemParam(0, 0x24, (_masterVolume << 3) + 7);
+}
+
void MidiPlayer_Fb01::setVoiceParam(byte voice, byte param, byte value) {
_sysExBuf[2] = 0x00;
_sysExBuf[3] = 0x18 | voice;
@@ -582,6 +699,11 @@ void MidiPlayer_Fb01::storeVoiceData(byte instrument, byte bank, byte index) {
}
void MidiPlayer_Fb01::initVoices() {
+ // There is no need for this in SCI0, since the voice assignment is done in initTrack().
+ // Maybe this can be merged (would require that I actually look at the SCI1 version of the driver).
+ if (_version == SCI_VERSION_0_LATE)
+ return;
+
int i = 2;
_sysExBuf[i++] = 0x70;
diff --git a/engines/sci/sound/drivers/pc9801.cpp b/engines/sci/sound/drivers/pc9801.cpp
index fe7d5bf7ba..824c4c9428 100644
--- a/engines/sci/sound/drivers/pc9801.cpp
+++ b/engines/sci/sound/drivers/pc9801.cpp
@@ -1343,6 +1343,7 @@ int MidiDriver_PC9801::open() {
return MERR_CANNOT_CONNECT;
_pc98a->setSoundEffectChanMask(0);
_pc98a->ssgSetVolume(205);
+ _pc98a->writeReg(0, 0x26, 256 - _baseTempo / 288);
_ready = true;
}
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) {