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).
1392 lines
36 KiB
C++
1392 lines
36 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "sci/sound/drivers/mididriver.h"
|
|
|
|
#include "audio/softsynth/emumidi.h"
|
|
#include "audio/softsynth/cms.h"
|
|
#include "audio/mixer.h"
|
|
|
|
#include "common/system.h"
|
|
|
|
#include "sci/resource.h"
|
|
#include "sci/util.h"
|
|
|
|
namespace Sci {
|
|
|
|
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:
|
|
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();
|
|
|
|
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; }
|
|
|
|
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);
|
|
|
|
struct Channel {
|
|
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 missingVoices;
|
|
uint8 lastVoiceUsed;
|
|
uint16 pitchWheel;
|
|
bool isValid;
|
|
};
|
|
|
|
Channel _channel[16];
|
|
CMSVoice *_voice[12];
|
|
|
|
const int _numVoicesPrimary;
|
|
const int _numVoicesSecondary;
|
|
|
|
CMSEmulator *_cms;
|
|
ResourceManager *_resMan;
|
|
Common::SpanOwner<SciSpan<const uint8> > _patchData;
|
|
|
|
bool _playSwitch;
|
|
uint16 _masterVolume;
|
|
|
|
const int _actualTimerInterval;
|
|
const int _reqTimerInterval;
|
|
int _updateTimer;
|
|
int _rate;
|
|
|
|
SciVersion _version;
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
CMSVoice::~CMSVoice() {
|
|
|
|
}
|
|
|
|
void CMSVoice::sendFrequency() {
|
|
uint8 frequency = 0;
|
|
uint8 octave = 0;
|
|
|
|
recalculateFrequency(frequency, octave);
|
|
|
|
uint8 octaveData = _octaveRegs[_id >> 1];
|
|
octaveData = (_id & 1) ? (octaveData & 0x0F) | (octave << 4) : (octaveData & 0xF0) | octave;
|
|
|
|
cmsWrite(8 + _regOffset, frequency);
|
|
cmsWrite(0x10 + (_regOffset >> 1), octaveData);
|
|
}
|
|
|
|
void CMSVoice::cmsWrite(uint8 reg, uint8 val) {
|
|
_cms->portWrite(0x221 + _portOffset, reg);
|
|
_cms->portWrite(0x220 + _portOffset, val);
|
|
|
|
if (reg >= 16 && reg <= 18)
|
|
_octaveRegs[_id >> 1] = val;
|
|
}
|
|
|
|
uint8 CMSVoice::_octaveRegs[6] = {
|
|
0, 0, 0, 0, 0, 0
|
|
};
|
|
|
|
const int CMSVoice::_frequencyTable[48] = {
|
|
3, 10, 17, 24,
|
|
31, 38, 46, 51,
|
|
58, 64, 71, 77,
|
|
83, 89, 95, 101,
|
|
107, 113, 119, 124,
|
|
130, 135, 141, 146,
|
|
151, 156, 162, 167,
|
|
172, 177, 182, 186,
|
|
191, 196, 200, 205,
|
|
209, 213, 217, 222,
|
|
226, 230, 234, 238,
|
|
242, 246, 250, 253
|
|
};
|
|
|
|
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;
|
|
_envState = kRelease;
|
|
if (_secondaryVoice)
|
|
_secondaryVoice->stop();
|
|
}
|
|
|
|
void CMSVoice_V0::programChange(int program) {
|
|
assert(program < 128);
|
|
if (program == 127) {
|
|
// This seems to replace the start of track offset with the current position so that 0xFC (kEndOfTrack)
|
|
// midi events would not reset the track to the start, but to the current position instead. This cannot
|
|
// be handled here. All versions of the SCI0 driver that I have seen so far do this. Still, I somehow
|
|
// doubt that it will ever come up, but let's see...
|
|
warning("CMSVoice_V0::programChange(): Unhandled program change 127");
|
|
return;
|
|
}
|
|
|
|
SciSpan<const uint8> data = _patchData.subspan(128 + (_patchData.getUint8At(program) << 3));
|
|
uint8 pos = _isSecondary ? 3 : 0;
|
|
|
|
selectEnvelope(data.getUint8At(pos++));
|
|
|
|
if (_isSecondary) {
|
|
_envSSL = data.getUint8At(pos++);
|
|
// This decides whether the secondary voice has the same or the opposite pan position as the primary voice.
|
|
_panMask = _strMask ^ -(_envSSL & 1);
|
|
}
|
|
|
|
_transOct = data.getInt8At(pos++);
|
|
_transFreq = data.getInt8At(pos++);
|
|
|
|
if (_isSecondary)
|
|
_envPA = data.getUint8At(pos++);
|
|
|
|
if (_secondaryVoice) {
|
|
assert(!_isSecondary);
|
|
if (data.getUint8At(pos) == 0xFF) {
|
|
_secondaryVoice->stop();
|
|
_secondaryVoice->_assign = 0xFF;
|
|
_secondaryVoice = 0;
|
|
} else {
|
|
_secondaryVoice->setPanMask(_panMask);
|
|
_secondaryVoice->programChange(program);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CMSVoice_V0::update() {
|
|
if (_updateCMS) {
|
|
sendFrequency();
|
|
cmsWrite(_regOffset, ((_currentLevel & 0xF0) | (_currentLevel >> 4)) & _panMask);
|
|
_updateCMS = false;
|
|
}
|
|
|
|
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;
|
|
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/*18206*/), _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;
|
|
|
|
assert(_resMan);
|
|
Resource *res = _resMan->findResource(ResourceId(kResourceTypePatch, 101), false);
|
|
if (!res)
|
|
return -1;
|
|
|
|
_patchData->allocateFromSpan(_version < SCI_VERSION_1_EARLY ? res->subspan(30) : *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) {
|
|
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) {
|
|
writeToChip(0, i, 0);
|
|
writeToChip(1, i, 0);
|
|
}
|
|
|
|
// 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);
|
|
|
|
// Sync and reset generators
|
|
writeToChip(0, 0x1C, 2);
|
|
writeToChip(1, 0x1C, 2);
|
|
|
|
// Enable all channels
|
|
writeToChip(0, 0x1C, 1);
|
|
writeToChip(1, 0x1C, 1);
|
|
|
|
int retVal = MidiDriver_Emulated::open();
|
|
if (retVal != 0)
|
|
return retVal;
|
|
|
|
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO);
|
|
return 0;
|
|
}
|
|
|
|
void MidiDriver_CMS::close() {
|
|
_mixer->stopHandle(_mixerSoundHandle);
|
|
|
|
_patchData.clear();
|
|
delete _cms;
|
|
_cms = nullptr;
|
|
}
|
|
|
|
void MidiDriver_CMS::send(uint32 b) {
|
|
const uint8 command = b & 0xf0;
|
|
const uint8 channel = b & 0xf;
|
|
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);
|
|
break;
|
|
|
|
case 0x90:
|
|
noteOn(channel, op1, op2);
|
|
break;
|
|
|
|
case 0xB0:
|
|
controlChange(channel, op1, op2);
|
|
break;
|
|
|
|
case 0xC0:
|
|
programChange(channel, op1);
|
|
break;
|
|
|
|
case 0xE0:
|
|
pitchWheel(channel, (op1 & 0x7f) | ((op2 & 0x7f) << 7));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint32 MidiDriver_CMS::property(int prop, uint32 param) {
|
|
switch (prop) {
|
|
case MIDI_PROP_MASTER_VOLUME:
|
|
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::initTrack(SciSpan<const byte>& header) {
|
|
if (!_isOpen || _version > SCI_VERSION_0_LATE)
|
|
return;
|
|
|
|
uint8 readPos = 0;
|
|
uint8 caps = header.getInt8At(readPos++);
|
|
int numChan = (caps == 2) ? 15 : 16;
|
|
if (caps != 0 && caps != 2)
|
|
return;
|
|
|
|
for (int i = 0; i < 12; ++i)
|
|
_voice[i]->reset();
|
|
|
|
for (int i = 0; i < 16; ++i) {
|
|
_channel[i].isValid = false;
|
|
_channel[i].volume = 180;
|
|
_channel[i].pitchWheel = 0x2000;
|
|
_channel[i].pan = 0;
|
|
|
|
if (i == numChan)
|
|
continue;
|
|
|
|
uint8 num = header.getInt8At(readPos++) & 0x0F;
|
|
uint8 flags = header.getInt8At(readPos++);
|
|
|
|
if (num == 15 || !(flags & 4))
|
|
continue;
|
|
|
|
// 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;
|
|
|
|
// 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). The secondary
|
|
// voice is controlled through the primary voice. It will not receive its own separate commands.
|
|
// The main purpose seems to be to have 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);
|
|
}
|
|
}
|
|
|
|
void MidiDriver_CMS::onTimer() {
|
|
_updateTimer -= _actualTimerInterval;
|
|
if (_updateTimer > 0)
|
|
return;
|
|
|
|
_updateTimer += _reqTimerInterval;
|
|
|
|
for (uint i = 0; i < ARRAYSIZE(_voice); ++i)
|
|
_voice[i]->update();
|
|
}
|
|
|
|
void MidiDriver_CMS::noteOn(int channelNr, int note, int velocity) {
|
|
if (note < 21 || note > 116)
|
|
return;
|
|
|
|
if (velocity == 0) {
|
|
noteOff(channelNr, note);
|
|
return;
|
|
}
|
|
|
|
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 id = findVoiceBasic(channelNr);
|
|
#else
|
|
int id = findVoice(channelNr, note);
|
|
#endif
|
|
if (id != -1) {
|
|
if (_version > SCI_VERSION_0_LATE)
|
|
_voice[id]->programChange(_channel[channelNr].program);
|
|
_voice[id]->noteOn(note, velocity);
|
|
}
|
|
}
|
|
|
|
void MidiDriver_CMS::noteOff(int channelNr, int note) {
|
|
for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
|
|
if (_voice[i]->_assign == channelNr && _voice[i]->_note == note) {
|
|
if (_channel[channelNr].hold != 0)
|
|
_voice[i]->_sustained = true;
|
|
else
|
|
_voice[i]->noteOff();
|
|
}
|
|
}
|
|
}
|
|
|
|
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:
|
|
_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[channelNr].pan = value;
|
|
break;
|
|
|
|
case 64:
|
|
_channel[channelNr].hold = value;
|
|
|
|
if (!value) {
|
|
for (int i = 0; i < _numVoicesPrimary; ++i) {
|
|
if (_voice[i]->_assign == channelNr && _voice[i]->_sustained) {
|
|
_voice[i]->_sustained = false;
|
|
_voice[i]->noteOff();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 75:
|
|
#ifndef CMS_DISABLE_VOICE_MAPPING
|
|
voiceMapping(channelNr, value);
|
|
#endif
|
|
break;
|
|
|
|
case 123:
|
|
for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
|
|
if (_voice[i]->_assign == channelNr && _voice[i]->_note != 0xFF)
|
|
_voice[i]->stop();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
void MidiDriver_CMS::programChange(int channelNr, int value) {
|
|
_channel[channelNr].program = value;
|
|
if (_version > SCI_VERSION_0_LATE)
|
|
return;
|
|
|
|
for (int i = 0; i < _numVoicesPrimary; ++i) {
|
|
if (_voice[i]->_assign == channelNr)
|
|
_voice[i]->programChange(value);
|
|
}
|
|
}
|
|
|
|
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 (int i = 0; i < _numVoicesPrimary; ++i) {
|
|
if (_voice[i]->_assign == channelNr)
|
|
++curVoices;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
//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;
|
|
}
|
|
|
|
// 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[channelNr].missingVoices += voices;
|
|
}
|
|
|
|
void MidiDriver_CMS::unbindVoices(int channelNr, int voices, bool bindSecondary) {
|
|
int secondary = bindSecondary ? _numVoicesSecondary : 0;
|
|
Channel &channel = _channel[channelNr];
|
|
|
|
if (channel.missingVoices >= voices) {
|
|
channel.missingVoices -= voices;
|
|
} else {
|
|
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;
|
|
}
|
|
|
|
--voices;
|
|
if (voices == 0)
|
|
return;
|
|
}
|
|
}
|
|
|
|
do {
|
|
uint16 voiceTime = 0;
|
|
uint voiceNr = 0;
|
|
|
|
for (int i = 0; i < _numVoicesPrimary; ++i) {
|
|
if (_voice[i]->_assign != channelNr)
|
|
continue;
|
|
|
|
uint16 curTime = _voice[i]->_releaseDuration;
|
|
if (curTime)
|
|
curTime += 0x8000;
|
|
else
|
|
curTime = _voice[i]->_duration;
|
|
|
|
if (curTime >= voiceTime) {
|
|
voiceNr = i;
|
|
voiceTime = curTime;
|
|
}
|
|
}
|
|
|
|
_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(bool bindSecondary) {
|
|
int freeVoices = 0;
|
|
|
|
for (int i = 0; i < _numVoicesPrimary; ++i) {
|
|
if (_voice[i]->_assign == 0xFF)
|
|
++freeVoices;
|
|
}
|
|
|
|
if (!freeVoices)
|
|
return;
|
|
|
|
for (int i = 0; i < ARRAYSIZE(_channel); ++i) {
|
|
Channel &channel = _channel[i];
|
|
|
|
if (!channel.missingVoices) {
|
|
continue;
|
|
} else if (channel.missingVoices < freeVoices) {
|
|
freeVoices -= channel.missingVoices;
|
|
int missing = channel.missingVoices;
|
|
channel.missingVoices = 0;
|
|
bindVoices(i, missing, bindSecondary, true);
|
|
} else {
|
|
channel.missingVoices -= freeVoices;
|
|
bindVoices(i, freeVoices, bindSecondary, true);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 == _numVoicesPrimary)
|
|
voiceNr = 0;
|
|
|
|
if (voiceNr == channel.lastVoiceUsed)
|
|
loopDone = true;
|
|
|
|
if (_voice[voiceNr]->_assign == channelNr) {
|
|
if (_voice[voiceNr]->_note == 0xFF) {
|
|
channel.lastVoiceUsed = (_version > SCI_VERSION_0_LATE) ? voiceNr : _numVoicesPrimary - 1;
|
|
return voiceNr;
|
|
}
|
|
|
|
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[voiceNr]->_duration;
|
|
|
|
if (curTime >= newVoiceTime) {
|
|
newVoice = voiceNr;
|
|
newVoiceTime = curTime;
|
|
}
|
|
}
|
|
} while (!loopDone);
|
|
|
|
if (newVoiceAltSCI0 >= 0)
|
|
return newVoiceAltSCI0;
|
|
|
|
if (newVoiceTime > 0) {
|
|
voiceNr = newVoice;
|
|
channel.lastVoiceUsed = _numVoicesPrimary - 1;
|
|
|
|
if (_version > SCI_VERSION_0_LATE) {
|
|
_voice[voiceNr]->stop();
|
|
channel.lastVoiceUsed = voiceNr;
|
|
}
|
|
|
|
return voiceNr;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int MidiDriver_CMS::findVoiceBasic(int channelNr) {
|
|
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 < _numVoicesPrimary; i++) {
|
|
int v = (_channel[channelNr].lastVoiceUsed + i + 1) % _numVoicesPrimary;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (voice == -1) {
|
|
if (oldestVoice >= 0) {
|
|
_voice[oldestVoice]->stop();
|
|
voice = oldestVoice;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
_voice[voice]->_assign = channelNr;
|
|
_channel[channelNr].lastVoiceUsed = (_version > SCI_VERSION_0_LATE) ? voice : 0;
|
|
return voice;
|
|
}
|
|
|
|
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) {
|
|
_cms->readBuffer(buffer, len);
|
|
}
|
|
|
|
class MidiPlayer_CMS : public MidiPlayer {
|
|
public:
|
|
MidiPlayer_CMS(SciVersion version) : MidiPlayer(version) {}
|
|
|
|
int open(ResourceManager *resMan);
|
|
void close();
|
|
|
|
void MidiPlayer_CMS::initTrack(SciSpan<const byte>& trackData);
|
|
|
|
bool hasRhythmChannel() const { return false; }
|
|
byte getPlayId() const { return _version > SCI_VERSION_0_LATE ? 9 : 4; }
|
|
int getPolyphony() const { return 12; }
|
|
|
|
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>& trackData) {
|
|
if (_driver)
|
|
static_cast<MidiDriver_CMS*>(_driver)->initTrack(trackData);
|
|
}
|
|
|
|
MidiPlayer *MidiPlayer_CMS_create(SciVersion version) {
|
|
return new MidiPlayer_CMS(version);
|
|
}
|
|
|
|
} // End of namespace SCI
|