1394 lines
36 KiB
C++
1394 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;
|
|
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;
|
|
|
|
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 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);
|
|
}
|
|
}
|
|
|
|
void MidiDriver_CMS::onTimer() {
|
|
for (_updateTimer -= _actualTimerInterval; _updateTimer <= 0; _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
|