815 lines
18 KiB
C++
815 lines
18 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 {
|
|
|
|
// 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 : public MidiDriver_Emulated {
|
|
public:
|
|
MidiDriver_CMS(Audio::Mixer *mixer, ResourceManager *resMan)
|
|
: MidiDriver_Emulated(mixer), _resMan(resMan), _cms(0), _rate(0), _playSwitch(true), _masterVolume(0) {
|
|
}
|
|
|
|
int open();
|
|
void close();
|
|
|
|
void send(uint32 b);
|
|
uint32 property(int prop, uint32 param);
|
|
|
|
MidiChannel *allocateChannel() { return 0; }
|
|
MidiChannel *getPercussionChannel() { return 0; }
|
|
|
|
bool isStereo() const { return true; }
|
|
int getRate() const { return _rate; }
|
|
|
|
void playSwitch(bool play);
|
|
private:
|
|
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;
|
|
uint8 volume;
|
|
uint8 pan;
|
|
uint8 hold;
|
|
uint8 extraVoices;
|
|
uint16 pitchWheel;
|
|
uint8 pitchModifier;
|
|
bool pitchAdditive;
|
|
uint8 lastVoiceUsed;
|
|
};
|
|
|
|
Channel _channel[16];
|
|
|
|
struct Voice {
|
|
Voice() : channel(0xFF), note(0xFF), sustained(0xFF), ticks(0),
|
|
turnOffTicks(0), patchDataPtr(), patchDataIndex(0),
|
|
amplitudeTimer(0), amplitudeModifier(0), turnOff(false),
|
|
velocity(0) {
|
|
}
|
|
|
|
uint8 channel;
|
|
uint8 note;
|
|
uint8 sustained;
|
|
uint16 ticks;
|
|
uint16 turnOffTicks;
|
|
SciSpan<uint8> patchDataPtr;
|
|
uint8 patchDataIndex;
|
|
uint8 amplitudeTimer;
|
|
uint8 amplitudeModifier;
|
|
bool turnOff;
|
|
uint8 velocity;
|
|
};
|
|
|
|
Voice _voice[12];
|
|
|
|
void voiceOn(int voice, int note, int velocity);
|
|
void voiceOff(int voice);
|
|
|
|
void noteSend(int voice);
|
|
|
|
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);
|
|
|
|
int findVoiceBasic(int channel);
|
|
|
|
void updateVoiceAmplitude(int voice);
|
|
void setupVoiceAmplitude(int voice);
|
|
|
|
uint8 _octaveRegs[2][3];
|
|
|
|
static const int _timerFreq = 60;
|
|
|
|
static const int _frequencyTable[];
|
|
static const int _velocityTable[];
|
|
};
|
|
|
|
const int MidiDriver_CMS::_frequencyTable[] = {
|
|
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
|
|
};
|
|
|
|
const int MidiDriver_CMS::_velocityTable[] = {
|
|
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
|
|
};
|
|
|
|
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(*res);
|
|
|
|
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);
|
|
_playSwitch = true;
|
|
_masterVolume = 0;
|
|
|
|
for (int i = 0; i < 31; ++i) {
|
|
writeToChip1(i, 0);
|
|
writeToChip2(i, 0);
|
|
}
|
|
|
|
writeToChip1(0x14, 0xFF);
|
|
writeToChip2(0x14, 0xFF);
|
|
|
|
writeToChip1(0x1C, 1);
|
|
writeToChip2(0x1C, 1);
|
|
|
|
_samplesPerCallback = getRate() / _timerFreq;
|
|
_samplesPerCallbackRemainder = getRate() % _timerFreq;
|
|
_samplesTillCallback = 0;
|
|
_samplesTillCallbackRemainder = 0;
|
|
|
|
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;
|
|
|
|
switch (command) {
|
|
case 0x80:
|
|
noteOff(channel, op1);
|
|
break;
|
|
|
|
case 0x90:
|
|
noteOn(channel, op1, op2);
|
|
break;
|
|
|
|
case 0xB0:
|
|
controlChange(channel, op1, op2);
|
|
break;
|
|
|
|
case 0xC0:
|
|
_channel[channel].patch = 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;
|
|
|
|
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::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);
|
|
}
|
|
|
|
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;
|
|
|
|
setupVoiceAmplitude(voiceNr);
|
|
}
|
|
|
|
void MidiDriver_CMS::noteSend(int voiceNr) {
|
|
Voice &voice = _voice[voiceNr];
|
|
|
|
int frequency = (CLIP<int>(voice.note, 21, 116) - 21) * 4;
|
|
if (_channel[voice.channel].pitchModifier) {
|
|
int modifier = _channel[voice.channel].pitchModifier;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
int chipNumber = 0;
|
|
if (voiceNr >= 6) {
|
|
voiceNr -= 6;
|
|
chipNumber = 1;
|
|
}
|
|
|
|
int octave = 0;
|
|
while (frequency >= 48) {
|
|
frequency -= 48;
|
|
++octave;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (chipNumber == 1)
|
|
writeToChip2(0x10 + (voiceNr >> 1), octaveData);
|
|
else
|
|
writeToChip1(0x10 + (voiceNr >> 1), octaveData);
|
|
}
|
|
|
|
void MidiDriver_CMS::noteOn(int channel, int note, int velocity) {
|
|
if (note < 21 || note > 116)
|
|
return;
|
|
|
|
if (velocity == 0) {
|
|
noteOff(channel, 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);
|
|
return;
|
|
}
|
|
}
|
|
|
|
#ifdef CMS_DISABLE_VOICE_MAPPING
|
|
int voice = findVoiceBasic(channel);
|
|
#else
|
|
int voice = findVoice(channel);
|
|
#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;
|
|
}
|
|
}
|
|
|
|
_voice[voice].channel = channel;
|
|
_channel[channel].lastVoiceUsed = voice;
|
|
return voice;
|
|
}
|
|
|
|
void MidiDriver_CMS::noteOff(int channel, 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;
|
|
else
|
|
_voice[i].turnOff = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MidiDriver_CMS::controlChange(int channel, int control, int value) {
|
|
switch (control) {
|
|
case 7:
|
|
if (value) {
|
|
value >>= 3;
|
|
if (!value)
|
|
++value;
|
|
}
|
|
|
|
_channel[channel].volume = value;
|
|
break;
|
|
|
|
case 10:
|
|
_channel[channel].pan = value;
|
|
break;
|
|
|
|
case 64:
|
|
_channel[channel].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;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 75:
|
|
#ifndef CMS_DISABLE_VOICE_MAPPING
|
|
voiceMapping(channel, value);
|
|
#endif
|
|
break;
|
|
|
|
case 123:
|
|
for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
|
|
if (_voice[i].channel == channel && _voice[i].note != 0xFF)
|
|
voiceOff(i);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
void MidiDriver_CMS::pitchWheel(int channelNr, int value) {
|
|
Channel &channel = _channel[channelNr];
|
|
channel.pitchWheel = value;
|
|
channel.pitchAdditive = false;
|
|
channel.pitchModifier = 0;
|
|
|
|
if (value < 0x2000) {
|
|
channel.pitchModifier = (0x2000 - value) / 170;
|
|
} else if (value > 0x2000) {
|
|
channel.pitchModifier = (value - 0x2000) / 170;
|
|
channel.pitchAdditive = true;
|
|
}
|
|
|
|
for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
|
|
if (_voice[i].channel == channelNr && _voice[i].note != 0xFF)
|
|
noteSend(i);
|
|
}
|
|
}
|
|
|
|
void MidiDriver_CMS::voiceMapping(int channelNr, int value) {
|
|
int curVoices = 0;
|
|
|
|
for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
|
|
if (_voice[i].channel == channelNr)
|
|
++curVoices;
|
|
}
|
|
|
|
curVoices += _channel[channelNr].extraVoices;
|
|
|
|
if (curVoices == value) {
|
|
return;
|
|
} else if (curVoices < value) {
|
|
bindVoices(channelNr, value - curVoices);
|
|
} else {
|
|
unbindVoices(channelNr, curVoices - value);
|
|
donateVoices();
|
|
}
|
|
}
|
|
|
|
void MidiDriver_CMS::bindVoices(int channel, int voices) {
|
|
for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
|
|
if (_voice[i].channel == 0xFF)
|
|
continue;
|
|
|
|
Voice &voice = _voice[i];
|
|
voice.channel = channel;
|
|
|
|
if (voice.note != 0xFF)
|
|
voiceOff(i);
|
|
|
|
--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.
|
|
}
|
|
|
|
void MidiDriver_CMS::unbindVoices(int channelNr, int voices) {
|
|
Channel &channel = _channel[channelNr];
|
|
|
|
if (channel.extraVoices >= voices) {
|
|
channel.extraVoices -= voices;
|
|
} else {
|
|
voices -= channel.extraVoices;
|
|
channel.extraVoices = 0;
|
|
|
|
for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
|
|
if (_voice[i].channel == channelNr
|
|
&& _voice[i].note == 0xFF) {
|
|
--voices;
|
|
if (voices == 0)
|
|
return;
|
|
}
|
|
}
|
|
|
|
do {
|
|
uint16 voiceTime = 0;
|
|
uint voiceNr = 0;
|
|
|
|
for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
|
|
if (_voice[i].channel != channelNr)
|
|
continue;
|
|
|
|
uint16 curTime = _voice[i].turnOffTicks;
|
|
if (curTime)
|
|
curTime += 0x8000;
|
|
else
|
|
curTime = _voice[i].ticks;
|
|
|
|
if (curTime >= voiceTime) {
|
|
voiceNr = i;
|
|
voiceTime = curTime;
|
|
}
|
|
}
|
|
|
|
_voice[voiceNr].sustained = 0;
|
|
voiceOff(voiceNr);
|
|
_voice[voiceNr].channel = 0xFF;
|
|
--voices;
|
|
} while (voices != 0);
|
|
}
|
|
}
|
|
|
|
void MidiDriver_CMS::donateVoices() {
|
|
int freeVoices = 0;
|
|
|
|
for (uint i = 0; i < ARRAYSIZE(_voice); ++i) {
|
|
if (_voice[i].channel == 0xFF)
|
|
++freeVoices;
|
|
}
|
|
|
|
if (!freeVoices)
|
|
return;
|
|
|
|
for (uint i = 0; i < ARRAYSIZE(_channel); ++i) {
|
|
Channel &channel = _channel[i];
|
|
|
|
if (!channel.extraVoices) {
|
|
continue;
|
|
} else if (channel.extraVoices < freeVoices) {
|
|
freeVoices -= channel.extraVoices;
|
|
channel.extraVoices = 0;
|
|
bindVoices(i, channel.extraVoices);
|
|
} else {
|
|
channel.extraVoices -= freeVoices;
|
|
bindVoices(i, freeVoices);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
int MidiDriver_CMS::findVoice(int channelNr) {
|
|
Channel &channel = _channel[channelNr];
|
|
int voiceNr = channel.lastVoiceUsed;
|
|
|
|
int newVoice = 0;
|
|
uint16 newVoiceTime = 0;
|
|
|
|
bool loopDone = false;
|
|
do {
|
|
++voiceNr;
|
|
|
|
if (voiceNr == 12)
|
|
voiceNr = 0;
|
|
|
|
Voice &voice = _voice[voiceNr];
|
|
|
|
if (voiceNr == channel.lastVoiceUsed)
|
|
loopDone = true;
|
|
|
|
if (voice.channel == channelNr) {
|
|
if (voice.note == 0xFF) {
|
|
channel.lastVoiceUsed = voiceNr;
|
|
return voiceNr;
|
|
}
|
|
|
|
uint16 curTime = voice.turnOffTicks;
|
|
if (curTime)
|
|
curTime += 0x8000;
|
|
else
|
|
curTime = voice.ticks;
|
|
|
|
if (curTime >= newVoiceTime) {
|
|
newVoice = voiceNr;
|
|
newVoiceTime = curTime;
|
|
}
|
|
}
|
|
} while (!loopDone);
|
|
|
|
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;
|
|
}
|
|
|
|
int nextDataIndex = voice.patchDataIndex;
|
|
uint8 timerData = 0;
|
|
uint8 amplitudeData = voice.patchDataPtr[nextDataIndex];
|
|
|
|
if (amplitudeData == 255) {
|
|
timerData = amplitudeData = 0;
|
|
voiceOff(voiceNr);
|
|
} else {
|
|
timerData = voice.patchDataPtr[nextDataIndex + 1];
|
|
nextDataIndex += 2;
|
|
}
|
|
|
|
voice.patchDataIndex = nextDataIndex;
|
|
voice.amplitudeTimer = timerData;
|
|
voice.amplitudeModifier = amplitudeData;
|
|
}
|
|
|
|
void MidiDriver_CMS::setupVoiceAmplitude(int voiceNr) {
|
|
Voice &voice = _voice[voiceNr];
|
|
uint amplitude = 0;
|
|
|
|
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;
|
|
|
|
if (!amplitude)
|
|
++amplitude;
|
|
}
|
|
|
|
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 (!_playSwitch)
|
|
amplitudeData = 0;
|
|
|
|
if (voiceNr >= 6)
|
|
writeToChip2(voiceNr - 6, amplitudeData);
|
|
else
|
|
writeToChip1(voiceNr, amplitudeData);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
class MidiPlayer_CMS : public MidiPlayer {
|
|
public:
|
|
MidiPlayer_CMS(SciVersion version) : MidiPlayer(version) {
|
|
}
|
|
|
|
int open(ResourceManager *resMan) {
|
|
if (_driver)
|
|
return MidiDriver::MERR_ALREADY_OPEN;
|
|
|
|
_driver = new MidiDriver_CMS(g_system->getMixer(), resMan);
|
|
int driverRetVal = _driver->open();
|
|
if (driverRetVal != 0)
|
|
return driverRetVal;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void close() {
|
|
_driver->setTimerCallback(0, 0);
|
|
_driver->close();
|
|
delete _driver;
|
|
_driver = nullptr;
|
|
}
|
|
|
|
bool hasRhythmChannel() const { return false; }
|
|
byte getPlayId() const { return 9; }
|
|
int getPolyphony() const { return 12; }
|
|
|
|
void playSwitch(bool play) { static_cast<MidiDriver_CMS *>(_driver)->playSwitch(play); }
|
|
};
|
|
|
|
MidiPlayer *MidiPlayer_CMS_create(SciVersion version) {
|
|
return new MidiPlayer_CMS(version);
|
|
}
|
|
|
|
} // End of namespace SCI
|