1021 lines
26 KiB
C++
1021 lines
26 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "sci/sound/drivers/mididriver.h"
|
|
#include "sci/resource/resource.h"
|
|
|
|
#include "audio/audiostream.h"
|
|
#include "audio/mixer.h"
|
|
|
|
#include "common/debug.h"
|
|
#include "common/system.h"
|
|
|
|
namespace Sci {
|
|
|
|
class SoundHWChannel {
|
|
public:
|
|
SoundHWChannel(const Common::SharedPtr<const uint16> &freqTable, const Common::SharedPtr<const uint16> &smpVolTable, const byte &masterVol, int outputRate);
|
|
virtual ~SoundHWChannel() {}
|
|
|
|
virtual void reset();
|
|
virtual void updateChannelVolume() = 0;
|
|
virtual void processEnvelope() = 0;
|
|
void recalcSample();
|
|
|
|
void setPart(byte number) { _partMapping = number; }
|
|
|
|
virtual void noteOn(byte note, byte velocity) = 0;
|
|
virtual void noteOff(byte sustain) = 0;
|
|
virtual void chanOff();
|
|
virtual void sustainOff() {}
|
|
void pitchBend(uint16 pb);
|
|
void setVolume(byte volume) { _ctrlVolume = volume; }
|
|
|
|
int16 currentSample() const { return _curSample; }
|
|
bool isMappedToPart(byte part) const { return _partMapping == part; }
|
|
bool hasNote(byte note) const { return _note == note; }
|
|
bool isPlaying() const { return _note != 0xFF; }
|
|
virtual bool isSustained() const { return false; }
|
|
virtual uint16 getDuration() const { return 0; }
|
|
|
|
protected:
|
|
byte _partMapping;
|
|
byte _ctrlVolume;
|
|
byte _velocity;
|
|
int8 _envVolume;
|
|
byte _note;
|
|
byte _pbDiv;
|
|
byte _envState;
|
|
byte _envCount;
|
|
int8 _envAttn;
|
|
uint16 _smpAmplitude;
|
|
|
|
const int _outputRate;
|
|
const byte &_masterVolume;
|
|
const Common::SharedPtr<const uint16> _freqTable;
|
|
const Common::SharedPtr<const uint16> _smpVolTable;
|
|
|
|
private:
|
|
int getFrequency() const;
|
|
|
|
int16 _pitchBend;
|
|
int16 _curSample;
|
|
int _freqCount;
|
|
};
|
|
|
|
class SoundChannel_PCSpeaker : public SoundHWChannel {
|
|
public:
|
|
SoundChannel_PCSpeaker(const Common::SharedPtr<const uint16> &freqTable, const Common::SharedPtr<const uint16> &smpVolTable, const byte &masterVol, int outputRate) : SoundHWChannel(freqTable, smpVolTable, masterVol, outputRate) {}
|
|
~SoundChannel_PCSpeaker() override {}
|
|
|
|
void reset() override;
|
|
void updateChannelVolume() override;
|
|
void processEnvelope() override {}
|
|
|
|
void noteOn(byte note, byte velocity) override;
|
|
void noteOff(byte) override;
|
|
};
|
|
|
|
class SoundChannel_PCJr_SCI0 : public SoundHWChannel {
|
|
public:
|
|
SoundChannel_PCJr_SCI0(const Common::SharedPtr<const uint16> &freqTable, const Common::SharedPtr<const uint16> &smpVolTable, const byte &masterVol, int outputRate) : SoundHWChannel(freqTable, smpVolTable, masterVol, outputRate), _envCount2(0) {}
|
|
~SoundChannel_PCJr_SCI0() override {}
|
|
|
|
void reset() override;
|
|
void updateChannelVolume() override;
|
|
void processEnvelope() override;
|
|
|
|
void noteOn(byte note, byte velocity) override;
|
|
void noteOff(byte) override;
|
|
|
|
private:
|
|
void envAT();
|
|
void envST();
|
|
void envRL();
|
|
|
|
byte _envCount2;
|
|
};
|
|
|
|
class SoundChannel_PCJr_SCI1 : public SoundHWChannel {
|
|
public:
|
|
SoundChannel_PCJr_SCI1(const Common::SharedPtr<const uint16> &freqTable, const Common::SharedPtr<const uint16> &smpVolTable, const byte &masterVol, int outputRate, const uint16 *&instrumentOffsets, const byte *&instrumentData, byte *&program) :
|
|
SoundHWChannel(freqTable, smpVolTable, masterVol, outputRate), _instrumentOffsets(instrumentOffsets), _instrumentData(instrumentData), _program(program), _duration(0), _releaseDuration(0), _sustain(0),
|
|
_release(0), _envData(nullptr) {}
|
|
~SoundChannel_PCJr_SCI1() override {}
|
|
|
|
void reset() override;
|
|
void updateChannelVolume() override;
|
|
void processEnvelope() override;
|
|
|
|
void noteOn(byte note, byte velocity) override;
|
|
void noteOff(byte sustain) override;
|
|
void chanOff() override;
|
|
void sustainOff() override;
|
|
|
|
bool isSustained() const override { return _sustain != 0; }
|
|
uint16 getDuration() const override { return _releaseDuration ? _releaseDuration + 0x8000 : _duration; }
|
|
|
|
private:
|
|
void processInstrument();
|
|
|
|
uint16 _duration;
|
|
uint16 _releaseDuration;
|
|
byte _sustain;
|
|
byte _release;
|
|
byte *&_program;
|
|
const byte *_envData;
|
|
const uint16 *&_instrumentOffsets;
|
|
const byte *&_instrumentData;
|
|
};
|
|
|
|
SoundHWChannel::SoundHWChannel(const Common::SharedPtr<const uint16> &freqTable, const Common::SharedPtr<const uint16> &smpVolTable, const byte &masterVol, int outputRate) : _freqTable(freqTable), _smpVolTable(smpVolTable), _masterVolume(masterVol), _outputRate(outputRate) {
|
|
reset();
|
|
}
|
|
|
|
void SoundHWChannel::reset() {
|
|
_partMapping = _note = 0xFF;
|
|
_ctrlVolume = _velocity = 0;
|
|
_envVolume = 0;
|
|
_envAttn = 0;
|
|
_envState = _envCount = 0;
|
|
_freqCount = 0;
|
|
_smpAmplitude = 0;
|
|
_curSample = 0;
|
|
_pitchBend = 0;
|
|
_pbDiv = 0;
|
|
}
|
|
|
|
void SoundHWChannel::recalcSample() {
|
|
int freq = getFrequency();
|
|
|
|
if (freq == 0) {
|
|
_curSample = 0;
|
|
return;
|
|
}
|
|
|
|
_freqCount += freq;
|
|
|
|
while (_freqCount >= (_outputRate << 1))
|
|
_freqCount -= (_outputRate << 1);
|
|
|
|
if (_freqCount - freq < 0) {
|
|
// Unclean rising edge
|
|
int l = _smpAmplitude << 1;
|
|
_curSample = (int16)~_smpAmplitude + (l * _freqCount) / freq;
|
|
} else if (_freqCount >= _outputRate && _freqCount - freq < _outputRate) {
|
|
// Unclean falling edge
|
|
int l = _smpAmplitude << 1;
|
|
_curSample = _smpAmplitude - (l * (_freqCount - _outputRate)) / freq;
|
|
} else {
|
|
if (_freqCount < _outputRate)
|
|
_curSample = _smpAmplitude;
|
|
else
|
|
_curSample = (int16)~_smpAmplitude;
|
|
}
|
|
}
|
|
|
|
void SoundHWChannel::chanOff() {
|
|
_note = 0xFF;
|
|
_envState = 0;
|
|
_envAttn = 0;
|
|
_envVolume = 0;
|
|
_envCount = 0;
|
|
}
|
|
|
|
void SoundHWChannel::pitchBend(uint16 pb) {
|
|
_pitchBend = ((pb < 0x2000) ? -(0x2000 - pb) : pb - 0x2000) / _pbDiv;
|
|
}
|
|
|
|
#define SCI_PCJR_BASE_NOTE 129 // A10
|
|
#define SCI_PCJR_BASE_OCTAVE 10 // A10, as I said
|
|
|
|
int SoundHWChannel::getFrequency() const {
|
|
if (_note == 0xFF)
|
|
return 0;
|
|
|
|
int halftoneDelta = (_note - SCI_PCJR_BASE_NOTE) * 4 + _pitchBend;
|
|
int octDiff = ((halftoneDelta + SCI_PCJR_BASE_OCTAVE * 48) / 48) - SCI_PCJR_BASE_OCTAVE;
|
|
int halftoneIndex = (halftoneDelta + (48 * 100)) % 48;
|
|
int freq = _freqTable.get()[halftoneIndex] / (1 << (-octDiff));
|
|
|
|
return freq;
|
|
}
|
|
|
|
#undef SCI_PCJR_BASE_NOTE
|
|
#undef SCI_PCJR_BASE_OCTAVE
|
|
|
|
void SoundChannel_PCSpeaker::reset() {
|
|
SoundHWChannel::reset();
|
|
_pbDiv = 171;
|
|
updateChannelVolume();
|
|
}
|
|
|
|
void SoundChannel_PCSpeaker::updateChannelVolume() {
|
|
// The PC speaker has a fixed volume level. The original will turn it off if the volume is zero
|
|
// or otherwise turn it on. We just use the PCJr volume table for the master volume, so that our
|
|
// volume controls still work.
|
|
_smpAmplitude = _smpVolTable.get()[15 - _masterVolume];
|
|
}
|
|
|
|
void SoundChannel_PCSpeaker::noteOn(byte note, byte velocity) {
|
|
_note = note;
|
|
}
|
|
|
|
void SoundChannel_PCSpeaker::noteOff(byte) {
|
|
chanOff();
|
|
}
|
|
|
|
void SoundChannel_PCJr_SCI0::reset() {
|
|
SoundHWChannel::reset();
|
|
_ctrlVolume = 96;
|
|
_pbDiv = 171;
|
|
updateChannelVolume();
|
|
}
|
|
|
|
void SoundChannel_PCJr_SCI0::updateChannelVolume() {
|
|
int veloAttn = 3 - (_velocity >> 5);
|
|
int volAttn = 15 - (_ctrlVolume >> 3);
|
|
int attn = volAttn + veloAttn + _envVolume;
|
|
if (attn >= 15) {
|
|
attn = 15;
|
|
if (_envState >= 2)
|
|
chanOff();
|
|
} else if (attn < 0) {
|
|
attn = 0;
|
|
}
|
|
attn = CLIP<int>(attn + (15 - _masterVolume), volAttn, 15);
|
|
_smpAmplitude = _smpVolTable.get()[attn];
|
|
}
|
|
|
|
void SoundChannel_PCJr_SCI0::processEnvelope() {
|
|
switch (_envState) {
|
|
case 1:
|
|
envAT();
|
|
break;
|
|
case 2:
|
|
envST();
|
|
break;
|
|
case 3:
|
|
envRL();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void SoundChannel_PCJr_SCI0::noteOn(byte note, byte velocity) {
|
|
_note = note;
|
|
_velocity = velocity;
|
|
_envState = 1;
|
|
_envCount = 0;
|
|
envAT();
|
|
}
|
|
|
|
void SoundChannel_PCJr_SCI0::noteOff(byte) {
|
|
_envState = 3;
|
|
_envCount = 0;
|
|
envRL();
|
|
}
|
|
|
|
void SoundChannel_PCJr_SCI0::envAT() {
|
|
static const int8 envTable[] = { 0, -2, -3, -2, -1, 0, 127 };
|
|
byte c = _envCount++;
|
|
if (envTable[c] == 127) {
|
|
_envState++;
|
|
_envCount = 0;
|
|
_envCount2 = 20;
|
|
envST();
|
|
} else {
|
|
// The original makes a signed check whether the table value is negative. If negative, it will put
|
|
// zero, if zero or positive it will put the table value. Since the table is all zero or negative,
|
|
// it will always be zero. This is weird, but just just be the remainder of a sloppy bug fix. If
|
|
// we put the table value instead of zero, e. g. the LSL3 opening fanfare will sound wrong.
|
|
_envVolume = _envAttn = 0;
|
|
updateChannelVolume();
|
|
}
|
|
}
|
|
|
|
void SoundChannel_PCJr_SCI0::envST() {
|
|
if (!_envCount2 || --_envCount2)
|
|
return;
|
|
_envVolume = ++_envAttn;
|
|
_envCount2 = 20;
|
|
updateChannelVolume();
|
|
}
|
|
|
|
void SoundChannel_PCJr_SCI0::envRL() {
|
|
static const byte envTable[] = { 1, 1, 1, 2, 2, 3, 3, 4, 4, 6, 6, 7, 8, 9, 10, 11, 12, 127 };
|
|
byte c = _envCount++;
|
|
_envVolume = (envTable[c] == 127) ? 15 : _envAttn + envTable[c];
|
|
updateChannelVolume();
|
|
}
|
|
|
|
void SoundChannel_PCJr_SCI1::reset() {
|
|
SoundHWChannel::reset();
|
|
_duration = _releaseDuration = 0;
|
|
_sustain = _release = 0;
|
|
_ctrlVolume = _envVolume = 15;
|
|
_envData = nullptr;
|
|
_pbDiv = 170;
|
|
updateChannelVolume();
|
|
}
|
|
|
|
void SoundChannel_PCJr_SCI1::updateChannelVolume() {
|
|
static const byte veloTable[16] = { 0x01, 0x03, 0x06, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0C, 0x0D, 0x0D, 0x0E, 0x0E, 0x0E, 0x0F, 0x0F };
|
|
int velo = _velocity ? veloTable[_velocity >> 3] : 0;
|
|
int vol = _ctrlVolume ? MAX<int>(1, _ctrlVolume >> 3) : 0;
|
|
int tl = ((vol * velo / 15) * _envVolume / 15) * _masterVolume;
|
|
if (tl > 0 && tl < 15)
|
|
tl = 15;
|
|
int attn = 15 - tl / 15;
|
|
_smpAmplitude = _smpVolTable.get()[attn];
|
|
}
|
|
|
|
void SoundChannel_PCJr_SCI1::processEnvelope() {
|
|
++_duration;
|
|
if (_release)
|
|
++_releaseDuration;
|
|
processInstrument();
|
|
updateChannelVolume();
|
|
}
|
|
|
|
void SoundChannel_PCJr_SCI1::noteOn(byte note, byte velocity) {
|
|
_note = note;
|
|
_velocity = velocity;
|
|
_envState = 0;
|
|
_envCount = 0;
|
|
_duration = 0;
|
|
_releaseDuration = 0;
|
|
_release = 0;
|
|
_sustain = 0;
|
|
|
|
assert(_instrumentOffsets);
|
|
assert(_instrumentData);
|
|
_envData = &_instrumentData[_instrumentOffsets[_program[_partMapping]]];
|
|
}
|
|
|
|
void SoundChannel_PCJr_SCI1::noteOff(byte sustain) {
|
|
byte &dest = sustain ? _sustain : _release;
|
|
dest = 1;
|
|
}
|
|
|
|
void SoundChannel_PCJr_SCI1::chanOff() {
|
|
SoundHWChannel::chanOff();
|
|
_duration = 0;
|
|
_releaseDuration = 0;
|
|
_sustain = 0;
|
|
_release = 0;
|
|
}
|
|
|
|
void SoundChannel_PCJr_SCI1::sustainOff() {
|
|
_sustain = 0;
|
|
_release = 1;
|
|
}
|
|
|
|
void SoundChannel_PCJr_SCI1::processInstrument() {
|
|
if (_envCount == 0xFE) {
|
|
if (_release)
|
|
_envCount = 0;
|
|
else
|
|
return;
|
|
}
|
|
|
|
if (_envCount == 0) {
|
|
byte a = _envData[_envState << 1];
|
|
if (a == 0xFF) {
|
|
chanOff();
|
|
_envCount = 0;
|
|
} else {
|
|
_envCount = _envData[(_envState << 1) + 1];
|
|
_envVolume = a;
|
|
++_envState;
|
|
}
|
|
} else {
|
|
--_envCount;
|
|
}
|
|
}
|
|
|
|
class MidiDriver_PCJr : public MidiDriver, public Audio::AudioStream {
|
|
public:
|
|
friend class MidiPlayer_PCJr;
|
|
enum Properties {
|
|
kPropNone = 0,
|
|
kPropVolume
|
|
};
|
|
|
|
MidiDriver_PCJr(Audio::Mixer *mixer, SciVersion version, bool pcsMode);
|
|
~MidiDriver_PCJr() override;
|
|
|
|
// MidiDriver
|
|
int open() override;
|
|
void close() override;
|
|
void send(uint32 b) override;
|
|
MidiChannel *allocateChannel() override { return nullptr; }
|
|
MidiChannel *getPercussionChannel() override { return nullptr; }
|
|
uint32 property(int prop, uint32 value) override;
|
|
void initTrack(SciSpan<const byte> &header);
|
|
|
|
// AudioStream
|
|
int readBuffer(int16 *buf, const int len) override { return generateSamples(buf, len); }
|
|
bool endOfData() const override { return false; }
|
|
bool isStereo() const override { return false; }
|
|
int getRate() const override { return _mixer->getOutputRate(); }
|
|
|
|
// MidiDriver
|
|
void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override;
|
|
bool isOpen() const override { return _isOpen; }
|
|
uint32 getBaseTempo() override { return 16666; }
|
|
|
|
private:
|
|
bool loadInstruments(Resource &resource);
|
|
|
|
void noteOn(byte part, byte note, byte velocity);
|
|
void noteOff(byte part, byte note);
|
|
void controlChange(byte part, byte controller, byte value);
|
|
void programChange(byte part, byte program);
|
|
void pitchBend(byte part, uint16 value);
|
|
|
|
void controlChangeSustain(byte part, byte sus);
|
|
void controlChangePolyphony(byte part, byte numChan);
|
|
void addChannels(byte part, byte num);
|
|
void dropChannels(byte part, byte num);
|
|
void assignFreeChannels(byte part);
|
|
byte allocateChannel(byte part);
|
|
|
|
int generateSamples(int16 *buf, int len);
|
|
void nextTick();
|
|
|
|
byte _masterVolume;
|
|
byte *_chanMapping;
|
|
byte *_chanMissing;
|
|
byte *_program;
|
|
byte *_sustain;
|
|
|
|
uint32 _sndUpdateSmpQty;
|
|
uint32 _sndUpdateSmpQtyRem;
|
|
uint32 _sndUpdateCountDown;
|
|
uint32 _sndUpdateCountDownRem;
|
|
|
|
SoundHWChannel **_channels;
|
|
|
|
const uint16 *_instrumentOffsets;
|
|
const byte *_instrumentData;
|
|
|
|
const SciVersion _version;
|
|
const byte _numChannels;
|
|
const bool _pcsMode;
|
|
|
|
Audio::Mixer *_mixer;
|
|
Audio::SoundHandle _mixerSoundHandle;
|
|
Common::TimerManager::TimerProc _timerProc;
|
|
void *_timerProcPara;
|
|
bool _isOpen;
|
|
};
|
|
|
|
MidiDriver_PCJr::MidiDriver_PCJr(Audio::Mixer *mixer, SciVersion version, bool pcsMode) : _mixer(mixer), _version(version), _pcsMode(pcsMode), _numChannels(pcsMode ? 1 : 3),
|
|
_masterVolume(0), _channels(nullptr), _instrumentOffsets(nullptr), _instrumentData(nullptr), _timerProc(nullptr), _timerProcPara(nullptr), _isOpen(false) {
|
|
|
|
uint16 *smpVolTable = new uint16[16]();
|
|
for (int i = 0; i < 15; ++i) // The last entry is left at zero.
|
|
smpVolTable[i] = (double)((32767 & ~_numChannels) / _numChannels) / pow(10.0, (double)i / 10.0);
|
|
Common::SharedPtr<const uint16> smpVolTablePtr(smpVolTable, Common::ArrayDeleter<const uint16>());
|
|
|
|
uint16 *freqTable = new uint16[48]();
|
|
assert(freqTable);
|
|
for (int i = 0; i < 48; ++i)
|
|
freqTable[i] = pow(2, (double)(288 + i) / 48.0) * 440.0;
|
|
Common::SharedPtr<const uint16> freqTablePtr(freqTable, Common::ArrayDeleter<const uint16>());
|
|
|
|
_chanMapping = new byte[16]();
|
|
_chanMissing = new byte[16]();
|
|
_program = new byte[16]();
|
|
_sustain = new byte[16]();
|
|
|
|
assert(_chanMapping);
|
|
assert(_chanMissing);
|
|
assert(_program);
|
|
assert(_sustain);
|
|
|
|
_channels = new SoundHWChannel*[_numChannels]();
|
|
assert(_channels);
|
|
for (int i = 0; i < _numChannels; ++i) {
|
|
if (pcsMode)
|
|
_channels[i] = new SoundChannel_PCSpeaker(freqTablePtr, smpVolTablePtr, _masterVolume, getRate());
|
|
else if (version <= SCI_VERSION_0_LATE)
|
|
_channels[i] = new SoundChannel_PCJr_SCI0(freqTablePtr, smpVolTablePtr, _masterVolume, getRate());
|
|
else
|
|
_channels[i] = new SoundChannel_PCJr_SCI1(freqTablePtr, smpVolTablePtr, _masterVolume, getRate(), _instrumentOffsets, _instrumentData, _program);
|
|
}
|
|
|
|
_sndUpdateSmpQty = (mixer->getOutputRate() << 16) / 0x3C0000;
|
|
_sndUpdateSmpQtyRem = (mixer->getOutputRate() << 16) % 0x3C0000;
|
|
_sndUpdateCountDown = _sndUpdateSmpQty;
|
|
_sndUpdateCountDownRem = 0;
|
|
}
|
|
|
|
MidiDriver_PCJr::~MidiDriver_PCJr() {
|
|
close();
|
|
if (_channels) {
|
|
for (int i = 0; i < _numChannels; ++i)
|
|
delete _channels[i];
|
|
delete[] _channels;
|
|
}
|
|
|
|
delete[] _instrumentOffsets;
|
|
delete[] _instrumentData;
|
|
delete[] _chanMapping;
|
|
delete[] _chanMissing;
|
|
delete[] _program;
|
|
delete[] _sustain;
|
|
}
|
|
|
|
void MidiDriver_PCJr::send(uint32 b) {
|
|
byte command = b & 0xff;
|
|
byte op1 = (b >> 8) & 0xff;
|
|
byte op2 = (b >> 16) & 0xff;
|
|
byte part = command & 0x0f;
|
|
|
|
switch (command & 0xf0) {
|
|
case 0x80:
|
|
noteOff(part, op1);
|
|
break;
|
|
case 0x90:
|
|
if (!op2)
|
|
noteOff(part, op1);
|
|
else
|
|
noteOn(part, op1, op2);
|
|
break;
|
|
case 0xb0:
|
|
controlChange(part, op1, op2);
|
|
break;
|
|
case 0xc0:
|
|
programChange(part, op1);
|
|
break;
|
|
case 0xe0:
|
|
pitchBend(part, (op1 & 0x7f) | ((op2 & 0x7f) << 7));
|
|
break;
|
|
default:
|
|
debug(2, "Unused MIDI command %02x %02x %02x", command, op1, op2);
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint32 MidiDriver_PCJr::property(int prop, uint32 value) {
|
|
uint32 res = 0;
|
|
value &= 0xffff;
|
|
|
|
switch (prop) {
|
|
case kPropVolume:
|
|
res = _masterVolume;
|
|
if (value != 0xffff) {
|
|
_masterVolume = value;
|
|
for (int i = 0; i < _numChannels; ++i)
|
|
_channels[i]->updateChannelVolume();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
void MidiDriver_PCJr::initTrack(SciSpan<const byte> &header) {
|
|
if (!_isOpen || _version > SCI_VERSION_0_LATE)
|
|
return;
|
|
|
|
uint8 readPos = 0;
|
|
uint8 caps = header.getInt8At(readPos++);
|
|
if (caps != 0 && caps != 2)
|
|
return;
|
|
|
|
for (int i = 0; i < _numChannels; ++i)
|
|
_channels[i]->reset();
|
|
|
|
if (_version == SCI_VERSION_0_EARLY) {
|
|
byte chanFlag = _pcsMode ? 0x04 : 0x02;
|
|
for (int i = 0, numAssigned = 0; i < 16 && numAssigned < _numChannels; ++i) {
|
|
uint8 f = header.getInt8At(++readPos);
|
|
if ((!(f & 8) || (f & 1)) && (f & chanFlag))
|
|
_channels[numAssigned++]->setPart(i);
|
|
}
|
|
} else {
|
|
byte chanFlag = _pcsMode ? 0x20 : 0x10;
|
|
for (int i = 0, numAssigned = 0; i < 16 && numAssigned < _numChannels; ++i) {
|
|
uint8 f = header.getInt8At(++readPos);
|
|
readPos++;
|
|
if (f & chanFlag)
|
|
_channels[numAssigned++]->setPart(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MidiDriver_PCJr::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
|
|
_timerProc = timer_proc;
|
|
_timerProcPara = timer_param;
|
|
}
|
|
|
|
int MidiDriver_PCJr::open() {
|
|
if (_isOpen)
|
|
return MERR_ALREADY_OPEN;
|
|
|
|
_masterVolume = 15;
|
|
for (int i = 0; i < _numChannels; i++)
|
|
_channels[i]->reset();
|
|
|
|
if (_version > SCI_VERSION_0_LATE && !_pcsMode) {
|
|
ResourceManager *resMan = g_sci->getResMan();
|
|
Resource *resource = resMan->findResource(ResourceId(kResourceTypePatch, 101), false);
|
|
if (resource == nullptr)
|
|
return MERR_CANNOT_CONNECT;
|
|
if (!loadInstruments(*resource))
|
|
return MERR_CANNOT_CONNECT;
|
|
}
|
|
|
|
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO);
|
|
|
|
_isOpen = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void MidiDriver_PCJr::close() {
|
|
_mixer->stopHandle(_mixerSoundHandle);
|
|
}
|
|
|
|
bool MidiDriver_PCJr::loadInstruments(Resource &resource) {
|
|
uint16 *instrumentOffsets = new uint16[128]();
|
|
if (!instrumentOffsets)
|
|
return false;
|
|
|
|
uint32 readPos = 0;
|
|
for (int i = 0; i < 128; ++i) {
|
|
int in = resource.getUint16LEAt(readPos) - 0x100;
|
|
if (in < 0) {
|
|
warning("%s(): Error reading instrument patch resource", __FUNCTION__);
|
|
delete[] instrumentOffsets;
|
|
return false;
|
|
}
|
|
instrumentOffsets[i] = in;
|
|
readPos += 2;
|
|
}
|
|
|
|
uint8 *instrumentData = new uint8[770]();
|
|
if (!instrumentData)
|
|
return false;
|
|
|
|
uint32 writePos = 0;
|
|
for (int i = 0; i < 40; ++i) {
|
|
for (uint8 in = 0; writePos < 770 && in != 0xFF; ) {
|
|
in = resource.getUint8At(readPos++);
|
|
instrumentData[writePos++] = in;
|
|
}
|
|
}
|
|
|
|
_instrumentOffsets = instrumentOffsets;
|
|
_instrumentData = instrumentData;
|
|
|
|
return true;
|
|
}
|
|
|
|
void MidiDriver_PCJr::noteOn(byte part, byte note, byte velocity) {
|
|
if (_pcsMode) {
|
|
if (!_channels[0]->isMappedToPart(part))
|
|
return;
|
|
_channels[0]->chanOff();
|
|
if (note >= 24 && note <= 119)
|
|
_channels[0]->noteOn(note, velocity);
|
|
return;
|
|
} else if (_version > SCI_VERSION_0_LATE) {
|
|
if (note < 21 || note > 116)
|
|
return;
|
|
|
|
for (int i = 0; i < _numChannels; ++i) {
|
|
if (!_channels[i]->isMappedToPart(part) || !_channels[i]->hasNote(note))
|
|
continue;
|
|
_channels[i]->chanOff();
|
|
_channels[i]->noteOn(note, velocity);
|
|
return;
|
|
}
|
|
} else if (note == 0 || note == 1) {
|
|
return;
|
|
}
|
|
|
|
byte c = allocateChannel(part);
|
|
if (c != 0xFF)
|
|
_channels[c]->noteOn(note, velocity);
|
|
}
|
|
|
|
void MidiDriver_PCJr::noteOff(byte part, byte note) {
|
|
for (int i = 0; i < _numChannels; ++i) {
|
|
if (!_channels[i]->isMappedToPart(part) || !_channels[i]->hasNote(note))
|
|
continue;
|
|
_channels[i]->noteOff(_sustain[part]);
|
|
}
|
|
}
|
|
|
|
void MidiDriver_PCJr::controlChange(byte part, byte controller, byte value) {
|
|
switch (controller) {
|
|
case 7:
|
|
for (int i = 0; i < _numChannels; ++i) {
|
|
if (_channels[i]->isMappedToPart(part))
|
|
_channels[i]->setVolume(value);
|
|
}
|
|
break;
|
|
case 64:
|
|
controlChangeSustain(part, value);
|
|
break;
|
|
case SCI_MIDI_SET_POLYPHONY:
|
|
controlChangePolyphony(part, value);
|
|
break;
|
|
case SCI_MIDI_CHANNEL_NOTES_OFF:
|
|
case SCI_MIDI_CHANNEL_SOUND_OFF:
|
|
for (int i = 0; i < _numChannels; ++i) {
|
|
if (_channels[i]->isMappedToPart(part))
|
|
_channels[i]->chanOff();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MidiDriver_PCJr::programChange(byte part, byte program) {
|
|
if (_version > SCI_VERSION_0_LATE && !_pcsMode)
|
|
_program[part] = program;
|
|
}
|
|
|
|
void MidiDriver_PCJr::pitchBend(byte part, uint16 value) {
|
|
for (int i = 0; i < _numChannels; ++i) {
|
|
if (_channels[i]->isMappedToPart(part))
|
|
_channels[i]->pitchBend(value);
|
|
}
|
|
}
|
|
|
|
void MidiDriver_PCJr::controlChangeSustain(byte part, byte sus) {
|
|
if (_version <= SCI_VERSION_0_LATE || _pcsMode)
|
|
return;
|
|
|
|
_sustain[part] = sus;
|
|
if (sus)
|
|
return;
|
|
|
|
for (int i = 0; i < _numChannels; ++i) {
|
|
if (_channels[i]->isMappedToPart(part) && _channels[i]->isSustained())
|
|
_channels[i]->sustainOff();
|
|
}
|
|
}
|
|
|
|
void MidiDriver_PCJr::controlChangePolyphony(byte part, byte numChan) {
|
|
if (_version <= SCI_VERSION_0_LATE)
|
|
return;
|
|
|
|
if (_pcsMode) {
|
|
if (numChan == 0 || !_channels[0]->isMappedToPart(part))
|
|
_channels[0]->chanOff();
|
|
_channels[0]->setPart(numChan ? part : 0xFF);
|
|
return;
|
|
}
|
|
|
|
uint8 numAssigned = 0;
|
|
for (int i = 0; i < _numChannels; ++i) {
|
|
if (_channels[i]->isMappedToPart(part))
|
|
numAssigned++;
|
|
}
|
|
|
|
numAssigned += _chanMissing[part];
|
|
if (numAssigned < numChan) {
|
|
addChannels(part, numChan - numAssigned);
|
|
} else if (numAssigned > numChan) {
|
|
dropChannels(part, numAssigned - numChan);
|
|
assignFreeChannels(part);
|
|
}
|
|
}
|
|
|
|
void MidiDriver_PCJr::addChannels(byte part, byte num) {
|
|
for (int i = 0; i < _numChannels; ++i) {
|
|
if (!_channels[i]->isMappedToPart(0xFF))
|
|
continue;
|
|
_channels[i]->setPart(part);
|
|
|
|
if (_channels[i]->isPlaying())
|
|
_channels[i]->chanOff();
|
|
|
|
if (!--num)
|
|
break;
|
|
}
|
|
_chanMissing[part] += num;
|
|
}
|
|
|
|
void MidiDriver_PCJr::dropChannels(byte part, byte num) {
|
|
if (_chanMissing[part] == num) {
|
|
_chanMissing[part] = 0;
|
|
return;
|
|
} else if (_chanMissing[part] > num) {
|
|
_chanMissing[part] -= num;
|
|
return;
|
|
}
|
|
|
|
num -= _chanMissing[part];
|
|
_chanMissing[part] = 0;
|
|
|
|
for (int i = 0; i < _numChannels; i++) {
|
|
if (!_channels[i]->isMappedToPart(part) || _channels[i]->isPlaying())
|
|
continue;
|
|
_channels[i]->setPart(0xFF);
|
|
if (!--num)
|
|
return;
|
|
}
|
|
|
|
do {
|
|
uint16 oldest = 0;
|
|
byte dropCh = 0;
|
|
for (int i = 0; i < _numChannels; i++) {
|
|
if (!_channels[i]->isMappedToPart(part))
|
|
continue;
|
|
|
|
uint16 ct = _channels[i]->getDuration();
|
|
|
|
if (ct >= oldest) {
|
|
dropCh = i;
|
|
oldest = ct;
|
|
}
|
|
}
|
|
|
|
_channels[dropCh]->chanOff();
|
|
_channels[dropCh]->setPart(0xFF);
|
|
} while (--num);
|
|
}
|
|
|
|
void MidiDriver_PCJr::assignFreeChannels(byte part) {
|
|
uint8 freeChan = 0;
|
|
for (int i = 0; i < _numChannels; i++) {
|
|
if (_channels[i]->isMappedToPart(0xFF))
|
|
freeChan++;
|
|
}
|
|
|
|
if (!freeChan)
|
|
return;
|
|
|
|
for (int i = 0; i < 16; i++) {
|
|
if (!_chanMissing[i])
|
|
continue;
|
|
if (_chanMissing[i] < freeChan) {
|
|
freeChan -= _chanMissing[i];
|
|
addChannels(part, _chanMissing[i]);
|
|
_chanMissing[i] = 0;
|
|
} else {
|
|
_chanMissing[i] -= freeChan;
|
|
addChannels(part, _chanMissing[i]);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
byte MidiDriver_PCJr::allocateChannel(byte part) {
|
|
byte res = 0xFF;
|
|
if (_version <= SCI_VERSION_0_LATE) {
|
|
for (int i = 0; i < _numChannels; ++i) {
|
|
if (_channels[i]->isMappedToPart(part))
|
|
res = i;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
uint16 oldest = 0;
|
|
byte c = _chanMapping[part];
|
|
|
|
for (bool loop = true; loop;) {
|
|
c = (c + 1) % _numChannels;
|
|
if (c == _chanMapping[part])
|
|
loop = false;
|
|
|
|
if (!_channels[c]->isMappedToPart(part))
|
|
continue;
|
|
|
|
if (!_channels[c]->isPlaying()) {
|
|
_chanMapping[part] = c;
|
|
return c;
|
|
}
|
|
|
|
uint16 ct = _channels[c]->getDuration();
|
|
if (ct < oldest)
|
|
continue;
|
|
|
|
res = c;
|
|
oldest = ct;
|
|
}
|
|
|
|
if (oldest != 0) {
|
|
_chanMapping[part] = res;
|
|
_channels[res]->chanOff();
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
int MidiDriver_PCJr::generateSamples(int16 *data, int len) {
|
|
for (int i = 0; i < len; i++) {
|
|
if (!--_sndUpdateCountDown) {
|
|
_sndUpdateCountDown = _sndUpdateSmpQty;
|
|
_sndUpdateCountDownRem += _sndUpdateSmpQtyRem;
|
|
while (_sndUpdateCountDownRem >= (_sndUpdateSmpQty << 16)) {
|
|
_sndUpdateCountDownRem -= (_sndUpdateSmpQty << 16);
|
|
++_sndUpdateCountDown;
|
|
}
|
|
nextTick();
|
|
}
|
|
|
|
int16 smp = 0;
|
|
|
|
for (int chan = 0; chan < _numChannels; chan++) {
|
|
if (!_channels[chan]->isPlaying())
|
|
continue;
|
|
_channels[chan]->recalcSample();
|
|
smp += _channels[chan]->currentSample();
|
|
}
|
|
data[i] = smp;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
void MidiDriver_PCJr::nextTick() {
|
|
_timerProc(_timerProcPara);
|
|
for (int i = 0; i < _numChannels; i++) {
|
|
if (_channels[i]->isPlaying())
|
|
_channels[i]->processEnvelope();
|
|
}
|
|
}
|
|
|
|
class MidiPlayer_PCJr : public MidiPlayer {
|
|
public:
|
|
MidiPlayer_PCJr(SciVersion version, bool pcsMode) : MidiPlayer(version) { _driver = new MidiDriver_PCJr(g_system->getMixer(), version, pcsMode); }
|
|
byte getPlayId() const override;
|
|
int getPolyphony() const override { return 3; }
|
|
bool hasRhythmChannel() const override { return false; }
|
|
void setVolume(byte volume) override { _driver->property(MidiDriver_PCJr::kPropVolume, volume); }
|
|
void initTrack(SciSpan<const byte> &trackData) override;
|
|
};
|
|
|
|
byte MidiPlayer_PCJr::getPlayId() const {
|
|
switch (_version) {
|
|
case SCI_VERSION_0_EARLY:
|
|
return 0x02;
|
|
case SCI_VERSION_0_LATE:
|
|
return 0x10;
|
|
default:
|
|
return 0x13;
|
|
}
|
|
}
|
|
|
|
void MidiPlayer_PCJr::initTrack(SciSpan<const byte> &trackData) {
|
|
if (_driver)
|
|
static_cast<MidiDriver_PCJr*>(_driver)->initTrack(trackData);
|
|
}
|
|
|
|
MidiPlayer *MidiPlayer_PCJr_create(SciVersion version) {
|
|
return new MidiPlayer_PCJr(version, false);
|
|
}
|
|
|
|
class MidiPlayer_PCSpeaker : public MidiPlayer_PCJr {
|
|
public:
|
|
MidiPlayer_PCSpeaker(SciVersion version) : MidiPlayer_PCJr(version, true) { }
|
|
byte getPlayId() const override;
|
|
int getPolyphony() const override { return 1; }
|
|
};
|
|
|
|
byte MidiPlayer_PCSpeaker::getPlayId() const {
|
|
switch (_version) {
|
|
case SCI_VERSION_0_EARLY:
|
|
return 0x04;
|
|
case SCI_VERSION_0_LATE:
|
|
return 0x20;
|
|
default:
|
|
return 0x12;
|
|
}
|
|
}
|
|
|
|
MidiPlayer *MidiPlayer_PCSpeaker_create(SciVersion version) {
|
|
return new MidiPlayer_PCSpeaker(version);
|
|
}
|
|
|
|
} // End of namespace Sci
|