[Scummvm-git-logs] scummvm master -> f62f0c2db25d6a12b2b2d4723f0405e2531de2b3
athrxx
athrxx at scummvm.org
Wed Jul 17 20:40:28 CEST 2019
This automated email contains information about 7 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
97ea9fd704 KYRA: (EOB/Amiga) - t
96dc86ca97 SCI CP
ea9e7df0a8 CINE CP
d988e40e25 SCI: (PC98 audio driver) - base tempo correction
77dbefb907 SCI: (CMS sound driver) - add support for SCI0
59f1e7aa48 SCI: (FB01 sound driver) - SCI0_LATE fix
f62f0c2db2 Merge branch 'master' of https://github.com/scummvm/scummvm into test
Commit: 97ea9fd7042fceb9a692937d167a54f92750e470
https://github.com/scummvm/scummvm/commit/97ea9fd7042fceb9a692937d167a54f92750e470
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-17T17:38:24+02:00
Commit Message:
KYRA: (EOB/Amiga) - t
Changed paths:
engines/kyra/sound/drivers/audiomaster2.cpp
diff --git a/engines/kyra/sound/drivers/audiomaster2.cpp b/engines/kyra/sound/drivers/audiomaster2.cpp
index 261c813..a4f7a3e 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);
Commit: 96dc86ca9784f3c4031ed76fcf7972bd2c680be3
https://github.com/scummvm/scummvm/commit/96dc86ca9784f3c4031ed76fcf7972bd2c680be3
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-17T17:38:25+02:00
Commit Message:
SCI CP
Conflicts:
engines/sci/engine/script_patches.cpp
Changed paths:
engines/sci/engine/kscripts.cpp
engines/sci/engine/script_patches.cpp
diff --git a/engines/sci/engine/kscripts.cpp b/engines/sci/engine/kscripts.cpp
index 61b0f16..5bdbb4f 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 0b328d5..8a92a64 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;
Commit: ea9e7df0a8664a49e2ae572864ee70eccbfb06c9
https://github.com/scummvm/scummvm/commit/ea9e7df0a8664a49e2ae572864ee70eccbfb06c9
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-17T17:38:25+02:00
Commit Message:
CINE CP
Changed paths:
engines/cine/script_fw.cpp
diff --git a/engines/cine/script_fw.cpp b/engines/cine/script_fw.cpp
index 86eb709..9692c54 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);
Commit: d988e40e259871d8f9a6e36016b99d68a6167fcc
https://github.com/scummvm/scummvm/commit/d988e40e259871d8f9a6e36016b99d68a6167fcc
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-17T17:38:26+02:00
Commit Message:
SCI: (PC98 audio driver) - base tempo correction
(somewhat difficult to make out any difference, but it does affect the envelope processing)
Changed paths:
engines/sci/sound/drivers/pc9801.cpp
diff --git a/engines/sci/sound/drivers/pc9801.cpp b/engines/sci/sound/drivers/pc9801.cpp
index fe7d5bf..824c4c9 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;
}
Commit: 77dbefb907125481ffc83f29da4627564612ebf1
https://github.com/scummvm/scummvm/commit/77dbefb907125481ffc83f29da4627564612ebf1
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-17T17:38:26+02:00
Commit Message:
SCI: (CMS sound driver) - add support for SCI0
I haven't found an elegant and non-intrusive way to squeeze SCI0 support into LordHoto's existing code. The drivers are too different. So I made some rearrangements. The basic mechanisms of LordHoto's SCI1 code should remain the same as before, though. I only introduced some more classes, moved some code into these classes and renamed some things (mainly for myself, so as not to get confused).
I fixed two voice mapping bugs in the existing driver code. The first bug in bindVocies() effectively hindered the driver from playing anything at all when the CMS_DISABLE_VOICE_MAPPING #define wasn't set (_voice[i].channel == 0xFF instead of _voice[i].channel != 0xFF). The second bug in unbindVoices() was not a complete show stopper, but the function simply did not "unbind the voice". The line which does the actual removal of the channel assignment was missing.
The SCI0 driver portions have been tested with: PQ2, KQ4, LSL3, QFG1, ICE and COC.
SCI_0_EARLY versions apparently don't support the CMS. At least I haven't seen a driver file so far. And there seems to be no no instrument patch resource. Although the latter issue needn't necessarily be one, since the patch data array in the driver is actually preset with data (which gets overwritten as soon as a patch file is loaded). Maybe this would work for SCI_0_EARLY. However, I haven't tested this, since I really would have have a look at a driver file first if one actually exists. For now, I have limited the driver to SCI_0_LATE.
SCI1 has been tested with KQ5 and LSL5 (not extensively, just to see whether anything got broken and whether my voice mapping fixes work).
Changed paths:
engines/sci/sound/drivers/cms.cpp
engines/sci/sound/music.cpp
diff --git a/engines/sci/sound/drivers/cms.cpp b/engines/sci/sound/drivers/cms.cpp
index 8b92432..908b0ef 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/music.cpp b/engines/sci/sound/music.cpp
index 5977cf0..dc62acb 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) {
Commit: 59f1e7aa48960d0c6f1a75067a7975ce01bc947a
https://github.com/scummvm/scummvm/commit/59f1e7aa48960d0c6f1a75067a7975ce01bc947a
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-17T17:38:26+02:00
Commit Message:
SCI: (FB01 sound driver) - SCI0_LATE fix
Fix up SCI0_LATE variant of the driver so that it actually plays anything (and even correctly). SCI1 hasn't seen any testing from me so far. I don't know whether that version works. Same for SCI0_EARLY...
Changed paths:
engines/sci/sound/drivers/fb01.cpp
diff --git a/engines/sci/sound/drivers/fb01.cpp b/engines/sci/sound/drivers/fb01.cpp
index 3f3f581..4713b23 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;
Commit: f62f0c2db25d6a12b2b2d4723f0405e2531de2b3
https://github.com/scummvm/scummvm/commit/f62f0c2db25d6a12b2b2d4723f0405e2531de2b3
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-17T20:40:12+02:00
Commit Message:
Merge branch 'master' of https://github.com/scummvm/scummvm into test
Changed paths:
engines/bladerunner/actor.cpp
engines/bladerunner/actor.h
engines/bladerunner/actor_dialogue_queue.cpp
engines/bladerunner/actor_dialogue_queue.h
engines/bladerunner/ambient_sounds.cpp
engines/bladerunner/ambient_sounds.h
engines/bladerunner/audio_mixer.cpp
engines/bladerunner/audio_mixer.h
engines/bladerunner/audio_player.cpp
engines/bladerunner/audio_player.h
engines/bladerunner/bladerunner.cpp
engines/bladerunner/bladerunner.h
engines/bladerunner/debugger.cpp
engines/bladerunner/item_pickup.cpp
engines/bladerunner/item_pickup.h
engines/bladerunner/movement_track.cpp
engines/bladerunner/movement_track.h
engines/bladerunner/music.cpp
engines/bladerunner/music.h
engines/bladerunner/savefile.cpp
engines/bladerunner/savefile.h
engines/bladerunner/script/police_maze.cpp
engines/bladerunner/script/script.cpp
engines/bladerunner/script/script.h
engines/bladerunner/script/vk_script.cpp
engines/bladerunner/settings.cpp
engines/bladerunner/time.cpp
engines/bladerunner/time.h
engines/bladerunner/ui/elevator.cpp
engines/bladerunner/ui/elevator.h
engines/bladerunner/ui/end_credits.cpp
engines/bladerunner/ui/esper.cpp
engines/bladerunner/ui/esper.h
engines/bladerunner/ui/kia.cpp
engines/bladerunner/ui/kia.h
engines/bladerunner/ui/kia_section_diagnostic.cpp
engines/bladerunner/ui/kia_section_diagnostic.h
engines/bladerunner/ui/kia_section_load.cpp
engines/bladerunner/ui/kia_section_pogo.cpp
engines/bladerunner/ui/kia_section_pogo.h
engines/bladerunner/ui/kia_section_save.cpp
engines/bladerunner/ui/spinner.cpp
engines/bladerunner/ui/spinner.h
engines/bladerunner/ui/ui_check_box.cpp
engines/bladerunner/ui/ui_check_box.h
engines/bladerunner/ui/ui_image_picker.cpp
engines/bladerunner/ui/ui_image_picker.h
engines/bladerunner/ui/ui_input_box.h
engines/bladerunner/ui/ui_scroll_box.cpp
engines/bladerunner/ui/ui_scroll_box.h
engines/bladerunner/ui/vk.cpp
engines/bladerunner/ui/vk.h
More information about the Scummvm-git-logs
mailing list