Initial commit
This commit is contained in:
969
engines/sci/sound/drivers/adlib.cpp
Normal file
969
engines/sci/sound/drivers/adlib.cpp
Normal file
@@ -0,0 +1,969 @@
|
||||
/* 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/sci.h"
|
||||
|
||||
#include "common/file.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
#include "audio/fmopl.h"
|
||||
#include "audio/mididrv.h"
|
||||
|
||||
#include "sci/resource/resource.h"
|
||||
#include "sci/sound/drivers/mididriver.h"
|
||||
#include "sci/util.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
#ifdef __DC__
|
||||
#define STEREO false
|
||||
#else
|
||||
#define STEREO true
|
||||
#endif
|
||||
|
||||
class MidiDriver_AdLib : public MidiDriver {
|
||||
public:
|
||||
enum {
|
||||
kVoices = 9,
|
||||
kRhythmKeys = 62
|
||||
};
|
||||
|
||||
MidiDriver_AdLib(SciVersion version) : _version(version), _isSCI0(version < SCI_VERSION_1_EARLY), _playSwitch(true), _masterVolume(15),
|
||||
_numVoiceMax(version == SCI_VERSION_0_EARLY ? 8 : kVoices), _rhythmKeyMap(), _opl(nullptr), _adlibTimerParam(nullptr), _adlibTimerProc(nullptr), _stereo(false), _isOpen(false) { }
|
||||
~MidiDriver_AdLib() override { }
|
||||
|
||||
// MidiDriver
|
||||
int open() override { return -1; } // Dummy implementation (use openAdLib)
|
||||
int openAdLib();
|
||||
void close() override;
|
||||
void send(uint32 b) override;
|
||||
void initTrack(SciSpan<const byte> &header);
|
||||
|
||||
MidiChannel *allocateChannel() override { return nullptr; }
|
||||
MidiChannel *getPercussionChannel() override { return nullptr; }
|
||||
bool isOpen() const override { return _isOpen; }
|
||||
uint32 getBaseTempo() override { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; }
|
||||
|
||||
// MidiDriver
|
||||
void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) override;
|
||||
|
||||
void onTimer();
|
||||
|
||||
void setVolume(byte volume);
|
||||
void playSwitch(bool play);
|
||||
bool loadResource(const SciSpan<const byte> &data);
|
||||
uint32 property(int prop, uint32 param) override;
|
||||
|
||||
bool useRhythmChannel() const { return _rhythmKeyMap; }
|
||||
|
||||
private:
|
||||
enum ChannelID {
|
||||
kLeftChannel = 1,
|
||||
kRightChannel = 2
|
||||
};
|
||||
|
||||
struct AdLibOperator {
|
||||
bool amplitudeMod;
|
||||
bool vibrato;
|
||||
bool envelopeType;
|
||||
bool kbScaleRate;
|
||||
byte frequencyMult; // (0-15)
|
||||
byte kbScaleLevel; // (0-3)
|
||||
byte totalLevel; // (0-63, 0=max, 63=min)
|
||||
byte attackRate; // (0-15)
|
||||
byte decayRate; // (0-15)
|
||||
byte sustainLevel; // (0-15)
|
||||
byte releaseRate; // (0-15)
|
||||
byte waveForm; // (0-3)
|
||||
};
|
||||
|
||||
struct AdLibModulator {
|
||||
byte feedback; // (0-7)
|
||||
bool algorithm;
|
||||
};
|
||||
|
||||
struct AdLibPatch {
|
||||
AdLibOperator op[2];
|
||||
AdLibModulator mod;
|
||||
};
|
||||
|
||||
struct Channel {
|
||||
uint8 patch; // Patch setting
|
||||
uint8 volume; // Channel volume (0-63)
|
||||
uint8 pan; // Pan setting (0-127, 64 is center)
|
||||
uint8 holdPedal; // Hold pedal setting (0 to 63 is off, 127 to 64 is on)
|
||||
uint8 extraVoices; // The number of additional voices this channel optimally needs
|
||||
uint16 pitchWheel; // Pitch wheel setting (0-16383, 8192 is center)
|
||||
uint8 lastVoice; // Last voice used for this MIDI channel
|
||||
bool enableVelocity; // Enable velocity control (SCI0)
|
||||
uint8 voices; // Number of voices currently used by this channel
|
||||
uint8 mappedVoices; // Number of voices currently mapped to this channel
|
||||
|
||||
Channel() : patch(0), volume(63), pan(64), holdPedal(0), extraVoices(0),
|
||||
pitchWheel(8192), lastVoice(0), enableVelocity(false), voices(0),
|
||||
mappedVoices(0) { }
|
||||
};
|
||||
|
||||
struct AdLibVoice {
|
||||
int8 channel; // MIDI channel that is currently using this voice, or -1
|
||||
int8 mappedChannel; // MIDI channel that this voice is mapped to, or -1
|
||||
int8 note; // Currently playing MIDI note or -1
|
||||
int patch; // Currently playing patch or -1
|
||||
uint8 velocity; // Note velocity
|
||||
bool isSustained; // Flag indicating a note that is being sustained by the hold pedal
|
||||
uint16 age; // Age of the current note
|
||||
|
||||
AdLibVoice() : channel(-1), mappedChannel(-1), note(-1), patch(-1), velocity(0), isSustained(false), age(0) { }
|
||||
};
|
||||
|
||||
bool _stereo;
|
||||
bool _isSCI0;
|
||||
SciVersion _version;
|
||||
OPL::OPL *_opl;
|
||||
bool _isOpen;
|
||||
bool _playSwitch;
|
||||
int _masterVolume;
|
||||
const uint8 _numVoiceMax;
|
||||
Channel _channels[MIDI_CHANNELS];
|
||||
AdLibVoice _voices[kVoices];
|
||||
Common::SpanOwner<SciSpan<const byte> > _rhythmKeyMap;
|
||||
Common::Array<AdLibPatch> _patches;
|
||||
Common::List<int> _voiceQueue;
|
||||
|
||||
Common::TimerManager::TimerProc _adlibTimerProc;
|
||||
void *_adlibTimerParam;
|
||||
|
||||
void loadInstrument(const SciSpan<const byte> &ins);
|
||||
void voiceOn(int voice, int note, int velocity);
|
||||
void voiceOff(int voice);
|
||||
void setPatch(int voice, int patch);
|
||||
void setNote(int voice, int note, bool key);
|
||||
void setVelocity(int voice);
|
||||
void setOperator(int oper, AdLibOperator &op);
|
||||
void setRegister(int reg, int value, int channels = kLeftChannel | kRightChannel);
|
||||
void renewNotes(int channel, bool key);
|
||||
void noteOn(int channel, int note, int velocity);
|
||||
void noteOff(int channel, int note);
|
||||
int findVoice(int channel);
|
||||
int findVoiceLateSci11(int channel);
|
||||
void voiceMapping(int channel, int voices);
|
||||
void assignVoices(int channel, int voices);
|
||||
void releaseVoices(int channel, int voices);
|
||||
void donateVoices();
|
||||
void queueMoveToBack(int voice);
|
||||
void setVelocityReg(int regOffset, int velocity, int kbScaleLevel, int pan);
|
||||
int calcVelocity(int voice, int op);
|
||||
};
|
||||
|
||||
class MidiPlayer_AdLib : public MidiPlayer {
|
||||
public:
|
||||
MidiPlayer_AdLib(SciVersion soundVersion) : MidiPlayer(soundVersion) { _driver = new MidiDriver_AdLib(soundVersion); }
|
||||
~MidiPlayer_AdLib() override {
|
||||
delete _driver;
|
||||
_driver = nullptr;
|
||||
}
|
||||
|
||||
int open(ResourceManager *resMan) override;
|
||||
void close() override;
|
||||
|
||||
byte getPlayId() const override;
|
||||
int getPolyphony() const override { return MidiDriver_AdLib::kVoices; }
|
||||
bool hasRhythmChannel() const override { return false; }
|
||||
void setVolume(byte volume) override { static_cast<MidiDriver_AdLib *>(_driver)->setVolume(volume); }
|
||||
void playSwitch(bool play) override { static_cast<MidiDriver_AdLib *>(_driver)->playSwitch(play); }
|
||||
void initTrack(SciSpan<const byte> &header) override { static_cast<MidiDriver_AdLib *>(_driver)->initTrack(header); }
|
||||
int getLastChannel() const override { return (static_cast<const MidiDriver_AdLib *>(_driver)->useRhythmChannel() ? 8 : 15); }
|
||||
};
|
||||
|
||||
static const byte registerOffset[MidiDriver_AdLib::kVoices] = {
|
||||
0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12
|
||||
};
|
||||
|
||||
static const byte velocityMap1[64] = {
|
||||
0x00, 0x0c, 0x0d, 0x0e, 0x0f, 0x11, 0x12, 0x13,
|
||||
0x14, 0x16, 0x17, 0x18, 0x1a, 0x1b, 0x1c, 0x1d,
|
||||
0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
|
||||
0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2d, 0x2d, 0x2e,
|
||||
0x2f, 0x30, 0x31, 0x32, 0x32, 0x33, 0x34, 0x34,
|
||||
0x35, 0x36, 0x36, 0x37, 0x38, 0x38, 0x39, 0x3a,
|
||||
0x3b, 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3d, 0x3d,
|
||||
0x3d, 0x3e, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f
|
||||
};
|
||||
|
||||
static const byte velocityMap2[64] = {
|
||||
0x00, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a,
|
||||
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x21,
|
||||
0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
|
||||
0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x2f, 0x30,
|
||||
0x31, 0x32, 0x32, 0x33, 0x34, 0x34, 0x35, 0x36,
|
||||
0x36, 0x37, 0x38, 0x38, 0x39, 0x39, 0x3a, 0x3a,
|
||||
0x3b, 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3d, 0x3d,
|
||||
0x3d, 0x3e, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f
|
||||
};
|
||||
|
||||
// One octave with three pitch wheel positions after each note
|
||||
static const int adlibFreq[48] = {
|
||||
0x157, 0x15c, 0x161, 0x166, 0x16b, 0x171, 0x176, 0x17b,
|
||||
0x181, 0x186, 0x18c, 0x192, 0x198, 0x19e, 0x1a4, 0x1aa,
|
||||
0x1b0, 0x1b6, 0x1bd, 0x1c3, 0x1ca, 0x1d0, 0x1d7, 0x1de,
|
||||
0x1e5, 0x1ec, 0x1f3, 0x1fa, 0x202, 0x209, 0x211, 0x218,
|
||||
0x220, 0x228, 0x230, 0x238, 0x241, 0x249, 0x252, 0x25a,
|
||||
0x263, 0x26c, 0x275, 0x27e, 0x287, 0x290, 0x29a, 0x2a4
|
||||
};
|
||||
|
||||
int MidiDriver_AdLib::openAdLib() {
|
||||
_stereo = STEREO;
|
||||
|
||||
debug(3, "ADLIB: Starting driver in %s mode", (_isSCI0 ? "SCI0" : "SCI1"));
|
||||
|
||||
// Fill in the voice queue
|
||||
for (int i = 0; i < kVoices; ++i)
|
||||
_voiceQueue.push_back(i);
|
||||
|
||||
_opl = OPL::Config::create(_stereo ? OPL::Config::kDualOpl2 : OPL::Config::kOpl2);
|
||||
|
||||
// Try falling back to mono, thus plain OPL2 emulator, when no Dual OPL2 is available.
|
||||
if (!_opl && _stereo) {
|
||||
_stereo = false;
|
||||
_opl = OPL::Config::create(OPL::Config::kOpl2);
|
||||
}
|
||||
|
||||
if (!_opl)
|
||||
return -1;
|
||||
|
||||
if (!_opl->init()) {
|
||||
delete _opl;
|
||||
_opl = nullptr;
|
||||
return -1;
|
||||
}
|
||||
|
||||
setRegister(0xBD, 0);
|
||||
setRegister(0x08, 0);
|
||||
setRegister(0x01, 0x20);
|
||||
|
||||
_isOpen = true;
|
||||
|
||||
_opl->start(new Common::Functor0Mem<void, MidiDriver_AdLib>(this, &MidiDriver_AdLib::onTimer));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::close() {
|
||||
delete _opl;
|
||||
_rhythmKeyMap.clear();
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::setVolume(byte volume) {
|
||||
_masterVolume = volume;
|
||||
renewNotes(-1, true);
|
||||
}
|
||||
|
||||
// MIDI messages can be found at https://web.archive.org/web/20120128110425/http://www.midi.org/techspecs/midimessages.php
|
||||
void MidiDriver_AdLib::send(uint32 b) {
|
||||
byte command = b & 0xf0;
|
||||
byte channel = b & 0xf;
|
||||
byte op1 = (b >> 8) & 0xff;
|
||||
byte op2 = (b >> 16) & 0xff;
|
||||
|
||||
switch (command) {
|
||||
case 0x80:
|
||||
noteOff(channel, op1);
|
||||
break;
|
||||
case 0x90:
|
||||
noteOn(channel, op1, op2);
|
||||
break;
|
||||
case 0xb0:
|
||||
switch (op1) {
|
||||
case 0x07:
|
||||
_channels[channel].volume = op2 >> 1;
|
||||
renewNotes(channel, true);
|
||||
break;
|
||||
case 0x0a:
|
||||
_channels[channel].pan = op2;
|
||||
renewNotes(channel, true);
|
||||
break;
|
||||
case 0x40:
|
||||
_channels[channel].holdPedal = op2;
|
||||
if (op2 == 0) {
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
if ((_voices[i].channel == channel) && _voices[i].isSustained)
|
||||
voiceOff(i);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x4b:
|
||||
#ifndef ADLIB_DISABLE_VOICE_MAPPING
|
||||
voiceMapping(channel, op2);
|
||||
#endif
|
||||
break;
|
||||
case 0x4e:
|
||||
_channels[channel].enableVelocity = op2;
|
||||
break;
|
||||
case SCI_MIDI_CHANNEL_NOTES_OFF:
|
||||
for (int i = 0; i < kVoices; i++)
|
||||
if ((_voices[i].channel == channel) && (_voices[i].note != -1))
|
||||
voiceOff(i);
|
||||
break;
|
||||
default:
|
||||
//warning("ADLIB: ignoring MIDI command %02x %02x %02x", command | channel, op1, op2);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 0xc0:
|
||||
_channels[channel].patch = op1;
|
||||
break;
|
||||
// The original AdLib driver from sierra ignores aftertouch completely, so should we
|
||||
case 0xa0: // Polyphonic key pressure (aftertouch)
|
||||
case 0xd0: // Channel pressure (aftertouch)
|
||||
break;
|
||||
case 0xe0:
|
||||
_channels[channel].pitchWheel = (op1 & 0x7f) | ((op2 & 0x7f) << 7);
|
||||
renewNotes(channel, true);
|
||||
break;
|
||||
default:
|
||||
warning("ADLIB: Unknown event %02x", command);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::initTrack(SciSpan<const byte> &header) {
|
||||
if (!_isOpen || !_isSCI0)
|
||||
return;
|
||||
|
||||
uint8 readPos = 0;
|
||||
uint8 caps = header.getInt8At(readPos++);
|
||||
if (caps != 0 && (_version == SCI_VERSION_0_EARLY || caps != 2))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < kVoices; ++i) {
|
||||
_voices[i].channel = _voices[i].mappedChannel = _voices[i].note = -1;
|
||||
_voices[i].isSustained = false;
|
||||
_voices[i].patch = 13;
|
||||
_voices[i].velocity = 0;
|
||||
_voices[i].age = 0;
|
||||
}
|
||||
|
||||
int numVoices = 0;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
_channels[i].patch = 13;
|
||||
_channels[i].extraVoices = 0;
|
||||
_channels[i].mappedVoices = 0;
|
||||
|
||||
if (_version == SCI_VERSION_0_LATE) {
|
||||
uint8 num = header.getInt8At(readPos++) & 0x7F;
|
||||
uint8 flags = header.getInt8At(readPos++);
|
||||
if ((flags & 0x04) && num)
|
||||
assignVoices(i, num);
|
||||
} else {
|
||||
uint8 val = header.getInt8At(readPos++);
|
||||
if (val & 0x01) {
|
||||
uint8 num = val >> 4;
|
||||
if (!(val & 0x08) && num && num != 0x0F) {
|
||||
while (num--) {
|
||||
if (numVoices >= _numVoiceMax)
|
||||
continue;
|
||||
_voices[numVoices++].mappedChannel = i;
|
||||
_channels[i].mappedVoices++;
|
||||
}
|
||||
}
|
||||
} else if (val & 0x08) {
|
||||
debugC(9, kDebugLevelSound, "MidiDriver_AdLib::initTrack(): Control channel found: 0x%.02x", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
|
||||
_adlibTimerProc = timerProc;
|
||||
_adlibTimerParam = timerParam;
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::onTimer() {
|
||||
if (_adlibTimerProc)
|
||||
(*_adlibTimerProc)(_adlibTimerParam);
|
||||
|
||||
// Increase the age of the notes
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
if (_voices[i].note != -1)
|
||||
_voices[i].age++;
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::loadInstrument(const SciSpan<const byte> &ins) {
|
||||
AdLibPatch patch;
|
||||
|
||||
// Set data for the operators
|
||||
for (int i = 0; i < 2; i++) {
|
||||
const byte *op = ins.getUnsafeDataAt(i * 13, 13);
|
||||
patch.op[i].kbScaleLevel = op[0] & 0x3;
|
||||
patch.op[i].frequencyMult = op[1] & 0xf;
|
||||
patch.op[i].attackRate = op[3] & 0xf;
|
||||
patch.op[i].sustainLevel = op[4] & 0xf;
|
||||
patch.op[i].envelopeType = op[5];
|
||||
patch.op[i].decayRate = op[6] & 0xf;
|
||||
patch.op[i].releaseRate = op[7] & 0xf;
|
||||
patch.op[i].totalLevel = op[8] & 0x3f;
|
||||
patch.op[i].amplitudeMod = op[9];
|
||||
patch.op[i].vibrato = op[10];
|
||||
patch.op[i].kbScaleRate = op[11];
|
||||
}
|
||||
patch.op[0].waveForm = ins[26] & 0x3;
|
||||
patch.op[1].waveForm = ins[27] & 0x3;
|
||||
|
||||
// Set data for the modulator
|
||||
patch.mod.feedback = ins[2] & 0x7;
|
||||
patch.mod.algorithm = !ins[12]; // Flag is inverted
|
||||
|
||||
_patches.push_back(patch);
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::voiceMapping(int channel, int voices) {
|
||||
int curVoices = 0;
|
||||
|
||||
for (int i = 0; i < _numVoiceMax; i++)
|
||||
if (_voices[i].mappedChannel == channel)
|
||||
curVoices++;
|
||||
|
||||
curVoices += _channels[channel].extraVoices;
|
||||
|
||||
if (curVoices < voices) {
|
||||
debug(3, "ADLIB: assigning %i additional voices to channel %i", voices - curVoices, channel);
|
||||
assignVoices(channel, voices - curVoices);
|
||||
} else if (curVoices > voices) {
|
||||
debug(3, "ADLIB: releasing %i voices from channel %i", curVoices - voices, channel);
|
||||
releaseVoices(channel, curVoices - voices);
|
||||
donateVoices();
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::assignVoices(int channel, int voices) {
|
||||
assert(voices > 0);
|
||||
|
||||
for (int i = 0; i < _numVoiceMax; i++)
|
||||
if (_voices[i].mappedChannel == -1) {
|
||||
if (_voices[i].note != -1) // Late SCI1.1, stop note on unmapped channel
|
||||
voiceOff(i);
|
||||
_voices[i].mappedChannel = channel;
|
||||
++_channels[channel].mappedVoices;
|
||||
if (--voices == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
// This is already too advanced for SCI0...
|
||||
if (!_isSCI0)
|
||||
_channels[channel].extraVoices += voices;
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::releaseVoices(int channel, int voices) {
|
||||
if (_channels[channel].extraVoices >= voices) {
|
||||
_channels[channel].extraVoices -= voices;
|
||||
return;
|
||||
}
|
||||
|
||||
voices -= _channels[channel].extraVoices;
|
||||
_channels[channel].extraVoices = 0;
|
||||
|
||||
for (int i = 0; i < _numVoiceMax; i++) {
|
||||
if ((_voices[i].mappedChannel == channel) && (_voices[i].note == -1)) {
|
||||
_voices[i].mappedChannel = -1;
|
||||
--_channels[channel].mappedVoices;
|
||||
if (--voices == 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _numVoiceMax; i++) {
|
||||
if (_voices[i].mappedChannel == channel) {
|
||||
voiceOff(i);
|
||||
_voices[i].mappedChannel = -1;
|
||||
--_channels[channel].mappedVoices;
|
||||
if (--voices == 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::donateVoices() {
|
||||
if (_isSCI0)
|
||||
return;
|
||||
|
||||
int freeVoices = 0;
|
||||
|
||||
for (int i = 0; i < kVoices; i++)
|
||||
if (_voices[i].mappedChannel == -1)
|
||||
freeVoices++;
|
||||
|
||||
if (freeVoices == 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < MIDI_CHANNELS; i++) {
|
||||
if (_channels[i].extraVoices >= freeVoices) {
|
||||
assignVoices(i, freeVoices);
|
||||
_channels[i].extraVoices -= freeVoices;
|
||||
return;
|
||||
} else if (_channels[i].extraVoices > 0) {
|
||||
assignVoices(i, _channels[i].extraVoices);
|
||||
freeVoices -= _channels[i].extraVoices;
|
||||
_channels[i].extraVoices = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::renewNotes(int channel, bool key) {
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
// Update all notes playing this channel
|
||||
if ((channel == -1) || (_voices[i].channel == channel)) {
|
||||
if (_voices[i].note != -1)
|
||||
setNote(i, _voices[i].note, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::noteOn(int channel, int note, int velocity) {
|
||||
if (velocity == 0)
|
||||
return noteOff(channel, note);
|
||||
|
||||
velocity >>= 1;
|
||||
|
||||
// Check for playable notes
|
||||
if ((note < 12) || (note > 107))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
if ((_voices[i].channel == channel) && (_voices[i].note == note)) {
|
||||
voiceOff(i);
|
||||
voiceOn(i, note, velocity);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int voice = _rhythmKeyMap ? findVoiceLateSci11(channel) : findVoice(channel);
|
||||
|
||||
if (voice == -1) {
|
||||
debug(3, "ADLIB: failed to find free voice assigned to channel %i", channel);
|
||||
return;
|
||||
}
|
||||
|
||||
voiceOn(voice, note, velocity);
|
||||
}
|
||||
|
||||
int MidiDriver_AdLib::findVoice(int channel) {
|
||||
int voice = -1;
|
||||
int oldestVoice = -1;
|
||||
uint32 oldestAge = 0;
|
||||
|
||||
// Try to find a voice assigned to this channel that is free (round-robin)
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
int v = (_channels[channel].lastVoice + i + 1) % kVoices;
|
||||
|
||||
if (_voices[v].mappedChannel == channel) {
|
||||
if (_voices[v].note == -1) {
|
||||
voice = v;
|
||||
_voices[voice].channel = channel;
|
||||
break;
|
||||
}
|
||||
|
||||
// We also keep track of the oldest note in case the search fails
|
||||
// Notes started in the current time slice will not be selected
|
||||
if (_voices[v].age >= oldestAge) {
|
||||
oldestAge = _voices[v].age;
|
||||
oldestVoice = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (voice == -1) {
|
||||
if (!oldestAge)
|
||||
return -1;
|
||||
voiceOff(oldestVoice);
|
||||
voice = oldestVoice;
|
||||
_voices[voice].channel = channel;
|
||||
}
|
||||
|
||||
_channels[channel].lastVoice = voice;
|
||||
|
||||
return voice;
|
||||
}
|
||||
|
||||
int MidiDriver_AdLib::findVoiceLateSci11(int channel) {
|
||||
Common::List<int>::const_iterator it;
|
||||
|
||||
// Search for unused voice
|
||||
for (it = _voiceQueue.begin(); it != _voiceQueue.end(); ++it) {
|
||||
int voice = *it;
|
||||
if (_voices[voice].note == -1 && _voices[voice].patch == _channels[channel].patch) {
|
||||
_voices[voice].channel = channel;
|
||||
return voice;
|
||||
}
|
||||
}
|
||||
|
||||
// Same as before, minus the program check
|
||||
for (it = _voiceQueue.begin(); it != _voiceQueue.end(); ++it) {
|
||||
int voice = *it;
|
||||
if (_voices[voice].note == -1) {
|
||||
_voices[voice].channel = channel;
|
||||
return voice;
|
||||
}
|
||||
}
|
||||
|
||||
// Search for channel with highest excess of voices
|
||||
int maxExceed = 0;
|
||||
int maxExceedChan = 0;
|
||||
for (uint i = 0; i < MIDI_CHANNELS; ++i) {
|
||||
if (_channels[i].voices > _channels[i].mappedVoices) {
|
||||
int exceed = _channels[i].voices - _channels[i].mappedVoices;
|
||||
if (exceed > maxExceed) {
|
||||
maxExceed = exceed;
|
||||
maxExceedChan = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop voice on channel with highest excess if possible, otherwise stop
|
||||
// note on this channel.
|
||||
int stopChan = (maxExceed > 0) ? maxExceedChan : channel;
|
||||
|
||||
for (it = _voiceQueue.begin(); it != _voiceQueue.end(); ++it) {
|
||||
int voice = *it;
|
||||
if (_voices[voice].channel == stopChan) {
|
||||
voiceOff(voice);
|
||||
_voices[voice].channel = channel;
|
||||
return voice;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::queueMoveToBack(int voice) {
|
||||
_voiceQueue.remove(voice);
|
||||
_voiceQueue.push_back(voice);
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::noteOff(int channel, int note) {
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
if ((_voices[i].channel == channel) && (_voices[i].note == note)) {
|
||||
if (_channels[channel].holdPedal)
|
||||
_voices[i].isSustained = true;
|
||||
else
|
||||
voiceOff(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::voiceOn(int voice, int note, int velocity) {
|
||||
int channel = _voices[voice].channel;
|
||||
int patch = _channels[channel].patch;
|
||||
|
||||
_voices[voice].age = 0;
|
||||
++_channels[channel].voices;
|
||||
queueMoveToBack(voice);
|
||||
|
||||
if ((channel == 9) && _rhythmKeyMap) {
|
||||
patch = CLIP(note, 27, 88) + 101;
|
||||
}
|
||||
|
||||
// Set patch if different from current patch
|
||||
if (patch != _voices[voice].patch && _playSwitch)
|
||||
setPatch(voice, patch);
|
||||
|
||||
_voices[voice].velocity = velocity;
|
||||
setNote(voice, note, true);
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::voiceOff(int voice) {
|
||||
int channel = _voices[voice].channel;
|
||||
|
||||
_voices[voice].isSustained = false;
|
||||
setNote(voice, _voices[voice].note, 0);
|
||||
_voices[voice].note = -1;
|
||||
_voices[voice].age = 0;
|
||||
queueMoveToBack(voice);
|
||||
--_channels[channel].voices;
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::setNote(int voice, int note, bool key) {
|
||||
int channel = _voices[voice].channel;
|
||||
|
||||
if ((channel == 9) && _rhythmKeyMap)
|
||||
note = _rhythmKeyMap[CLIP(note, 27, 88) - 27];
|
||||
|
||||
_voices[voice].note = note;
|
||||
|
||||
int index = note << 2;
|
||||
uint16 pitchWheel = _channels[channel].pitchWheel;
|
||||
int sign;
|
||||
|
||||
if (pitchWheel == 0x2000) {
|
||||
pitchWheel = 0;
|
||||
sign = 0;
|
||||
} else if (pitchWheel > 0x2000) {
|
||||
pitchWheel -= 0x2000;
|
||||
sign = 1;
|
||||
} else {
|
||||
pitchWheel = 0x2000 - pitchWheel;
|
||||
sign = -1;
|
||||
}
|
||||
|
||||
pitchWheel /= 171;
|
||||
|
||||
if (sign == 1)
|
||||
index += pitchWheel;
|
||||
else
|
||||
index -= pitchWheel;
|
||||
|
||||
if (index > 0x1fc) // Limit to max MIDI note (<< 2)
|
||||
index = 0x1fc;
|
||||
|
||||
if (index < 0) // Not in SSCI
|
||||
index = 0;
|
||||
|
||||
int freq = adlibFreq[index % 48];
|
||||
|
||||
setRegister(0xA0 + voice, freq & 0xff);
|
||||
|
||||
int oct = index / 48;
|
||||
if (oct > 0)
|
||||
--oct;
|
||||
|
||||
if (oct > 7) // Not in SSCI
|
||||
oct = 7;
|
||||
|
||||
setRegister(0xB0 + voice, (key << 5) | (oct << 2) | (freq >> 8));
|
||||
setVelocity(voice);
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::setVelocity(int voice) {
|
||||
AdLibPatch &patch = _patches[_voices[voice].patch];
|
||||
int pan = _channels[_voices[voice].channel].pan;
|
||||
setVelocityReg(registerOffset[voice] + 3, calcVelocity(voice, 1), patch.op[1].kbScaleLevel, pan);
|
||||
|
||||
// In AM mode we need to set the level for both operators
|
||||
if (_patches[_voices[voice].patch].mod.algorithm == 1)
|
||||
setVelocityReg(registerOffset[voice], calcVelocity(voice, 0), patch.op[0].kbScaleLevel, pan);
|
||||
}
|
||||
|
||||
int MidiDriver_AdLib::calcVelocity(int voice, int op) {
|
||||
if (_isSCI0) {
|
||||
int velocity = _masterVolume;
|
||||
|
||||
if (velocity > 0)
|
||||
velocity += 3;
|
||||
|
||||
if (velocity > 15)
|
||||
velocity = 15;
|
||||
|
||||
int insVelocity;
|
||||
if (_channels[_voices[voice].channel].enableVelocity)
|
||||
insVelocity = _voices[voice].velocity;
|
||||
else
|
||||
insVelocity = 63 - _patches[_voices[voice].patch].op[op].totalLevel;
|
||||
|
||||
// Note: Later SCI0 has a static table that is close to this formula, but not exactly the same.
|
||||
// Early SCI0 does (velocity * (insVelocity / 15))
|
||||
return velocity * insVelocity / 15;
|
||||
} else {
|
||||
AdLibOperator &oper = _patches[_voices[voice].patch].op[op];
|
||||
int velocity = _channels[_voices[voice].channel].volume + 1;
|
||||
velocity = velocity * (velocityMap1[_voices[voice].velocity] + 1) / 64;
|
||||
velocity = velocity * (_masterVolume + 1) / 16;
|
||||
|
||||
if (--velocity < 0)
|
||||
velocity = 0;
|
||||
|
||||
return velocityMap2[velocity] * (63 - oper.totalLevel) / 63;
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::setVelocityReg(int regOffset, int velocity, int kbScaleLevel, int pan) {
|
||||
if (!_playSwitch)
|
||||
velocity = 0;
|
||||
|
||||
if (_stereo) {
|
||||
int velLeft = velocity;
|
||||
int velRight = velocity;
|
||||
|
||||
if (pan > 0x40)
|
||||
velLeft = velLeft * (0x7f - pan) / 0x3f;
|
||||
else if (pan < 0x40)
|
||||
velRight = velRight * pan / 0x40;
|
||||
|
||||
setRegister(0x40 + regOffset, (kbScaleLevel << 6) | (63 - velLeft), kLeftChannel);
|
||||
setRegister(0x40 + regOffset, (kbScaleLevel << 6) | (63 - velRight), kRightChannel);
|
||||
} else {
|
||||
setRegister(0x40 + regOffset, (kbScaleLevel << 6) | (63 - velocity));
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::setPatch(int voice, int patch) {
|
||||
if ((patch < 0) || ((uint)patch >= _patches.size())) {
|
||||
warning("ADLIB: Invalid patch %i requested", patch);
|
||||
// Substitute instrument 0
|
||||
patch = 0;
|
||||
}
|
||||
|
||||
_voices[voice].patch = patch;
|
||||
AdLibModulator &mod = _patches[patch].mod;
|
||||
|
||||
// Set the common settings for both operators
|
||||
setOperator(registerOffset[voice], _patches[patch].op[0]);
|
||||
setOperator(registerOffset[voice] + 3, _patches[patch].op[1]);
|
||||
|
||||
// Set the additional settings for the modulator
|
||||
byte algorithm = mod.algorithm ? 1 : 0;
|
||||
setRegister(0xC0 + voice, (mod.feedback << 1) | algorithm);
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::setOperator(int reg, AdLibOperator &op) {
|
||||
setRegister(0x40 + reg, (op.kbScaleLevel << 6) | op.totalLevel);
|
||||
setRegister(0x60 + reg, (op.attackRate << 4) | op.decayRate);
|
||||
setRegister(0x80 + reg, (op.sustainLevel << 4) | op.releaseRate);
|
||||
setRegister(0x20 + reg, (op.amplitudeMod << 7) | (op.vibrato << 6)
|
||||
| (op.envelopeType << 5) | (op.kbScaleRate << 4) | op.frequencyMult);
|
||||
setRegister(0xE0 + reg, op.waveForm);
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::setRegister(int reg, int value, int channels) {
|
||||
if (channels & kLeftChannel) {
|
||||
_opl->write(0x220, reg);
|
||||
_opl->write(0x221, value);
|
||||
}
|
||||
|
||||
if (_stereo) {
|
||||
if (channels & kRightChannel) {
|
||||
_opl->write(0x222, reg);
|
||||
_opl->write(0x223, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::playSwitch(bool play) {
|
||||
_playSwitch = play;
|
||||
renewNotes(-1, play);
|
||||
}
|
||||
|
||||
bool MidiDriver_AdLib::loadResource(const SciSpan<const byte> &data) {
|
||||
const uint32 size = data.size();
|
||||
if (size != 1344 && size != 2690 && size != 5382) {
|
||||
error("ADLIB: Unsupported patch format (%u bytes)", size);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 48; i++)
|
||||
loadInstrument(data.subspan(28 * i));
|
||||
|
||||
if (size == 1344) {
|
||||
byte dummy[28] = {0};
|
||||
|
||||
// Only 48 instruments, add dummies
|
||||
for (int i = 0; i < 48; i++)
|
||||
loadInstrument(SciSpan<const byte>(dummy, sizeof(dummy)));
|
||||
} else if (size == 2690) {
|
||||
for (int i = 48; i < 96; i++)
|
||||
loadInstrument(data.subspan(2 + (28 * i)));
|
||||
} else {
|
||||
// SCI1.1 and later
|
||||
for (int i = 48; i < 190; i++) {
|
||||
loadInstrument(data.subspan(28 * i));
|
||||
}
|
||||
|
||||
_rhythmKeyMap->allocateFromSpan(data.subspan(5320, kRhythmKeys));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 MidiDriver_AdLib::property(int prop, uint32 param) {
|
||||
switch(prop) {
|
||||
case MIDI_PROP_MASTER_VOLUME:
|
||||
if (param != 0xffff)
|
||||
_masterVolume = param;
|
||||
return _masterVolume;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int MidiPlayer_AdLib::open(ResourceManager *resMan) {
|
||||
assert(resMan != nullptr);
|
||||
|
||||
// Load up the patch.003 file, parse out the instruments
|
||||
Resource *res = resMan->findResource(ResourceId(kResourceTypePatch, 3), false);
|
||||
bool ok = false;
|
||||
|
||||
if (res) {
|
||||
ok = static_cast<MidiDriver_AdLib *>(_driver)->loadResource(*res);
|
||||
} else {
|
||||
// Early SCI0 games have the sound bank embedded in the AdLib driver
|
||||
|
||||
Common::File f;
|
||||
|
||||
if (f.open("ADL.DRV")) {
|
||||
int size = f.size();
|
||||
const uint patchSize = 1344;
|
||||
|
||||
// Note: Funseeker's Guide also has another version of adl.drv, 8803 bytes.
|
||||
// This isn't supported, but it's not really used anywhere, as that demo
|
||||
// doesn't have sound anyway.
|
||||
if (size == 5684 || size == 5720 || size == 5727) {
|
||||
ok = f.seek(0x45a);
|
||||
if (ok) {
|
||||
Common::SpanOwner<SciSpan<const byte> > patchData;
|
||||
patchData->allocateFromStream(f, patchSize);
|
||||
ok = static_cast<MidiDriver_AdLib *>(_driver)->loadResource(*patchData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
warning("ADLIB: Failed to load patch.003");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return static_cast<MidiDriver_AdLib *>(_driver)->openAdLib();
|
||||
}
|
||||
|
||||
void MidiPlayer_AdLib::close() {
|
||||
if (_driver) {
|
||||
_driver->close();
|
||||
}
|
||||
}
|
||||
|
||||
byte MidiPlayer_AdLib::getPlayId() const {
|
||||
switch (_version) {
|
||||
case SCI_VERSION_0_EARLY:
|
||||
return 0x09;
|
||||
case SCI_VERSION_0_LATE:
|
||||
return 0x04;
|
||||
default:
|
||||
return 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
MidiPlayer *MidiPlayer_AdLib_create(SciVersion _soundVersion) {
|
||||
return new MidiPlayer_AdLib(_soundVersion);
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
930
engines/sci/sound/drivers/amigamac0.cpp
Normal file
930
engines/sci/sound/drivers/amigamac0.cpp
Normal file
@@ -0,0 +1,930 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// TODO: Original Mac driver allows the interpreter to play notes. Channel
|
||||
// is allocated via controller 0x4D. Find out if this is used. This would
|
||||
// allow for music and sfx to be played simultaneously.
|
||||
|
||||
// FIXME: SQ3, LSL2 and HOYLE1 for Amiga don't seem to load any
|
||||
// patches, even though patches are present. Later games do load
|
||||
// patches, but include disabled patches with a 'd' appended to the
|
||||
// filename, e.g. sound.010d. For SQ3, LSL2 and HOYLE1, we should
|
||||
// probably disable patch loading. Maybe the original interpreter
|
||||
// loads these disabled patches under some specific condition?
|
||||
|
||||
#include "sci/sound/drivers/mididriver.h"
|
||||
#include "sci/sound/drivers/macmixer.h"
|
||||
#include "sci/resource/resource.h"
|
||||
|
||||
#include "common/file.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/mutex.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/util.h"
|
||||
#include "audio/mods/paula.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
class MidiPlayer_AmigaMac0 : public MidiPlayer {
|
||||
public:
|
||||
enum {
|
||||
kVoices = 4,
|
||||
kBaseFreq = 60
|
||||
};
|
||||
|
||||
MidiPlayer_AmigaMac0(SciVersion version, Audio::Mixer *mixer, Common::Mutex &mutex);
|
||||
virtual ~MidiPlayer_AmigaMac0();
|
||||
|
||||
// MidiPlayer
|
||||
void close() override;
|
||||
void send(uint32 b) override;
|
||||
void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) override;
|
||||
uint32 getBaseTempo() override { return (1000000 + kBaseFreq / 2) / kBaseFreq; }
|
||||
byte getPlayId() const override { return 0x40; }
|
||||
int getPolyphony() const override { return kVoices; }
|
||||
bool hasRhythmChannel() const override { return false; }
|
||||
void setVolume(byte volume) override;
|
||||
int getVolume() override;
|
||||
void playSwitch(bool play) override;
|
||||
void initTrack(SciSpan<const byte> &trackData) override;
|
||||
|
||||
protected:
|
||||
bool _playSwitch;
|
||||
uint _masterVolume;
|
||||
|
||||
Audio::Mixer *_mixer;
|
||||
Audio::SoundHandle _mixerSoundHandle;
|
||||
Common::TimerManager::TimerProc _timerProc;
|
||||
void *_timerParam;
|
||||
bool _isOpen;
|
||||
|
||||
void freeInstruments();
|
||||
void onTimer();
|
||||
|
||||
struct Instrument {
|
||||
Instrument() :
|
||||
name(),
|
||||
loop(false),
|
||||
fixedNote(false),
|
||||
seg2Offset(0),
|
||||
seg3Offset(0),
|
||||
samples(nullptr),
|
||||
transpose(0),
|
||||
envelope() {}
|
||||
|
||||
~Instrument() { delete[] samples; }
|
||||
|
||||
char name[31];
|
||||
bool loop;
|
||||
bool fixedNote;
|
||||
uint32 seg2Offset;
|
||||
uint32 seg3Offset;
|
||||
const byte *samples;
|
||||
int16 transpose;
|
||||
|
||||
struct Envelope {
|
||||
byte skip;
|
||||
int8 step;
|
||||
byte target;
|
||||
} envelope[4];
|
||||
};
|
||||
|
||||
Common::Array<const Instrument *> _instruments;
|
||||
|
||||
class Voice {
|
||||
public:
|
||||
Voice(MidiPlayer_AmigaMac0 &driver, byte id) :
|
||||
_patch(0),
|
||||
_note(-1),
|
||||
_velocity(0),
|
||||
_pitch(0),
|
||||
_instrument(nullptr),
|
||||
_loop(false),
|
||||
_envState(0),
|
||||
_envCntDown(0),
|
||||
_envCurVel(0),
|
||||
_volume(0),
|
||||
_id(id),
|
||||
_driver(driver) {}
|
||||
|
||||
virtual ~Voice() {}
|
||||
|
||||
virtual void noteOn(int8 note, int8 velocity) = 0;
|
||||
virtual void noteOff(int8 note) = 0;
|
||||
virtual void setPitchWheel(int16 pitch) {}
|
||||
|
||||
virtual void stop() = 0;
|
||||
virtual void setEnvelopeVolume(byte volume) = 0;
|
||||
|
||||
void processEnvelope();
|
||||
|
||||
byte _patch;
|
||||
int8 _note;
|
||||
byte _velocity;
|
||||
uint16 _pitch;
|
||||
|
||||
const Instrument *_instrument;
|
||||
bool _loop;
|
||||
|
||||
byte _envState;
|
||||
byte _envCntDown;
|
||||
int8 _envCurVel;
|
||||
|
||||
byte _volume;
|
||||
byte _id;
|
||||
|
||||
private:
|
||||
MidiPlayer_AmigaMac0 &_driver;
|
||||
};
|
||||
|
||||
Common::Array<Voice *> _voices;
|
||||
typedef Common::Array<Voice *>::const_iterator VoiceIt;
|
||||
|
||||
Voice *_channels[MIDI_CHANNELS];
|
||||
|
||||
Common::Mutex &_mixMutex;
|
||||
Common::Mutex _timerMutex;
|
||||
};
|
||||
|
||||
MidiPlayer_AmigaMac0::MidiPlayer_AmigaMac0(SciVersion version, Audio::Mixer *mixer, Common::Mutex &mutex) :
|
||||
MidiPlayer(version),
|
||||
_playSwitch(true),
|
||||
_masterVolume(15),
|
||||
_mixer(mixer),
|
||||
_mixerSoundHandle(),
|
||||
_timerProc(),
|
||||
_timerParam(nullptr),
|
||||
_isOpen(false),
|
||||
_channels(),
|
||||
_mixMutex(mutex) {}
|
||||
|
||||
MidiPlayer_AmigaMac0::~MidiPlayer_AmigaMac0() {
|
||||
close();
|
||||
}
|
||||
|
||||
void MidiPlayer_AmigaMac0::close() {
|
||||
if (!_isOpen)
|
||||
return;
|
||||
|
||||
_mixer->stopHandle(_mixerSoundHandle);
|
||||
|
||||
for (uint ci = 0; ci < ARRAYSIZE(_channels); ++ci)
|
||||
_channels[ci] = nullptr;
|
||||
|
||||
for (VoiceIt v = _voices.begin(); v != _voices.end(); ++v)
|
||||
delete *v;
|
||||
_voices.clear();
|
||||
|
||||
freeInstruments();
|
||||
|
||||
_isOpen = false;
|
||||
}
|
||||
|
||||
void MidiPlayer_AmigaMac0::setVolume(byte volume) {
|
||||
Common::StackLock lock(_mixMutex);
|
||||
_masterVolume = CLIP<byte>(volume, 0, 15);
|
||||
}
|
||||
|
||||
int MidiPlayer_AmigaMac0::getVolume() {
|
||||
Common::StackLock lock(_mixMutex);
|
||||
return _masterVolume;
|
||||
}
|
||||
|
||||
void MidiPlayer_AmigaMac0::playSwitch(bool play) {
|
||||
Common::StackLock lock(_mixMutex);
|
||||
_playSwitch = play;
|
||||
}
|
||||
|
||||
void MidiPlayer_AmigaMac0::initTrack(SciSpan<const byte>& header) {
|
||||
if (!_isOpen)
|
||||
return;
|
||||
|
||||
uint8 readPos = 0;
|
||||
const uint8 caps = header.getInt8At(readPos++);
|
||||
|
||||
// We only implement the MIDI functionality here, samples are
|
||||
// handled by the generic sample code
|
||||
if (caps != 0)
|
||||
return;
|
||||
|
||||
Common::StackLock lock(_mixMutex);
|
||||
|
||||
uint vi = 0;
|
||||
|
||||
for (uint i = 0; i < 15; ++i) {
|
||||
readPos++;
|
||||
const uint8 flags = header.getInt8At(readPos++);
|
||||
|
||||
if ((flags & getPlayId()) && (vi < kVoices))
|
||||
_channels[i] = _voices[vi++];
|
||||
else
|
||||
_channels[i] = nullptr;
|
||||
}
|
||||
|
||||
_channels[15] = nullptr;
|
||||
|
||||
for (VoiceIt it = _voices.begin(); it != _voices.end(); ++it) {
|
||||
Voice *voice = *it;
|
||||
voice->stop();
|
||||
voice->_note = -1;
|
||||
voice->_envState = 0;
|
||||
voice->_pitch = 0x2000;
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_AmigaMac0::freeInstruments() {
|
||||
for (Common::Array<const Instrument *>::iterator it = _instruments.begin(); it != _instruments.end(); ++it)
|
||||
delete *it;
|
||||
|
||||
_instruments.clear();
|
||||
}
|
||||
|
||||
void MidiPlayer_AmigaMac0::onTimer() {
|
||||
_mixMutex.unlock();
|
||||
_timerMutex.lock();
|
||||
|
||||
if (_timerProc)
|
||||
(*_timerProc)(_timerParam);
|
||||
|
||||
_timerMutex.unlock();
|
||||
_mixMutex.lock();
|
||||
|
||||
for (VoiceIt it = _voices.begin(); it != _voices.end(); ++it)
|
||||
(*it)->processEnvelope();
|
||||
}
|
||||
|
||||
void MidiPlayer_AmigaMac0::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
|
||||
Common::StackLock lock(_timerMutex);
|
||||
_timerProc = timerProc;
|
||||
_timerParam = timerParam;
|
||||
}
|
||||
|
||||
void MidiPlayer_AmigaMac0::send(uint32 b) {
|
||||
Common::StackLock lock(_mixMutex);
|
||||
|
||||
byte command = b & 0xf0;
|
||||
byte channel = b & 0xf;
|
||||
byte op1 = (b >> 8) & 0xff;
|
||||
byte op2 = (b >> 16) & 0xff;
|
||||
|
||||
Voice *voice = _channels[channel];
|
||||
|
||||
if (!voice)
|
||||
return;
|
||||
|
||||
switch(command) {
|
||||
case 0x80:
|
||||
voice->noteOff(op1);
|
||||
break;
|
||||
case 0x90:
|
||||
voice->noteOn(op1, op2);
|
||||
break;
|
||||
case 0xb0:
|
||||
// Not in original driver
|
||||
if (op1 == 0x7b && voice->_note != -1 && voice->_envState < 4)
|
||||
voice->noteOff(voice->_note);
|
||||
break;
|
||||
case 0xc0:
|
||||
voice->_patch = op1;
|
||||
break;
|
||||
case 0xe0:
|
||||
voice->setPitchWheel((op2 << 7) | op1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_AmigaMac0::Voice::processEnvelope() {
|
||||
if (_envState == 0 || _envState == 3)
|
||||
return;
|
||||
|
||||
if (_envState == 6) {
|
||||
stop();
|
||||
_envState = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_envCntDown == 0) {
|
||||
const uint envIdx = (_envState > 3 ? _envState - 2 : _envState - 1);
|
||||
|
||||
_envCntDown = _instrument->envelope[envIdx].skip;
|
||||
int8 velocity = _envCurVel;
|
||||
|
||||
if (velocity <= 0) {
|
||||
stop();
|
||||
_envState = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (velocity > 63)
|
||||
velocity = 63;
|
||||
|
||||
if (!_driver._playSwitch)
|
||||
velocity = 0;
|
||||
|
||||
setEnvelopeVolume(velocity);
|
||||
|
||||
const int8 step = _instrument->envelope[envIdx].step;
|
||||
if (step < 0) {
|
||||
_envCurVel -= step;
|
||||
if (_envCurVel > _instrument->envelope[envIdx].target) {
|
||||
_envCurVel = _instrument->envelope[envIdx].target;
|
||||
++_envState;
|
||||
}
|
||||
} else {
|
||||
_envCurVel -= step;
|
||||
if (_envCurVel < _instrument->envelope[envIdx].target) {
|
||||
_envCurVel = _instrument->envelope[envIdx].target;
|
||||
++_envState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
--_envCntDown;
|
||||
}
|
||||
|
||||
class MidiPlayer_Mac0 : public Mixer_Mac<MidiPlayer_Mac0>, public MidiPlayer_AmigaMac0 {
|
||||
public:
|
||||
MidiPlayer_Mac0(SciVersion version, Audio::Mixer *mixer, Mode mode);
|
||||
|
||||
// MidiPlayer
|
||||
int open(ResourceManager *resMan) override;
|
||||
void setVolume(byte volume) override;
|
||||
|
||||
// MidiDriver
|
||||
void close() override;
|
||||
|
||||
// Mixer_Mac
|
||||
static int8 applyChannelVolume(byte volume, byte sample);
|
||||
void interrupt() { onTimer(); }
|
||||
void onChannelFinished(uint channel);
|
||||
|
||||
private:
|
||||
enum {
|
||||
kStepTableSize = 84
|
||||
};
|
||||
|
||||
template <Mode mode>
|
||||
void generateSamples(int16 *buf, int len);
|
||||
|
||||
struct MacInstrument : public Instrument {
|
||||
MacInstrument() :
|
||||
Instrument(),
|
||||
endOffset(0) {}
|
||||
|
||||
uint32 endOffset;
|
||||
};
|
||||
|
||||
class MacVoice : public Voice {
|
||||
public:
|
||||
MacVoice(MidiPlayer_Mac0 &driver, byte id) :
|
||||
Voice(driver, id),
|
||||
_macDriver(driver) {}
|
||||
|
||||
private:
|
||||
void noteOn(int8 note, int8 velocity) override;
|
||||
void noteOff(int8 note) override;
|
||||
|
||||
void stop() override;
|
||||
void setEnvelopeVolume(byte volume) override;
|
||||
|
||||
void calcVoiceStep();
|
||||
|
||||
MidiPlayer_Mac0 &_macDriver;
|
||||
};
|
||||
|
||||
bool loadInstruments(Common::SeekableReadStream &patch);
|
||||
|
||||
ufrac_t _stepTable[kStepTableSize];
|
||||
};
|
||||
|
||||
MidiPlayer_Mac0::MidiPlayer_Mac0(SciVersion version, Audio::Mixer *mixer, Mixer_Mac<MidiPlayer_Mac0>::Mode mode) :
|
||||
Mixer_Mac<MidiPlayer_Mac0>(mode),
|
||||
MidiPlayer_AmigaMac0(version, mixer, _mutex) {
|
||||
|
||||
for (uint i = 0; i < kStepTableSize; ++i)
|
||||
_stepTable[i] = round(0x2000 * pow(2.0, i / 12.0));
|
||||
}
|
||||
|
||||
int MidiPlayer_Mac0::open(ResourceManager *resMan) {
|
||||
if (_isOpen)
|
||||
return MidiDriver::MERR_ALREADY_OPEN;
|
||||
|
||||
Resource *patch = g_sci->getResMan()->findResource(ResourceId(kResourceTypePatch, 200), false);
|
||||
if (!patch) {
|
||||
warning("MidiPlayer_Mac0: Failed to open patch 200");
|
||||
return MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
Common::MemoryReadStream stream(patch->toStream());
|
||||
if (!loadInstruments(stream)) {
|
||||
freeInstruments();
|
||||
return MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
for (byte vi = 0; vi < kVoices; ++vi)
|
||||
_voices.push_back(new MacVoice(*this, vi));
|
||||
|
||||
startMixer();
|
||||
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO);
|
||||
|
||||
_isOpen = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MidiPlayer_Mac0::setVolume(byte volume) {
|
||||
MidiPlayer_AmigaMac0::setVolume(volume);
|
||||
setMixerVolume(volume / 2 + 1);
|
||||
}
|
||||
|
||||
void MidiPlayer_Mac0::close() {
|
||||
MidiPlayer_AmigaMac0::close();
|
||||
stopMixer();
|
||||
}
|
||||
|
||||
int8 MidiPlayer_Mac0::applyChannelVolume(byte volume, byte sample) {
|
||||
int8 signedSample = sample - 0x80;
|
||||
|
||||
if (volume == 0)
|
||||
return 0;
|
||||
|
||||
if (volume == 63)
|
||||
return signedSample;
|
||||
|
||||
if (signedSample >= 0)
|
||||
return (signedSample * volume + 32) / 64;
|
||||
else
|
||||
return ~((~signedSample * volume + 32) / 64);
|
||||
}
|
||||
|
||||
void MidiPlayer_Mac0::onChannelFinished(uint channel) {
|
||||
_voices[channel]->_envState = 0;
|
||||
}
|
||||
|
||||
void MidiPlayer_Mac0::MacVoice::stop() {
|
||||
_macDriver.resetChannel(_id);
|
||||
}
|
||||
|
||||
void MidiPlayer_Mac0::MacVoice::calcVoiceStep() {
|
||||
int8 note = _note;
|
||||
|
||||
if (_instrument->fixedNote)
|
||||
note = 72;
|
||||
|
||||
int16 index = note + _instrument->transpose;
|
||||
|
||||
index -= 24;
|
||||
|
||||
while (index < 0)
|
||||
index += 12;
|
||||
|
||||
while (index >= kStepTableSize)
|
||||
index -= 12;
|
||||
|
||||
_macDriver.setChannelStep(_id, _macDriver._stepTable[index]);
|
||||
}
|
||||
|
||||
void MidiPlayer_Mac0::MacVoice::setEnvelopeVolume(byte volume) {
|
||||
if (_macDriver._masterVolume == 0 || !_macDriver._playSwitch)
|
||||
volume = 0;
|
||||
_macDriver.setChannelVolume(_id, volume * _volume >> 6);
|
||||
}
|
||||
|
||||
void MidiPlayer_Mac0::MacVoice::noteOn(int8 note, int8 velocity) {
|
||||
if (velocity == 0) {
|
||||
noteOff(note);
|
||||
return;
|
||||
}
|
||||
|
||||
stop();
|
||||
_envState = 0;
|
||||
|
||||
if (!_macDriver._instruments[_patch]) // Not in original driver
|
||||
return;
|
||||
|
||||
_instrument = _macDriver._instruments[_patch];
|
||||
|
||||
_velocity = velocity;
|
||||
_volume = velocity >> 1;
|
||||
_envCurVel = 64;
|
||||
_envCntDown = 0;
|
||||
_loop = _instrument->loop;
|
||||
_note = note;
|
||||
|
||||
calcVoiceStep();
|
||||
|
||||
const MacInstrument *ins = static_cast<const MacInstrument *>(_instrument);
|
||||
|
||||
if (_loop) {
|
||||
_envState = 1;
|
||||
_macDriver.setChannelData(_id, ins->samples, 0, ins->seg3Offset, ins->seg3Offset - ins->seg2Offset);
|
||||
} else {
|
||||
_macDriver.setChannelData(_id, ins->samples, 0, ins->endOffset);
|
||||
}
|
||||
|
||||
setEnvelopeVolume(63);
|
||||
}
|
||||
|
||||
void MidiPlayer_Mac0::MacVoice::noteOff(int8 note) {
|
||||
if (_note == note) {
|
||||
if (_envState != 0) {
|
||||
_envState = 4;
|
||||
_envCntDown = 0;
|
||||
}
|
||||
// Original driver doesn't reset note anywhere that I could find,
|
||||
// but this seems like a good place to do that
|
||||
_note = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool MidiPlayer_Mac0::loadInstruments(Common::SeekableReadStream &patch) {
|
||||
char name[33];
|
||||
|
||||
if (patch.read(name, 8) < 8 || strncmp(name, "X1iUo123", 8) != 0) {
|
||||
warning("MidiPlayer_Mac0: Incorrect ID string in patch bank");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (patch.read(name, 32) < 32) {
|
||||
warning("MidiPlayer_Mac0: Error reading patch bank");
|
||||
return false;
|
||||
}
|
||||
name[32] = 0;
|
||||
|
||||
debugC(kDebugLevelSound, "Bank: '%s'", name);
|
||||
|
||||
_instruments.resize(128);
|
||||
|
||||
for (byte i = 0; i < 128; i++) {
|
||||
patch.seek(40 + i * 4);
|
||||
uint32 offset = patch.readUint32BE();
|
||||
|
||||
if (offset == 0) {
|
||||
_instruments[i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
patch.seek(offset);
|
||||
|
||||
MacInstrument *instrument = new MacInstrument();
|
||||
_instruments[i] = instrument;
|
||||
|
||||
patch.readUint16BE(); // index
|
||||
|
||||
const uint16 flags = patch.readUint16BE();
|
||||
instrument->loop = flags & 1;
|
||||
instrument->fixedNote = !(flags & 2);
|
||||
|
||||
instrument->seg2Offset = patch.readUint32BE();
|
||||
instrument->seg3Offset = patch.readUint32BE();
|
||||
instrument->endOffset = patch.readUint32BE();
|
||||
|
||||
instrument->transpose = patch.readUint16BE();
|
||||
|
||||
for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
|
||||
instrument->envelope[stage].skip = patch.readByte();
|
||||
|
||||
for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
|
||||
instrument->envelope[stage].step = patch.readByte();
|
||||
|
||||
// In the original, it uses the stage 0 step as the stage 3 target,
|
||||
// but we (most likely) don't have to replicate this bug.
|
||||
for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
|
||||
instrument->envelope[stage].target = patch.readByte();
|
||||
|
||||
patch.read(instrument->name, 30);
|
||||
instrument->name[30] = 0;
|
||||
|
||||
debugC(kDebugLevelSound, "\tInstrument[%d]: '%s'", i, instrument->name);
|
||||
debugC(kDebugLevelSound, "\t\tSegment offsets: %d, %d, %d", instrument->seg2Offset, instrument->seg3Offset, instrument->endOffset);
|
||||
debugC(kDebugLevelSound, "\t\tTranspose = %d, Fixed note = %d, Loop = %d", instrument->transpose, instrument->fixedNote, instrument->loop);
|
||||
debugC(kDebugLevelSound, "\t\tEnvelope:");
|
||||
for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
|
||||
debugC(kDebugLevelSound, "\t\t\tStage %d: skip %d, step %d, target %d", stage, instrument->envelope[stage].skip, instrument->envelope[stage].step, instrument->envelope[stage].target);
|
||||
|
||||
uint32 sampleSize = (instrument->loop ? instrument->seg3Offset : instrument->endOffset) + 1111;
|
||||
byte *samples = new byte[sampleSize];
|
||||
patch.read(samples, sampleSize);
|
||||
instrument->samples = samples;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class MidiPlayer_Amiga0 : public Audio::Paula, public MidiPlayer_AmigaMac0 {
|
||||
public:
|
||||
MidiPlayer_Amiga0(SciVersion version, Audio::Mixer *mixer);
|
||||
|
||||
// MidiPlayer
|
||||
int open(ResourceManager *resMan) override;
|
||||
|
||||
// MidiDriver
|
||||
void close() override;
|
||||
|
||||
// Audio::Paula
|
||||
void interrupt() override { onTimer(); }
|
||||
|
||||
private:
|
||||
struct AmigaInstrument : public Instrument {
|
||||
AmigaInstrument() :
|
||||
Instrument(),
|
||||
seg1Size(0),
|
||||
seg2Size(0),
|
||||
seg3Size(0) {}
|
||||
|
||||
int16 seg1Size;
|
||||
int16 seg2Size;
|
||||
int16 seg3Size;
|
||||
};
|
||||
|
||||
class AmigaVoice : public Voice {
|
||||
public:
|
||||
AmigaVoice(MidiPlayer_Amiga0 &driver, uint id) :
|
||||
Voice(driver, id),
|
||||
_amigaDriver(driver) {}
|
||||
|
||||
private:
|
||||
void noteOn(int8 note, int8 velocity) override;
|
||||
void noteOff(int8 note) override;
|
||||
void setPitchWheel(int16 pitch) override;
|
||||
|
||||
void stop() override;
|
||||
void setEnvelopeVolume(byte volume) override;
|
||||
|
||||
void calcVoiceStep();
|
||||
|
||||
MidiPlayer_Amiga0 &_amigaDriver;
|
||||
};
|
||||
|
||||
uint _defaultInstrument;
|
||||
bool _isEarlyDriver;
|
||||
|
||||
bool loadInstruments(Common::SeekableReadStream &patch);
|
||||
|
||||
uint16 _periodTable[333];
|
||||
};
|
||||
|
||||
MidiPlayer_Amiga0::MidiPlayer_Amiga0(SciVersion version, Audio::Mixer *mixer) :
|
||||
Audio::Paula(true, mixer->getOutputRate(), mixer->getOutputRate() / kBaseFreq),
|
||||
MidiPlayer_AmigaMac0(version, mixer, _mutex),
|
||||
_defaultInstrument(0),
|
||||
_isEarlyDriver(false) {
|
||||
|
||||
// These values are close, but not identical to the original
|
||||
for (int i = 0; i < ARRAYSIZE(_periodTable); ++i)
|
||||
_periodTable[i] = 3579545 / 20000.0 / pow(2.0, (i - 308) / 48.0);
|
||||
}
|
||||
|
||||
void MidiPlayer_Amiga0::AmigaVoice::setEnvelopeVolume(byte volume) {
|
||||
// Early games ignore note velocity for envelope-enabled notes
|
||||
if (_amigaDriver._isEarlyDriver)
|
||||
_amigaDriver.setChannelVolume(_id, volume * _amigaDriver._masterVolume >> 4);
|
||||
else
|
||||
_amigaDriver.setChannelVolume(_id, (volume * _amigaDriver._masterVolume >> 4) * _volume >> 6);
|
||||
}
|
||||
|
||||
int MidiPlayer_Amiga0::open(ResourceManager *resMan) {
|
||||
if (_isOpen)
|
||||
return MidiDriver::MERR_ALREADY_OPEN;
|
||||
|
||||
_isEarlyDriver = g_sci->getGameId() == GID_LSL2 || g_sci->getGameId() == GID_SQ3;
|
||||
|
||||
Common::File file;
|
||||
|
||||
if (!file.open("bank.001")) {
|
||||
warning("MidiPlayer_Amiga0: Failed to open bank.001");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadInstruments(file)) {
|
||||
freeInstruments();
|
||||
return MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
for (byte vi = 0; vi < NUM_VOICES; ++vi)
|
||||
_voices.push_back(new AmigaVoice(*this, vi));
|
||||
|
||||
startPaula();
|
||||
// Enable reverse stereo to counteract Audio::Paula's reverse stereo
|
||||
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO, false, true);
|
||||
_isOpen = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MidiPlayer_Amiga0::close() {
|
||||
MidiPlayer_AmigaMac0::close();
|
||||
clearVoices();
|
||||
stopPaula();
|
||||
}
|
||||
|
||||
void MidiPlayer_Amiga0::AmigaVoice::stop() {
|
||||
_amigaDriver.clearVoice(_id);
|
||||
}
|
||||
|
||||
void MidiPlayer_Amiga0::AmigaVoice::calcVoiceStep() {
|
||||
int8 note = _note;
|
||||
|
||||
if (_instrument->fixedNote)
|
||||
note = 101;
|
||||
|
||||
int16 index = (note + _instrument->transpose) * 4;
|
||||
|
||||
if (_pitch >= 0x2000)
|
||||
index += (_pitch - 0x2000) / 171;
|
||||
else
|
||||
index -= (0x2000 - _pitch) / 171;
|
||||
|
||||
// For very high notes, the original driver reads out of bounds
|
||||
// (see e.g. SQ3 intro sequence). We compute the period for
|
||||
// these notes. The original hardware would not be able to
|
||||
// handle these very low periods, but Audio::Paula doesn't
|
||||
// seem to mind.
|
||||
|
||||
while (index < 96)
|
||||
index += 48;
|
||||
|
||||
index -= 96;
|
||||
|
||||
while (index >= ARRAYSIZE(_amigaDriver._periodTable))
|
||||
index -= 48;
|
||||
|
||||
_amigaDriver.setChannelPeriod(_id, _amigaDriver._periodTable[index]);
|
||||
}
|
||||
|
||||
void MidiPlayer_Amiga0::AmigaVoice::noteOn(int8 note, int8 velocity) {
|
||||
if (velocity == 0) {
|
||||
noteOff(note);
|
||||
return;
|
||||
}
|
||||
|
||||
_instrument = _amigaDriver._instruments[_patch];
|
||||
|
||||
// Default to the first instrument in the bank
|
||||
if (!_instrument)
|
||||
_instrument = _amigaDriver._instruments[_amigaDriver._defaultInstrument];
|
||||
|
||||
_velocity = velocity;
|
||||
_volume = velocity >> 1;
|
||||
_loop = _instrument->loop;
|
||||
_note = note;
|
||||
|
||||
stop();
|
||||
_envState = 0;
|
||||
|
||||
calcVoiceStep();
|
||||
|
||||
const AmigaInstrument *ins = static_cast<const AmigaInstrument *>(_instrument);
|
||||
|
||||
const int8 *seg1 = (const int8 *)ins->samples;
|
||||
const int8 *seg2 = seg1;
|
||||
int16 seg1Size = ins->seg1Size;
|
||||
seg2 += ins->seg2Offset & 0xfffe;
|
||||
int16 seg2Size = ins->seg2Size;
|
||||
|
||||
if (!_loop) {
|
||||
seg1Size = seg1Size + seg2Size + ins->seg3Size;
|
||||
seg2 = nullptr;
|
||||
seg2Size = 0;
|
||||
}
|
||||
|
||||
if (ins->envelope[0].skip != 0 && _loop) {
|
||||
_envCurVel = _volume;
|
||||
_envCntDown = 0;
|
||||
_envState = 1;
|
||||
}
|
||||
|
||||
_amigaDriver.setChannelData(_id, seg1, seg2, seg1Size * 2, seg2Size * 2);
|
||||
if (_amigaDriver._playSwitch)
|
||||
_amigaDriver.setChannelVolume(_id, _amigaDriver._masterVolume * _volume >> 4);
|
||||
}
|
||||
|
||||
void MidiPlayer_Amiga0::AmigaVoice::noteOff(int8 note) {
|
||||
if (_note == note) {
|
||||
if (_envState != 0) {
|
||||
_envCurVel = _instrument->envelope[1].target;
|
||||
_envState = 4;
|
||||
}
|
||||
// Original driver doesn't reset note anywhere that I could find,
|
||||
// but this seems like a good place to do that
|
||||
_note = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_Amiga0::AmigaVoice::setPitchWheel(int16 pitch) {
|
||||
if (_amigaDriver._isEarlyDriver)
|
||||
return;
|
||||
|
||||
_pitch = pitch;
|
||||
|
||||
if (_note != -1)
|
||||
calcVoiceStep();
|
||||
}
|
||||
|
||||
bool MidiPlayer_Amiga0::loadInstruments(Common::SeekableReadStream &patch) {
|
||||
char name[31];
|
||||
|
||||
if (patch.read(name, 8) < 8 || strncmp(name, "X0iUo123", 8) != 0) {
|
||||
warning("MidiPlayer_Amiga0: Incorrect ID string in patch bank");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (patch.read(name, 30) < 30) {
|
||||
warning("MidiPlayer_Amiga0: Error reading patch bank");
|
||||
return false;
|
||||
}
|
||||
name[30] = 0;
|
||||
|
||||
debugC(kDebugLevelSound, "Bank: '%s'", name);
|
||||
|
||||
_instruments.resize(128);
|
||||
|
||||
const uint16 instrumentCount = patch.readUint16BE();
|
||||
|
||||
if (instrumentCount == 0) {
|
||||
warning("MidiPlayer_Amiga0: No instruments found in patch bank");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < instrumentCount; ++i) {
|
||||
AmigaInstrument *instrument = new AmigaInstrument();
|
||||
|
||||
const uint16 patchIdx = patch.readUint16BE();
|
||||
_instruments[patchIdx] = instrument;
|
||||
|
||||
if (i == 0)
|
||||
_defaultInstrument = patchIdx;
|
||||
|
||||
patch.read(instrument->name, 30);
|
||||
instrument->name[30] = 0;
|
||||
const uint16 flags = patch.readUint16BE();
|
||||
instrument->loop = flags & 1;
|
||||
instrument->fixedNote = !(flags & 2);
|
||||
instrument->transpose = patch.readSByte();
|
||||
instrument->seg1Size = patch.readSint16BE();
|
||||
instrument->seg2Offset = patch.readUint32BE();
|
||||
instrument->seg2Size = patch.readSint16BE();
|
||||
instrument->seg3Offset = patch.readUint32BE();
|
||||
instrument->seg3Size = patch.readSint16BE();
|
||||
|
||||
// There's some envelope-related bugs here in the original, these were not replicated
|
||||
for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
|
||||
instrument->envelope[stage].skip = patch.readByte();
|
||||
|
||||
for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
|
||||
instrument->envelope[stage].step = patch.readByte();
|
||||
|
||||
for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
|
||||
instrument->envelope[stage].target = patch.readByte();
|
||||
|
||||
int32 sampleSize = instrument->seg1Size + instrument->seg2Size + instrument->seg3Size;
|
||||
sampleSize <<= 1;
|
||||
byte *samples = new byte[sampleSize];
|
||||
patch.read(samples, sampleSize);
|
||||
instrument->samples = samples;
|
||||
|
||||
if (patch.eos() || patch.err()) {
|
||||
warning("MidiPlayer_Amiga0: Error reading patch bank");
|
||||
return false;
|
||||
}
|
||||
|
||||
debugC(kDebugLevelSound, "\tInstrument[%d]: '%s'", patchIdx, instrument->name);
|
||||
debugC(kDebugLevelSound, "\t\tSegment 1: offset 0, size %d", instrument->seg1Size * 2);
|
||||
debugC(kDebugLevelSound, "\t\tSegment 2: offset %d, size %d", instrument->seg2Offset, instrument->seg2Size * 2);
|
||||
debugC(kDebugLevelSound, "\t\tSegment 3: offset %d, size %d", instrument->seg3Offset, instrument->seg3Size * 2);
|
||||
debugC(kDebugLevelSound, "\t\tTranspose = %d, Fixed note = %d, Loop = %d", instrument->transpose, instrument->fixedNote, instrument->loop);
|
||||
debugC(kDebugLevelSound, "\t\tEnvelope:");
|
||||
for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
|
||||
debugC(kDebugLevelSound, "\t\t\tStage %d: skip %d, step %d, target %d", stage, instrument->envelope[stage].skip, instrument->envelope[stage].step, instrument->envelope[stage].target);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MidiPlayer *MidiPlayer_AmigaMac0_create(SciVersion version, Common::Platform platform) {
|
||||
if (platform == Common::kPlatformMacintosh)
|
||||
return new MidiPlayer_Mac0(version, g_system->getMixer(), MidiPlayer_Mac0::kModeHq);
|
||||
else
|
||||
return new MidiPlayer_Amiga0(version, g_system->getMixer());
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
1317
engines/sci/sound/drivers/amigamac1.cpp
Normal file
1317
engines/sci/sound/drivers/amigamac1.cpp
Normal file
File diff suppressed because it is too large
Load Diff
496
engines/sci/sound/drivers/casio.cpp
Normal file
496
engines/sci/sound/drivers/casio.cpp
Normal file
@@ -0,0 +1,496 @@
|
||||
/* 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 "audio/casio.h"
|
||||
|
||||
#include "sci/resource/resource.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
class MidiDriver_Casio : public ::MidiDriver_Casio {
|
||||
protected:
|
||||
// The instrument number used for the slap bass instrument on MT-540.
|
||||
static const uint8 SLAP_BASS_INSTRUMENT_MT540;
|
||||
// The instrument number used for the slap bass instrument on CT-460 and
|
||||
// CSM-1.
|
||||
static const uint8 SLAP_BASS_INSTRUMENT_CT460;
|
||||
static const uint8 PATCH_RESOURCE_SIZE;
|
||||
|
||||
public:
|
||||
MidiDriver_Casio(MusicType midiType) : ::MidiDriver_Casio(midiType),
|
||||
_highSplitInstOutputChannel(-1), _rhythmChannelMapped(false), _playSwitch(true) {
|
||||
Common::fill(_instrumentRemapping, _instrumentRemapping + ARRAYSIZE(_instrumentRemapping), 0);
|
||||
setInstrumentRemapping(_instrumentRemapping);
|
||||
_rhythmNoteRemapping = new byte[128];
|
||||
|
||||
Common::fill(_instrumentFixedNotes, _instrumentFixedNotes + ARRAYSIZE(_instrumentFixedNotes), 0);
|
||||
Common::fill(_channelMap, _channelMap + ARRAYSIZE(_channelMap), 0);
|
||||
Common::fill(_channelFixedNotes, _channelFixedNotes + ARRAYSIZE(_channelFixedNotes), 0);
|
||||
|
||||
_sendUntrackedNoteOff = false;
|
||||
}
|
||||
~MidiDriver_Casio() {
|
||||
delete[] _rhythmNoteRemapping;
|
||||
}
|
||||
|
||||
bool loadResource(const SciSpan<const byte> &data, MusicType midiType = MT_AUTO);
|
||||
void initTrack(SciSpan<const byte> &header);
|
||||
|
||||
void playSwitch(bool play);
|
||||
|
||||
protected:
|
||||
void noteOn(byte outputChannel, byte note, byte velocity, int8 source) override;
|
||||
void programChange(byte outputChannel, byte patchId, int8 source, bool applyRemapping = true) override;
|
||||
void programChange(byte outputChannel, byte patchId, int8 source, bool applyRemapping, bool applyBassSwap);
|
||||
|
||||
int8 mapSourceChannel(uint8 source, uint8 dataChannel) override;
|
||||
byte mapInstrument(byte program, bool applyRemapping) override;
|
||||
int8 mapNote(byte outputChannel, byte note) override;
|
||||
bool isRhythmChannel(uint8 outputChannel) override;
|
||||
byte calculateVelocity(int8 source, byte velocity) override;
|
||||
|
||||
byte _instrumentRemapping[128];
|
||||
// If > 0, a fixed note value should be played for the corresponding
|
||||
// instrument instead of the MIDI event note value.
|
||||
byte _instrumentFixedNotes[0x60];
|
||||
// Tracks the output channel which is currently being used by the "high"
|
||||
// split bass instrument (if any). Will be either -1 or 2.
|
||||
int8 _highSplitInstOutputChannel;
|
||||
|
||||
int8 _channelMap[16];
|
||||
// True if the rhythm channel has been mapped to output channel 3.
|
||||
bool _rhythmChannelMapped;
|
||||
// The fixed note that needs to be played on each output channel instead of
|
||||
// the MIDI event note value (or 0 if there is no fixed note).
|
||||
byte _channelFixedNotes[4];
|
||||
bool _playSwitch;
|
||||
};
|
||||
|
||||
class MidiPlayer_Casio : public MidiPlayer {
|
||||
public:
|
||||
static const uint8 RESOURCE_HEADER_FLAG;
|
||||
|
||||
protected:
|
||||
static const byte PATCH_RESOURCE_MT540;
|
||||
static const byte PATCH_RESOURCE_CT460;
|
||||
|
||||
public:
|
||||
MidiPlayer_Casio(SciVersion soundVersion, MusicType midiType);
|
||||
~MidiPlayer_Casio() override;
|
||||
|
||||
int open(ResourceManager *resMan) override;
|
||||
void close() override;
|
||||
|
||||
byte getPlayId() const override;
|
||||
int getPolyphony() const override;
|
||||
bool hasRhythmChannel() const override;
|
||||
void setVolume(byte volume) override;
|
||||
void playSwitch(bool play) override;
|
||||
void initTrack(SciSpan<const byte> &header) override;
|
||||
int getLastChannel() const override;
|
||||
|
||||
void send(uint32 b) override;
|
||||
|
||||
protected:
|
||||
MidiDriver_Casio *_casioDriver;
|
||||
MusicType _midiType;
|
||||
};
|
||||
|
||||
const uint8 MidiDriver_Casio::SLAP_BASS_INSTRUMENT_MT540 = 0x14;
|
||||
const uint8 MidiDriver_Casio::SLAP_BASS_INSTRUMENT_CT460 = 0x1E;
|
||||
|
||||
const uint8 MidiDriver_Casio::PATCH_RESOURCE_SIZE = 0xE9;
|
||||
|
||||
bool MidiDriver_Casio::loadResource(const SciSpan<const byte> &data, MusicType midiType) {
|
||||
if (midiType != MT_AUTO) {
|
||||
if (!(midiType == MT_MT540 || midiType == MT_CT460)) {
|
||||
error("CASIO: Unsupported music data type %i", midiType);
|
||||
}
|
||||
_midiType = midiType;
|
||||
}
|
||||
|
||||
const uint32 size = data.size();
|
||||
if (size != PATCH_RESOURCE_SIZE) {
|
||||
error("CASIO: Unsupported patch format (%u bytes)", size);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 dataIndex = 0;
|
||||
for (int i = 0; i < 0x60; i++) {
|
||||
_instrumentRemapping[i] = data.getUint8At(dataIndex++);
|
||||
_instrumentFixedNotes[i] = data.getUint8At(dataIndex++);
|
||||
}
|
||||
for (int i = 0; i < 0x29; i++) {
|
||||
_rhythmNoteRemapping[0x23 + i] = data.getUint8At(dataIndex++);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MidiDriver_Casio::initTrack(SciSpan<const byte> &header) {
|
||||
if (!_isOpen)
|
||||
return;
|
||||
|
||||
Common::fill(_channelMap, _channelMap + ARRAYSIZE(_channelMap), -1);
|
||||
Common::fill(_rhythmChannel, _rhythmChannel + ARRAYSIZE(_rhythmChannel), false);
|
||||
Common::fill(_channelFixedNotes, _channelFixedNotes + ARRAYSIZE(_channelFixedNotes), 0);
|
||||
_rhythmChannelMapped = false;
|
||||
|
||||
uint8 readPos = 0;
|
||||
uint8 caps = header.getInt8At(readPos++);
|
||||
if (caps != 0 && caps != 2)
|
||||
// Not a supported sound resource type.
|
||||
return;
|
||||
|
||||
uint8 numChannels = 16;
|
||||
if (caps == 2)
|
||||
// Digital sound data on channel 15; don't use this channel.
|
||||
numChannels--;
|
||||
|
||||
byte outputChannel = 0;
|
||||
for (int i = 0; i < numChannels; i++) {
|
||||
bool rhythmChannel = ((header.getInt8At(readPos++) & 0x80) > 0);
|
||||
bool deviceFlag = ((header.getInt8At(readPos++) & MidiPlayer_Casio::RESOURCE_HEADER_FLAG) > 0);
|
||||
if (!deviceFlag)
|
||||
// Data channel is not used for Casio devices.
|
||||
continue;
|
||||
|
||||
if (rhythmChannel) {
|
||||
if (!_rhythmChannelMapped) {
|
||||
if (outputChannel == 4) {
|
||||
// The rhythm channel has already been assigned to a melodic
|
||||
// instrument. This means that more than 4 channels have
|
||||
// been flagged for Casio, which should not happen, but
|
||||
// clear the existing channel mapping just in case.
|
||||
for (int j = 0; j < numChannels; j++) {
|
||||
if (_channelMap[j] == 3)
|
||||
_channelMap[j] = -1;
|
||||
}
|
||||
}
|
||||
_channelMap[i] = 3;
|
||||
programChange(3, _midiType == MusicType::MT_MT540 ? RHYTHM_INSTRUMENT_MT540 : RHYTHM_INSTRUMENT_CT460, 0, false);
|
||||
_rhythmChannelMapped = true;
|
||||
}
|
||||
} else if (outputChannel < (_rhythmChannelMapped ? 3 : 4)) {
|
||||
_channelMap[i] = outputChannel++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_Casio::playSwitch(bool play) {
|
||||
_playSwitch = play;
|
||||
if (!_playSwitch)
|
||||
stopAllNotes(0xFF, 0xFF);
|
||||
}
|
||||
|
||||
void MidiDriver_Casio::noteOn(byte outputChannel, byte note, byte velocity, int8 source) {
|
||||
if (velocity == 0) {
|
||||
// Note on with velocity 0 is a note off.
|
||||
noteOff(outputChannel, MIDI_COMMAND_NOTE_ON, note, velocity, source);
|
||||
return;
|
||||
}
|
||||
|
||||
_mutex.lock();
|
||||
|
||||
// Check if there is an available voice for this note.
|
||||
int polyphonyCount = 0;
|
||||
for (int i = 0; i < ARRAYSIZE(_activeNotes); i++) {
|
||||
// Note that this check ignores sustained notes; original driver does
|
||||
// this too.
|
||||
if (_activeNotes[i].channel == outputChannel && !_activeNotes[i].sustained) {
|
||||
polyphonyCount++;
|
||||
}
|
||||
}
|
||||
if (polyphonyCount >= CASIO_CHANNEL_POLYPHONY[outputChannel]) {
|
||||
// Maximum channel polyphony has been reached. Don't play this note.
|
||||
_mutex.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
::MidiDriver_Casio::noteOn(outputChannel, note, velocity, source);
|
||||
|
||||
_mutex.unlock();
|
||||
}
|
||||
|
||||
void MidiDriver_Casio::programChange(byte outputChannel, byte patchId, int8 source, bool applyRemapping) {
|
||||
programChange(outputChannel, patchId, source, applyRemapping, true);
|
||||
}
|
||||
|
||||
void MidiDriver_Casio::programChange(byte outputChannel, byte patchId, int8 source, bool applyRemapping, bool applyBassSwap) {
|
||||
if ((_rhythmChannelMapped && outputChannel == 3) || outputChannel >= 4)
|
||||
// Ignore program change on the rhythm channel or on unused channels.
|
||||
return;
|
||||
|
||||
// Apply instrument mapping.
|
||||
byte mappedInstrument = mapInstrument(patchId, applyRemapping);
|
||||
|
||||
// The Casio devices have an instrument (at 0x12 / 0x1C) which combines two
|
||||
// different bass instruments with a split note range. SCI assigns a
|
||||
// separate number to the slap bass instrument, which is on the "high" note
|
||||
// range (0x14 / 0x1E). Check for this number.
|
||||
if (mappedInstrument == (_deviceType == MT_MT540 ? SLAP_BASS_INSTRUMENT_MT540 : SLAP_BASS_INSTRUMENT_CT460)) {
|
||||
// The "high" split instrument (slap bass) has been selected.
|
||||
// Set the channel using this instrument so notes can be remapped to
|
||||
// the correct range.
|
||||
_highSplitInstOutputChannel = 2; // Output channel is set to 2 below.
|
||||
// Set the actual instrument number used by the Casio devices.
|
||||
mappedInstrument = (_deviceType == MT_MT540 ? BASS_INSTRUMENT_MT540 : BASS_INSTRUMENT_CT460);
|
||||
} else if (_highSplitInstOutputChannel == outputChannel) {
|
||||
// The instrument on this channel is changed from the "high" split
|
||||
// instrument to a different instrument. Reset the output channel
|
||||
// variable.
|
||||
_highSplitInstOutputChannel = -1;
|
||||
}
|
||||
|
||||
// If the bass instrument is set on any channel, SCI always moves this
|
||||
// instrument to output channel 2. This is probably because the Casio
|
||||
// devices have a limited fixed polyphony on each channel: 6, 4,
|
||||
// 2 and 4 respectively. Moving the bass to channel 2 overcomes this
|
||||
// limitation somewhat, because this channel has the lowest polyphony and
|
||||
// the bass doesn't tend to play chords.
|
||||
// Check if the bass instrument is set to a channel other than 2, and
|
||||
// move it to channel 2 if necessary.
|
||||
if (applyBassSwap && mappedInstrument == (_deviceType == MT_MT540 ? BASS_INSTRUMENT_MT540 : BASS_INSTRUMENT_CT460) && outputChannel != 2) {
|
||||
_mutex.lock();
|
||||
|
||||
int currentDataChannel = -1;
|
||||
int currentTargetDataChannel = -1;
|
||||
for (int i = 0; i < MIDI_CHANNEL_COUNT; i++) {
|
||||
if (_channelMap[i] == outputChannel) {
|
||||
currentDataChannel = i;
|
||||
} else if (_channelMap[i] == 2) {
|
||||
currentTargetDataChannel = i;
|
||||
}
|
||||
}
|
||||
|
||||
// These data channels should always be mapped, but check it just in case.
|
||||
if (currentDataChannel >= 0 && currentTargetDataChannel >= 0) {
|
||||
// The original drivers do not stop all notes before swapping channels.
|
||||
// This could potentially cause hanging notes, so this is done here to
|
||||
// be safe. Instead, the original drivers swap out the registered
|
||||
// active notes between the channels. This does not accomplish anything
|
||||
// other than putting the driver state out of sync with the device
|
||||
// state.
|
||||
stopAllNotes(source, outputChannel);
|
||||
stopAllNotes(source, 2);
|
||||
|
||||
_channelMap[currentDataChannel] = 2;
|
||||
_channelMap[currentTargetDataChannel] = outputChannel;
|
||||
|
||||
programChange(outputChannel, _instruments[2], source, applyRemapping, false);
|
||||
|
||||
outputChannel = 2;
|
||||
}
|
||||
|
||||
_mutex.unlock();
|
||||
}
|
||||
|
||||
// Register the new instrument.
|
||||
_instruments[outputChannel] = patchId;
|
||||
|
||||
_channelFixedNotes[outputChannel] = (patchId < ARRAYSIZE(_instrumentFixedNotes) ? _instrumentFixedNotes[patchId] : 0);
|
||||
|
||||
_rhythmChannel[outputChannel] =
|
||||
mappedInstrument == (_deviceType == MT_MT540 ? RHYTHM_INSTRUMENT_MT540 : RHYTHM_INSTRUMENT_CT460);
|
||||
|
||||
_driver->send(MIDI_COMMAND_PROGRAM_CHANGE | outputChannel | (mappedInstrument << 8));
|
||||
}
|
||||
|
||||
int8 MidiDriver_Casio::mapSourceChannel(uint8 source, uint8 dataChannel) {
|
||||
// Only source 0 is used by this driver.
|
||||
return _channelMap[dataChannel];
|
||||
}
|
||||
|
||||
byte MidiDriver_Casio::mapInstrument(byte program, bool applyRemapping) {
|
||||
byte mappedInstrument = ::MidiDriver_Casio::mapInstrument(program, applyRemapping);
|
||||
|
||||
if (applyRemapping) {
|
||||
// Correct remapping of the extra SCI slap bass instrument.
|
||||
if (_midiType == MT_MT540 && _deviceType == MT_CT460 &&
|
||||
mappedInstrument == INSTRUMENT_REMAPPING_MT540_TO_CT460[SLAP_BASS_INSTRUMENT_MT540]) {
|
||||
// For the MT-540, SCI uses 0x14 as the slap bass instrument, which
|
||||
// actually is the honky-tonk piano. If the instrument has been mapped
|
||||
// to the CT-460 honky-tonk piano, correct it to the CT-460 SCI slap
|
||||
// bass.
|
||||
mappedInstrument = SLAP_BASS_INSTRUMENT_CT460;
|
||||
} else if (_midiType == MT_CT460 && _deviceType == MT_MT540 && mappedInstrument == SLAP_BASS_INSTRUMENT_CT460) {
|
||||
// For the CT-460, SCI uses 0x1E as the slap bass instrument, which
|
||||
// is unused, so it will not be remapped. Manually remap it here to
|
||||
// the MT-540 SCI slap bass instrument.
|
||||
mappedInstrument = SLAP_BASS_INSTRUMENT_MT540;
|
||||
}
|
||||
}
|
||||
|
||||
return mappedInstrument;
|
||||
}
|
||||
|
||||
int8 MidiDriver_Casio::mapNote(byte outputChannel, byte note) {
|
||||
if (!isRhythmChannel(outputChannel) && outputChannel < 4) {
|
||||
if (_highSplitInstOutputChannel == outputChannel) {
|
||||
// The slap bass instrument has been set on this output channel.
|
||||
// Transpose the note up to the range used by this instrument.
|
||||
byte transposedNote = note + 0x18;
|
||||
if (transposedNote < 0x3C)
|
||||
transposedNote += 0xC;
|
||||
return transposedNote;
|
||||
}
|
||||
|
||||
// Check if the MIDI event note should be replaced by a fixed note.
|
||||
byte fixedNote = _channelFixedNotes[outputChannel];
|
||||
return fixedNote > 0 ? fixedNote : note;
|
||||
}
|
||||
|
||||
// Apply rhythm note mapping.
|
||||
return ::MidiDriver_Casio::mapNote(outputChannel, note);
|
||||
}
|
||||
|
||||
bool MidiDriver_Casio::isRhythmChannel(uint8 outputChannel) {
|
||||
// SCI only uses channel 3 as the rhythm channel. If the drum instrument is
|
||||
// set on a different channel, it is for a sound effect and rhythm note
|
||||
// remapping should not be applied.
|
||||
return _rhythmChannelMapped && outputChannel == 3;
|
||||
}
|
||||
|
||||
byte MidiDriver_Casio::calculateVelocity(int8 source, byte velocity) {
|
||||
if (!_playSwitch)
|
||||
return 0;
|
||||
|
||||
return ::MidiDriver_Casio::calculateVelocity(source, velocity);
|
||||
}
|
||||
|
||||
const uint8 MidiPlayer_Casio::RESOURCE_HEADER_FLAG = 0x08;
|
||||
|
||||
const byte MidiPlayer_Casio::PATCH_RESOURCE_MT540 = 4;
|
||||
const byte MidiPlayer_Casio::PATCH_RESOURCE_CT460 = 7;
|
||||
|
||||
MidiPlayer_Casio::MidiPlayer_Casio(SciVersion soundVersion, MusicType midiType) : MidiPlayer(soundVersion) {
|
||||
_casioDriver = new MidiDriver_Casio(midiType);
|
||||
_driver = _casioDriver;
|
||||
_midiType = midiType;
|
||||
}
|
||||
|
||||
MidiPlayer_Casio::~MidiPlayer_Casio() {
|
||||
delete _casioDriver;
|
||||
_casioDriver = nullptr;
|
||||
_driver = nullptr;
|
||||
}
|
||||
|
||||
int MidiPlayer_Casio::open(ResourceManager* resMan) {
|
||||
if (_version < SCI_VERSION_0_LATE || _version > SCI_VERSION_01) {
|
||||
warning("CASIO: Unsupported SCI version");
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert(resMan != nullptr);
|
||||
|
||||
// WORKAROUND The Casio devices have a bass instrument which combines two
|
||||
// instruments on different note ranges. To make the second instrument
|
||||
// (slap bass) selectable, SCI assigns a new instrument number to this
|
||||
// instrument. On the MT-540 Sierra used instrument 0x14 (20), probably
|
||||
// because they thought this was the first unused instrument number.
|
||||
// However, besides the 20 instruments selectable on the keyboard, the
|
||||
// device has 10 more instruments which can only be selected via MIDI.
|
||||
// The result of this is that the instrument which actually uses number
|
||||
// 0x14 on the MT-540 (honky-tonk piano) cannot be used by the instrument
|
||||
// mapping. Sierra worked around this by using the normal piano instead of
|
||||
// the honky-tonk piano for the MT-540. This affects at least Hoyle 1.
|
||||
// The CT-460 and CSM-1 are not affected by this issue because Sierra used
|
||||
// instrument 0x1E (30) as the slap bass instrument. To fix this problem,
|
||||
// the CT-460 instrument mapping is also used for the MT-540, with the
|
||||
// output instruments remapped to their MT-540 equivalents.
|
||||
|
||||
// Load the CT-460 patch resource.
|
||||
int patchResource = PATCH_RESOURCE_CT460;
|
||||
_midiType = MusicType::MT_CT460;
|
||||
Resource *res = resMan->findResource(ResourceId(kResourceTypePatch, patchResource), false);
|
||||
bool ok = false;
|
||||
|
||||
if (res) {
|
||||
ok = _casioDriver->loadResource(*res, _midiType);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
// CT-460 patch resource not present. Fall back to the MT-540 resource.
|
||||
MusicType altMidiType = MT_MT540;
|
||||
int altPatchResource = PATCH_RESOURCE_CT460;
|
||||
warning("CASIO: Failed to load patch.00%i - falling back to patch.00%i", patchResource, altPatchResource);
|
||||
res = resMan->findResource(ResourceId(kResourceTypePatch, altPatchResource), false);
|
||||
|
||||
if (res) {
|
||||
ok = _casioDriver->loadResource(*res, altMidiType);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
warning("CASIO: Failed to load fallback patch.00%i", altPatchResource);
|
||||
return -1;
|
||||
}
|
||||
|
||||
_midiType = altMidiType;
|
||||
}
|
||||
|
||||
return _casioDriver->open();
|
||||
}
|
||||
|
||||
void MidiPlayer_Casio::close() {
|
||||
_driver->close();
|
||||
}
|
||||
|
||||
byte MidiPlayer_Casio::getPlayId() const {
|
||||
return RESOURCE_HEADER_FLAG;
|
||||
}
|
||||
|
||||
int MidiPlayer_Casio::getPolyphony() const {
|
||||
return 16;
|
||||
}
|
||||
|
||||
bool MidiPlayer_Casio::hasRhythmChannel() const {
|
||||
// Only use the rhythm channel if it has the Casio flag set.
|
||||
return false;
|
||||
}
|
||||
|
||||
void MidiPlayer_Casio::setVolume(byte volume) {
|
||||
_casioDriver->setSourceVolume(0, volume);
|
||||
}
|
||||
|
||||
void MidiPlayer_Casio::playSwitch(bool play) {
|
||||
_casioDriver->playSwitch(play);
|
||||
}
|
||||
|
||||
void MidiPlayer_Casio::initTrack(SciSpan<const byte> &header) {
|
||||
_casioDriver->initTrack(header);
|
||||
}
|
||||
|
||||
int MidiPlayer_Casio::getLastChannel() const {
|
||||
// Not relevant for SCI0.
|
||||
return 15;
|
||||
}
|
||||
|
||||
void MidiPlayer_Casio::send(uint32 b) {
|
||||
_driver->send(0, b);
|
||||
}
|
||||
|
||||
MidiPlayer *MidiPlayer_Casio_create(SciVersion version, MusicType midiType) {
|
||||
return new MidiPlayer_Casio(version, midiType);
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
1375
engines/sci/sound/drivers/cms.cpp
Normal file
1375
engines/sci/sound/drivers/cms.cpp
Normal file
File diff suppressed because it is too large
Load Diff
809
engines/sci/sound/drivers/fb01.cpp
Normal file
809
engines/sci/sound/drivers/fb01.cpp
Normal file
@@ -0,0 +1,809 @@
|
||||
/* 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/sci.h"
|
||||
|
||||
#include "sci/resource/resource.h"
|
||||
#include "sci/sound/drivers/mididriver.h"
|
||||
#include "sci/util.h"
|
||||
|
||||
#include "common/file.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/mutex.h"
|
||||
|
||||
// The original driver uses the master volume setting of the hardware device by sending sysex messages
|
||||
// (applies to SCI0 and SCI1). We have a software mastervolume implementation instead. I don't know the
|
||||
// reason for this. Nor do I know whether our software effective volume calculation matches the device's
|
||||
// internal volume calculation algorithm.
|
||||
// I add the original style master volume handling, but make it optional via a #define
|
||||
#define HARDWARE_MASTERVOLUME
|
||||
|
||||
namespace Sci {
|
||||
|
||||
static byte volumeTable[64] = {
|
||||
0x00, 0x10, 0x14, 0x18, 0x1f, 0x26, 0x2a, 0x2e,
|
||||
0x2f, 0x32, 0x33, 0x33, 0x34, 0x35, 0x35, 0x36,
|
||||
0x36, 0x37, 0x37, 0x38, 0x38, 0x38, 0x39, 0x39,
|
||||
0x39, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3b, 0x3b,
|
||||
0x3b, 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c,
|
||||
0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3e, 0x3e, 0x3e,
|
||||
0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
|
||||
0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f
|
||||
};
|
||||
|
||||
class MidiPlayer_Fb01 : public MidiPlayer {
|
||||
public:
|
||||
enum {
|
||||
kVoices = 8,
|
||||
kMaxSysExSize = 264
|
||||
};
|
||||
|
||||
MidiPlayer_Fb01(SciVersion version);
|
||||
~MidiPlayer_Fb01() override;
|
||||
|
||||
int open(ResourceManager *resMan) override;
|
||||
void close() override;
|
||||
void initTrack(SciSpan<const byte>& header) override;
|
||||
void send(uint32 b) override;
|
||||
void sysEx(const byte *msg, uint16 length) override;
|
||||
bool hasRhythmChannel() const override { return false; }
|
||||
byte getPlayId() const override;
|
||||
int getPolyphony() const override { return _version <= SCI_VERSION_0_LATE ? 8 : 9; }
|
||||
void setVolume(byte volume) override;
|
||||
int getVolume() override;
|
||||
void playSwitch(bool play) override;
|
||||
|
||||
bool isOpen() const { return _isOpen; }
|
||||
|
||||
const char *reportMissingFiles() override { return _missingFiles; }
|
||||
|
||||
private:
|
||||
void noteOn(int channel, int note, int velocity);
|
||||
void noteOff(int channel, int note);
|
||||
void setPatch(int channel, int patch);
|
||||
void controlChange(int channel, int control, int value);
|
||||
|
||||
void setVoiceParam(byte voice, byte param, byte value);
|
||||
void setSystemParam(byte sysChan, byte param, byte value);
|
||||
void sendVoiceData(byte instrument, const SciSpan<const byte> &data);
|
||||
void sendBanks(const SciSpan<const byte> &data);
|
||||
void storeVoiceData(byte instrument, byte bank, byte index);
|
||||
void initVoices();
|
||||
|
||||
void voiceOn(int voice, int note, int velocity);
|
||||
void voiceOff(int voice);
|
||||
int findVoice(int channel);
|
||||
void voiceMapping(int channel, int voices);
|
||||
void assignVoices(int channel, int voices);
|
||||
void releaseVoices(int channel, int voices);
|
||||
void donateVoices();
|
||||
void sendToChannel(byte channel, byte command, byte op1, byte op2);
|
||||
|
||||
struct Channel {
|
||||
uint8 patch; // Patch setting
|
||||
uint8 volume; // Channel volume (0-63)
|
||||
uint8 pan; // Pan setting (0-127, 64 is center)
|
||||
uint8 holdPedal; // Hold pedal setting (0 to 63 is off, 127 to 64 is on)
|
||||
uint8 extraVoices; // The number of additional voices this channel optimally needs
|
||||
uint16 pitchWheel; // Pitch wheel setting (0-16383, 8192 is center)
|
||||
uint8 lastVoice; // Last voice used for this MIDI channel
|
||||
bool enableVelocity; // Enable velocity control (SCI0)
|
||||
|
||||
Channel() : patch(0), volume(127), pan(64), holdPedal(0), extraVoices(0),
|
||||
pitchWheel(8192), lastVoice(0), enableVelocity(false) { }
|
||||
};
|
||||
|
||||
struct Voice {
|
||||
int8 channel; // MIDI channel that this voice is assigned to or -1
|
||||
uint8 poly; // Number of hardware voices (SCI0); for SCI1 we just set this to 1
|
||||
int8 note; // Currently playing MIDI note or -1
|
||||
int bank; // Current bank setting or -1
|
||||
int patch; // Currently playing patch or -1
|
||||
//uint8 velocity; // Note velocity
|
||||
//bool isSustained; // Flag indicating a note that is being sustained by the hold pedal
|
||||
uint16 age; // Age of the current note
|
||||
|
||||
Voice() : channel(-1), note(-1), bank(-1), patch(-1), /*velocity(0), isSustained(false),*/ age(0), poly(1) { }
|
||||
};
|
||||
|
||||
bool _playSwitch;
|
||||
int _masterVolume;
|
||||
int _numParts;
|
||||
|
||||
bool _isOpen;
|
||||
|
||||
Channel _channels[16];
|
||||
Voice _voices[kVoices];
|
||||
|
||||
Common::TimerManager::TimerProc _timerProc;
|
||||
void *_timerParam;
|
||||
static void midiTimerCallback(void *p);
|
||||
void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override;
|
||||
|
||||
const char *_missingFiles;
|
||||
static const char _requiredFiles[2][12];
|
||||
|
||||
byte _sysExBuf[kMaxSysExSize];
|
||||
};
|
||||
|
||||
MidiPlayer_Fb01::MidiPlayer_Fb01(SciVersion version) : MidiPlayer(version), _playSwitch(true), _masterVolume(15), _timerParam(nullptr), _timerProc(nullptr),
|
||||
_numParts(version > SCI_VERSION_0_LATE ? kVoices : 0), _isOpen(false), _missingFiles(nullptr) {
|
||||
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI);
|
||||
_driver = MidiDriver::createMidi(dev);
|
||||
|
||||
_sysExBuf[0] = 0x43;
|
||||
_sysExBuf[1] = 0x75;
|
||||
}
|
||||
|
||||
MidiPlayer_Fb01::~MidiPlayer_Fb01() {
|
||||
if (_driver)
|
||||
_driver->setTimerCallback(nullptr, nullptr);
|
||||
close();
|
||||
delete _driver;
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::voiceMapping(int channel, int voices) {
|
||||
if (_version <= SCI_VERSION_0_LATE) {
|
||||
// The original SCI0 drivers don't do any software voice mapping. Only inside the device...
|
||||
for (int i = 0; i < _numParts; ++i) {
|
||||
if (_voices[i].channel == channel && _voices[i].poly != voices) {
|
||||
_voices[i].poly = voices;
|
||||
setVoiceParam(i, 0, voices);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int curVoices = 0;
|
||||
|
||||
for (int i = 0; i < kVoices; i++)
|
||||
if (_voices[i].channel == channel)
|
||||
curVoices++;
|
||||
|
||||
curVoices += _channels[channel].extraVoices;
|
||||
|
||||
if (curVoices < voices) {
|
||||
debug(3, "FB-01: assigning %i additional voices to channel %i", voices - curVoices, channel);
|
||||
assignVoices(channel, voices - curVoices);
|
||||
} else if (curVoices > voices) {
|
||||
debug(3, "FB-01: releasing %i voices from channel %i", curVoices - voices, channel);
|
||||
releaseVoices(channel, curVoices - voices);
|
||||
donateVoices();
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::assignVoices(int channel, int voices) {
|
||||
assert(voices > 0);
|
||||
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
if (_voices[i].channel == -1) {
|
||||
_voices[i].channel = channel;
|
||||
if (_voices[i].note != -1)
|
||||
voiceOff(i);
|
||||
if (--voices == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_channels[channel].extraVoices += voices;
|
||||
setPatch(channel, _channels[channel].patch);
|
||||
sendToChannel(channel, 0xe0, _channels[channel].pitchWheel & 0x7f, _channels[channel].pitchWheel >> 7);
|
||||
controlChange(channel, 0x07, _channels[channel].volume);
|
||||
controlChange(channel, 0x0a, _channels[channel].pan);
|
||||
controlChange(channel, 0x40, _channels[channel].holdPedal);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::releaseVoices(int channel, int voices) {
|
||||
if (_channels[channel].extraVoices >= voices) {
|
||||
_channels[channel].extraVoices -= voices;
|
||||
return;
|
||||
}
|
||||
|
||||
voices -= _channels[channel].extraVoices;
|
||||
_channels[channel].extraVoices = 0;
|
||||
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
if ((_voices[i].channel == channel) && (_voices[i].note == -1)) {
|
||||
_voices[i].channel = -1;
|
||||
if (--voices == 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
if (_voices[i].channel == channel) {
|
||||
voiceOff(i);
|
||||
_voices[i].channel = -1;
|
||||
if (--voices == 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::donateVoices() {
|
||||
int freeVoices = 0;
|
||||
|
||||
for (int i = 0; i < kVoices; i++)
|
||||
if (_voices[i].channel == -1)
|
||||
freeVoices++;
|
||||
|
||||
if (freeVoices == 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < MIDI_CHANNELS; i++) {
|
||||
if (_channels[i].extraVoices >= freeVoices) {
|
||||
assignVoices(i, freeVoices);
|
||||
_channels[i].extraVoices -= freeVoices;
|
||||
return;
|
||||
} else if (_channels[i].extraVoices > 0) {
|
||||
assignVoices(i, _channels[i].extraVoices);
|
||||
freeVoices -= _channels[i].extraVoices;
|
||||
_channels[i].extraVoices = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int MidiPlayer_Fb01::findVoice(int channel) {
|
||||
int voice = -1;
|
||||
int oldestVoice = -1;
|
||||
uint32 oldestAge = 0;
|
||||
|
||||
// Try to find a voice assigned to this channel that is free (round-robin)
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
int v = (_channels[channel].lastVoice + i + 1) % kVoices;
|
||||
|
||||
if (_voices[v].channel == channel) {
|
||||
if (_voices[v].note == -1) {
|
||||
voice = v;
|
||||
break;
|
||||
}
|
||||
|
||||
// We also keep track of the oldest note in case the search fails
|
||||
// Notes started in the current time slice will not be selected
|
||||
if (_voices[v].age > oldestAge) {
|
||||
oldestAge = _voices[v].age;
|
||||
oldestVoice = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (voice == -1) {
|
||||
if (oldestVoice >= 0) {
|
||||
voiceOff(oldestVoice);
|
||||
voice = oldestVoice;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
_channels[channel].lastVoice = voice;
|
||||
return voice;
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::sendToChannel(byte channel, byte command, byte op1, byte op2) {
|
||||
for (int i = 0; i < _numParts; i++) {
|
||||
// Send command to all voices assigned to this channel
|
||||
// I case of SCI0 the voice mapping is done inside the device.
|
||||
if (_voices[i].channel == channel)
|
||||
_driver->send(command | (_version <= SCI_VERSION_0_LATE ? channel : i), op1, op2);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::setPatch(int channel, int patch) {
|
||||
int bank = 0;
|
||||
|
||||
if (_version <= SCI_VERSION_0_LATE && channel == 15) {
|
||||
// The original driver has some parsing related handling for program 127.
|
||||
// We can't handle that here.
|
||||
return;
|
||||
}
|
||||
|
||||
_channels[channel].patch = patch;
|
||||
|
||||
if (patch >= 48) {
|
||||
patch -= 48;
|
||||
bank = 1;
|
||||
}
|
||||
|
||||
for (int voice = 0; voice < _numParts; voice++) {
|
||||
if (_voices[voice].channel == channel) {
|
||||
if (_voices[voice].bank != bank) {
|
||||
_voices[voice].bank = bank;
|
||||
setVoiceParam(voice, 4, bank);
|
||||
}
|
||||
_driver->send(0xc0 | (_version <= SCI_VERSION_0_LATE ? channel : voice), patch, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::voiceOn(int voice, int note, int velocity) {
|
||||
if (_playSwitch) {
|
||||
_voices[voice].note = note;
|
||||
_voices[voice].age = 0;
|
||||
_driver->send(0x90 | voice, note, velocity);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::voiceOff(int voice) {
|
||||
_voices[voice].note = -1;
|
||||
_driver->send(0xb0 | voice, 0x7b, 0x00);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::noteOff(int channel, int note) {
|
||||
int voice;
|
||||
for (voice = 0; voice < kVoices; voice++) {
|
||||
if ((_voices[voice].channel == channel) && (_voices[voice].note == note)) {
|
||||
voiceOff(voice);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::noteOn(int channel, int note, int velocity) {
|
||||
if (velocity == 0)
|
||||
return noteOff(channel, note);
|
||||
|
||||
if (_version > SCI_VERSION_0_LATE)
|
||||
velocity >>= 1;
|
||||
|
||||
int voice;
|
||||
for (voice = 0; voice < kVoices; voice++) {
|
||||
if ((_voices[voice].channel == channel) && (_voices[voice].note == note)) {
|
||||
voiceOff(voice);
|
||||
|
||||
// Original bug #1: The table is only applied here, but not to the voiceOn() call below.
|
||||
// Original bug #2: The velocity value also gets another right shift although the table
|
||||
// size of the volume table is 64 and not 32.
|
||||
// --> disable this ? It certainly can't do any good.
|
||||
if (_version > SCI_VERSION_0_LATE)
|
||||
velocity = volumeTable[velocity >> 1] << 1;
|
||||
|
||||
voiceOn(voice, note, velocity);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
voice = findVoice(channel);
|
||||
|
||||
if (voice == -1) {
|
||||
debug(3, "FB-01: failed to find free voice assigned to channel %i", channel);
|
||||
return;
|
||||
}
|
||||
|
||||
voiceOn(voice, note, velocity);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::controlChange(int channel, int control, int value) {
|
||||
// Events for the control channel shouldn't arrive here.
|
||||
// The original driver handles some specific parsing related midi events
|
||||
// sent on channel 15, but we (hopefully) do that in the SCI music engine.
|
||||
if (_version <= SCI_VERSION_0_LATE && channel == 15)
|
||||
return;
|
||||
|
||||
switch (control) {
|
||||
case 0x07: {
|
||||
_channels[channel].volume = value;
|
||||
|
||||
if (_version > SCI_VERSION_0_LATE)
|
||||
value = volumeTable[value >> 1] << 1;
|
||||
|
||||
#ifdef HARDWARE_MASTERVOLUME
|
||||
sendToChannel(channel, 0xb0, control, value);
|
||||
#else
|
||||
byte vol = _masterVolume;
|
||||
|
||||
if (vol > 0)
|
||||
vol = CLIP<byte>(vol + 3, 0, 15);
|
||||
|
||||
sendToChannel(channel, 0xb0, control, (value * vol / 15) & 0x7f);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 0x0a:
|
||||
_channels[channel].pan = value;
|
||||
sendToChannel(channel, 0xb0, control, value);
|
||||
break;
|
||||
case 0x40:
|
||||
_channels[channel].holdPedal = value;
|
||||
sendToChannel(channel, 0xb0, control, value);
|
||||
break;
|
||||
case 0x4b:
|
||||
voiceMapping(channel, value);
|
||||
break;
|
||||
case 0x7b:
|
||||
for (int i = 0; i < _numParts; i++) {
|
||||
if ((_voices[i].channel == channel) && (_voices[i].note != -1)) {
|
||||
_voices[i].note = -1;
|
||||
sendToChannel(channel, 0xb0, control, value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
sendToChannel(channel, 0xb0, control, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::send(uint32 b) {
|
||||
byte command = b & 0xf0;
|
||||
byte channel = b & 0xf;
|
||||
byte op1 = (b >> 8) & 0x7f;
|
||||
byte op2 = (b >> 16) & 0x7f;
|
||||
|
||||
if (_version <= SCI_VERSION_0_LATE && command != 0xB0 && command != 0xC0) {
|
||||
// Since the voice mapping takes place inside the hardware, most messages
|
||||
// are simply passed through. Channel 15 is never assigned to a part but is
|
||||
// used for certain parsing related events which we cannot handle here.
|
||||
// Just making sure that no nonsense is sent to the device...
|
||||
if (channel != 15)
|
||||
sendToChannel(channel, command, op1, op2);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (command) {
|
||||
case 0x80:
|
||||
noteOff(channel, op1);
|
||||
break;
|
||||
case 0x90:
|
||||
noteOn(channel, op1, op2);
|
||||
break;
|
||||
case 0xb0:
|
||||
controlChange(channel, op1, op2);
|
||||
break;
|
||||
case 0xc0:
|
||||
setPatch(channel, op1);
|
||||
break;
|
||||
case 0xe0:
|
||||
_channels[channel].pitchWheel = (op1 & 0x7f) | ((op2 & 0x7f) << 7);
|
||||
sendToChannel(channel, command, op1, op2);
|
||||
break;
|
||||
default:
|
||||
warning("FB-01: Ignoring MIDI event %02x %02x %02x", command | channel, op1, op2);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::setVolume(byte volume) {
|
||||
_masterVolume = volume;
|
||||
#ifdef HARDWARE_MASTERVOLUME
|
||||
setSystemParam(0, 0x24, (CLIP<byte>(_masterVolume + 3, 0, 15) << 3) + 7);
|
||||
#else
|
||||
for (uint i = 0; i < MIDI_CHANNELS; i++)
|
||||
controlChange(i, 0x07, _channels[i].volume & 0x7f);
|
||||
#endif
|
||||
}
|
||||
|
||||
int MidiPlayer_Fb01::getVolume() {
|
||||
return _masterVolume;
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::playSwitch(bool play) {
|
||||
_playSwitch = play;
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::midiTimerCallback(void *p) {
|
||||
if (!p)
|
||||
return;
|
||||
|
||||
MidiPlayer_Fb01 *m = (MidiPlayer_Fb01 *)p;
|
||||
|
||||
if (!m->isOpen())
|
||||
return;
|
||||
|
||||
// Increase the age of the notes
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
if (m->_voices[i].note != -1)
|
||||
m->_voices[i].age++;
|
||||
}
|
||||
|
||||
if (m->_timerProc)
|
||||
m->_timerProc(m->_timerParam);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
|
||||
_driver->setTimerCallback(nullptr, nullptr);
|
||||
|
||||
_timerParam = timer_param;
|
||||
_timerProc = timer_proc;
|
||||
|
||||
_driver->setTimerCallback(this, midiTimerCallback);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::sendBanks(const SciSpan<const byte> &data) {
|
||||
if (data.size() < 3072)
|
||||
error("Failed to read FB-01 patch");
|
||||
|
||||
// SSCI sends bank dumps containing 48 instruments at once. We cannot do that
|
||||
// due to the limited maximum SysEx length. Instead we send the instruments
|
||||
// one by one and store them in the banks.
|
||||
for (int i = 0; i < 48; i++) {
|
||||
sendVoiceData(0, data.subspan(i * 64));
|
||||
storeVoiceData(0, 0, i);
|
||||
}
|
||||
|
||||
// Send second bank if available
|
||||
if (data.size() < 6146)
|
||||
return;
|
||||
|
||||
if (data.getUint16BEAt(3072) != 0xabcd)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 48; i++) {
|
||||
sendVoiceData(0, data.subspan(3074 + i * 64));
|
||||
storeVoiceData(0, 1, i);
|
||||
}
|
||||
}
|
||||
|
||||
int MidiPlayer_Fb01::open(ResourceManager *resMan) {
|
||||
assert(resMan != nullptr);
|
||||
|
||||
int retval = _driver->open();
|
||||
if (retval != 0) {
|
||||
warning("Failed to open MIDI driver");
|
||||
return retval;
|
||||
}
|
||||
|
||||
// Set system channel to 0.
|
||||
setSystemParam(0, 0x20, 0);
|
||||
|
||||
// Turn off memory protection
|
||||
setSystemParam(0, 0x21, 0);
|
||||
|
||||
Resource *res = resMan->findResource(ResourceId(kResourceTypePatch, 2), false);
|
||||
|
||||
if (res) {
|
||||
sendBanks(*res);
|
||||
} else {
|
||||
// Early SCI0 games have the sound bank embedded in the IMF driver.
|
||||
// Note that these games didn't actually support the FB-01 as a device,
|
||||
// but the IMF, which is the same device on an ISA card. Check:
|
||||
// https://en.wikipedia.org/wiki/IBM_Music_Feature_Card
|
||||
|
||||
warning("FB-01 patch file not found, attempting to load sound bank from IMF.DRV");
|
||||
// Try to load sound bank from IMF.DRV
|
||||
Common::File f;
|
||||
|
||||
if (f.open("IMF.DRV")) {
|
||||
Common::SpanOwner<SciSpan<const byte> > buf;
|
||||
buf->allocateFromStream(f);
|
||||
|
||||
// Search for start of sound bank
|
||||
uint offset;
|
||||
for (offset = 0; offset < buf->size() - 7; ++offset) {
|
||||
if (!strncmp((const char *)buf->getUnsafeDataAt(offset, 7), "SIERRA ", 7))
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip to voice data
|
||||
offset += 0x20;
|
||||
|
||||
if (offset >= buf->size())
|
||||
error("Failed to locate start of FB-01 sound bank");
|
||||
|
||||
// The newer IMF.DRV versions without the sound bank will still have the 32 byte
|
||||
// "SIERRA 1" header, but after that they want to send the patch.002 resource.
|
||||
// These driver version are easy to identify by their small size.
|
||||
if (buf->subspan(offset).size() < 3072) {
|
||||
_missingFiles = _requiredFiles[1];
|
||||
return MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
sendBanks(buf->subspan(offset));
|
||||
} else {
|
||||
_missingFiles = _version == SCI_VERSION_0_EARLY ? _requiredFiles[0] : _requiredFiles[1];
|
||||
return MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
||||
}
|
||||
}
|
||||
|
||||
// Set up voices to use MIDI channels 0 - 7
|
||||
for (int i = 0; i < kVoices; i++)
|
||||
setVoiceParam(i, 1, i);
|
||||
|
||||
initVoices();
|
||||
|
||||
// Set master volume
|
||||
setSystemParam(0, 0x24, 0x7f);
|
||||
|
||||
_isOpen = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::close() {
|
||||
if (_driver)
|
||||
_driver->setTimerCallback(nullptr, nullptr);
|
||||
_isOpen = false;
|
||||
if (_driver)
|
||||
_driver->close();
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::initTrack(SciSpan<const byte>& header) {
|
||||
if (!_isOpen || _version > SCI_VERSION_0_LATE)
|
||||
return;
|
||||
|
||||
uint8 readPos = 0;
|
||||
uint8 caps = header.getInt8At(readPos++);
|
||||
if (caps != 0 && (_version == SCI_VERSION_0_EARLY || caps != 2))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
_voices[i] = Voice();
|
||||
|
||||
_numParts = 0;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
if (_version == SCI_VERSION_0_LATE) {
|
||||
uint8 num = header.getInt8At(readPos++) & 0x7F;
|
||||
uint8 flags = header.getInt8At(readPos++);
|
||||
|
||||
if (flags & 0x02) {
|
||||
_voices[_numParts].channel = i;
|
||||
_voices[_numParts].poly = num;
|
||||
_numParts++;
|
||||
}
|
||||
} else {
|
||||
uint8 val = header.getInt8At(readPos++);
|
||||
if (val & 0x01) {
|
||||
if (val & 0x08) {
|
||||
if (val >> 4)
|
||||
debugC(9, kDebugLevelSound, "MidiPlayer_Fb01::initTrack(): Unused rhythm channel found: 0x%.02x", i);
|
||||
} else if ((val >> 4) != 0x0F) {
|
||||
_voices[_numParts].channel = i;
|
||||
_voices[_numParts].poly = val >> 4;
|
||||
_numParts++;
|
||||
}
|
||||
} else if (val & 0x08) {
|
||||
debugC(9, kDebugLevelSound, "MidiPlayer_Fb01::initTrack(): Control channel found: 0x%.02x", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _numParts; ++i)
|
||||
setVoiceParam(i, 1, _voices[i].channel);
|
||||
|
||||
initVoices();
|
||||
|
||||
setVolume(_masterVolume);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::setVoiceParam(byte voice, byte param, byte value) {
|
||||
_sysExBuf[2] = 0x00;
|
||||
_sysExBuf[3] = 0x18 | voice;
|
||||
_sysExBuf[4] = param;
|
||||
_sysExBuf[5] = value;
|
||||
|
||||
_driver->sysEx(_sysExBuf, 6);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::setSystemParam(byte sysChan, byte param, byte value) {
|
||||
_sysExBuf[2] = sysChan;
|
||||
_sysExBuf[3] = 0x10;
|
||||
_sysExBuf[4] = param;
|
||||
_sysExBuf[5] = value;
|
||||
|
||||
sysEx(_sysExBuf, 6);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::sendVoiceData(byte instrument, const SciSpan<const byte> &data) {
|
||||
_sysExBuf[2] = 0x00;
|
||||
_sysExBuf[3] = 0x08 | instrument;
|
||||
_sysExBuf[4] = 0x00;
|
||||
_sysExBuf[5] = 0x00;
|
||||
_sysExBuf[6] = 0x01;
|
||||
_sysExBuf[7] = 0x00;
|
||||
|
||||
for (int i = 0; i < 64; i++) {
|
||||
_sysExBuf[8 + i * 2] = data[i] & 0xf;
|
||||
_sysExBuf[8 + i * 2 + 1] = data[i] >> 4;
|
||||
}
|
||||
|
||||
byte checksum = 0;
|
||||
for (int i = 8; i < 136; i++)
|
||||
checksum += _sysExBuf[i];
|
||||
|
||||
_sysExBuf[136] = (-checksum) & 0x7f;
|
||||
|
||||
sysEx(_sysExBuf, 137);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::storeVoiceData(byte instrument, byte bank, byte index) {
|
||||
_sysExBuf[2] = 0x00;
|
||||
_sysExBuf[3] = 0x28 | instrument;
|
||||
_sysExBuf[4] = 0x40;
|
||||
_sysExBuf[5] = (bank > 0 ? 48 : 0) + index;
|
||||
|
||||
sysEx(_sysExBuf, 6);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::initVoices() {
|
||||
int i = 2;
|
||||
_sysExBuf[i++] = 0x70;
|
||||
|
||||
// Set all MIDI channels to 0 voices
|
||||
for (int j = 0; j < MIDI_CHANNELS; j++) {
|
||||
_sysExBuf[i++] = 0x70 | j;
|
||||
_sysExBuf[i++] = 0x00;
|
||||
_sysExBuf[i++] = 0x00;
|
||||
}
|
||||
|
||||
// Set up the MIDI channels we will be using
|
||||
for (int j = 0; j < _numParts; j++) {
|
||||
int8 chan = _version > SCI_VERSION_0_LATE ? j : _voices[j].channel;
|
||||
// One voice
|
||||
_sysExBuf[i++] = 0x70 | chan;
|
||||
_sysExBuf[i++] = 0x00;
|
||||
_sysExBuf[i++] = _voices[j].poly;
|
||||
|
||||
// Full range of keys
|
||||
_sysExBuf[i++] = 0x70 | chan;
|
||||
_sysExBuf[i++] = 0x02;
|
||||
_sysExBuf[i++] = 0x7f;
|
||||
_sysExBuf[i++] = 0x70 | chan;
|
||||
_sysExBuf[i++] = 0x03;
|
||||
_sysExBuf[i++] = 0x00;
|
||||
|
||||
// Voice bank 0
|
||||
_sysExBuf[i++] = 0x70 | chan;
|
||||
_sysExBuf[i++] = 0x04;
|
||||
_sysExBuf[i++] = 0x00;
|
||||
|
||||
// Voice 10
|
||||
_sysExBuf[i++] = 0x70 | chan;
|
||||
_sysExBuf[i++] = 0x05;
|
||||
_sysExBuf[i++] = 0x0a;
|
||||
}
|
||||
|
||||
sysEx(_sysExBuf, i);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::sysEx(const byte *msg, uint16 length) {
|
||||
_driver->sysEx(msg, length);
|
||||
|
||||
// Wait the time it takes to send the SysEx data
|
||||
uint32 delay = (length + 2) * 1000 / 3125;
|
||||
|
||||
delay += 10;
|
||||
|
||||
g_system->delayMillis(delay);
|
||||
}
|
||||
|
||||
byte MidiPlayer_Fb01::getPlayId() const {
|
||||
switch (_version) {
|
||||
case SCI_VERSION_0_EARLY:
|
||||
return 0x09;
|
||||
case SCI_VERSION_0_LATE:
|
||||
return 0x02;
|
||||
default:
|
||||
return 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
const char MidiPlayer_Fb01::_requiredFiles[2][12] = {
|
||||
"'IMF.DRV'",
|
||||
"'PATCH.002'"
|
||||
};
|
||||
|
||||
MidiPlayer *MidiPlayer_Fb01_create(SciVersion version) {
|
||||
return new MidiPlayer_Fb01(version);
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#undef HARDWARE_MASTERVOLUME
|
||||
667
engines/sci/sound/drivers/fmtowns.cpp
Normal file
667
engines/sci/sound/drivers/fmtowns.cpp
Normal file
@@ -0,0 +1,667 @@
|
||||
/* 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/sci.h"
|
||||
|
||||
#include "common/file.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
#include "audio/softsynth/fmtowns_pc98/towns_audio.h"
|
||||
|
||||
#include "sci/resource/resource.h"
|
||||
#include "sci/sound/drivers/mididriver.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
class MidiDriver_FMTowns;
|
||||
|
||||
class TownsChannel {
|
||||
public:
|
||||
TownsChannel(MidiDriver_FMTowns *driver, uint8 id);
|
||||
~TownsChannel() {}
|
||||
|
||||
void noteOff();
|
||||
void noteOn(uint8 note, uint8 velo);
|
||||
void pitchBend(int16 val);
|
||||
void updateVolume();
|
||||
void updateDuration();
|
||||
|
||||
uint8 _assign;
|
||||
uint8 _note;
|
||||
uint8 _sustain;
|
||||
uint16 _duration;
|
||||
|
||||
private:
|
||||
uint8 _id;
|
||||
uint8 _velo;
|
||||
uint8 _program;
|
||||
|
||||
MidiDriver_FMTowns *_drv;
|
||||
};
|
||||
|
||||
class TownsMidiPart {
|
||||
friend class MidiDriver_FMTowns;
|
||||
public:
|
||||
TownsMidiPart(MidiDriver_FMTowns *driver, uint8 id);
|
||||
~TownsMidiPart() {}
|
||||
|
||||
void noteOff(uint8 note);
|
||||
void noteOn(uint8 note, uint8 velo);
|
||||
void controlChangeVolume(uint8 vol);
|
||||
void controlChangeSustain(uint8 sus);
|
||||
void controlChangePolyphony(uint8 numChan);
|
||||
void controlChangeAllNotesOff();
|
||||
void programChange(uint8 prg);
|
||||
void pitchBend(int16 val);
|
||||
|
||||
void addChannels(int num);
|
||||
void dropChannels(int num);
|
||||
|
||||
uint8 currentProgram() const;
|
||||
|
||||
private:
|
||||
int allocateChannel();
|
||||
|
||||
uint8 _id;
|
||||
uint8 _program;
|
||||
uint8 _volume;
|
||||
uint8 _sustain;
|
||||
uint8 _chanMissing;
|
||||
int16 _pitchBend;
|
||||
uint8 _outChan;
|
||||
|
||||
MidiDriver_FMTowns *_drv;
|
||||
};
|
||||
|
||||
class MidiDriver_FMTowns : public MidiDriver, public TownsAudioInterfacePluginDriver {
|
||||
friend class TownsChannel;
|
||||
friend class TownsMidiPart;
|
||||
public:
|
||||
MidiDriver_FMTowns(Audio::Mixer *mixer, SciVersion version);
|
||||
~MidiDriver_FMTowns() override;
|
||||
|
||||
int open() override;
|
||||
void loadInstruments(const SciSpan<const uint8> &data);
|
||||
bool isOpen() const override { return _isOpen; }
|
||||
void close() override;
|
||||
|
||||
void send(uint32 b) override;
|
||||
|
||||
uint32 property(int prop, uint32 param) override;
|
||||
void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override;
|
||||
|
||||
void setSoundOn(bool toggle);
|
||||
|
||||
uint32 getBaseTempo() override;
|
||||
MidiChannel *allocateChannel() override { return nullptr; }
|
||||
MidiChannel *getPercussionChannel() override { return nullptr; }
|
||||
|
||||
void timerCallback(int timerId) override;
|
||||
|
||||
private:
|
||||
int getChannelVolume(uint8 midiPart);
|
||||
void addMissingChannels();
|
||||
|
||||
void updateParser();
|
||||
void updateChannels();
|
||||
|
||||
Common::TimerManager::TimerProc _timerProc;
|
||||
void *_timerProcPara;
|
||||
|
||||
TownsMidiPart **_parts;
|
||||
TownsChannel **_out;
|
||||
|
||||
uint8 _masterVolume;
|
||||
|
||||
bool _soundOn;
|
||||
|
||||
bool _isOpen;
|
||||
bool _ready;
|
||||
|
||||
const uint16 _baseTempo;
|
||||
SciVersion _version;
|
||||
|
||||
TownsAudioInterface *_intf;
|
||||
};
|
||||
|
||||
class MidiPlayer_FMTowns : public MidiPlayer {
|
||||
public:
|
||||
MidiPlayer_FMTowns(SciVersion version);
|
||||
~MidiPlayer_FMTowns() override;
|
||||
|
||||
int open(ResourceManager *resMan) override;
|
||||
|
||||
bool hasRhythmChannel() const override;
|
||||
byte getPlayId() const override;
|
||||
int getPolyphony() const override;
|
||||
void playSwitch(bool play) override;
|
||||
|
||||
private:
|
||||
MidiDriver_FMTowns *_townsDriver;
|
||||
};
|
||||
|
||||
TownsChannel::TownsChannel(MidiDriver_FMTowns *driver, uint8 id) : _drv(driver), _id(id), _assign(0xff), _note(0xff), _velo(0), _sustain(0), _duration(0), _program(0xff) {
|
||||
}
|
||||
|
||||
void TownsChannel::noteOn(uint8 note, uint8 velo) {
|
||||
_duration = 0;
|
||||
|
||||
if (_drv->_version != SCI_VERSION_1_EARLY) {
|
||||
if (_program != _drv->_parts[_assign]->currentProgram() && _drv->_soundOn) {
|
||||
_program = _drv->_parts[_assign]->currentProgram();
|
||||
_drv->_intf->callback(4, _id, _program);
|
||||
}
|
||||
}
|
||||
|
||||
_note = note;
|
||||
_velo = velo;
|
||||
_drv->_intf->callback(1, _id, _note, _velo);
|
||||
}
|
||||
|
||||
void TownsChannel::noteOff() {
|
||||
if (_sustain)
|
||||
return;
|
||||
|
||||
_drv->_intf->callback(2, _id);
|
||||
_note = 0xff;
|
||||
_duration = 0;
|
||||
}
|
||||
|
||||
void TownsChannel::pitchBend(int16 val) {
|
||||
_drv->_intf->callback(7, _id, val);
|
||||
}
|
||||
|
||||
void TownsChannel::updateVolume() {
|
||||
if (_assign > 15 && _drv->_version != SCI_VERSION_1_EARLY)
|
||||
return;
|
||||
_drv->_intf->callback(8, _id, _drv->getChannelVolume((_drv->_version == SCI_VERSION_1_EARLY) ? 0 : _assign));
|
||||
}
|
||||
|
||||
void TownsChannel::updateDuration() {
|
||||
if (_note != 0xff)
|
||||
_duration++;
|
||||
}
|
||||
|
||||
TownsMidiPart::TownsMidiPart(MidiDriver_FMTowns *driver, uint8 id) : _drv(driver), _id(id), _program(0), _volume(0x3f), _sustain(0), _chanMissing(0), _pitchBend(0x2000), _outChan(0) {
|
||||
}
|
||||
|
||||
void TownsMidiPart::noteOff(uint8 note) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if ((_drv->_out[i]->_assign != _id && _drv->_version != SCI_VERSION_1_EARLY) || _drv->_out[i]->_note != note)
|
||||
continue;
|
||||
if (_sustain)
|
||||
_drv->_out[i]->_sustain = 1;
|
||||
else
|
||||
_drv->_out[i]->noteOff();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void TownsMidiPart::noteOn(uint8 note, uint8 velo) {
|
||||
if (note < 12 || note > 107)
|
||||
return;
|
||||
|
||||
if (velo == 0) {
|
||||
noteOff(note);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_drv->_version != SCI_VERSION_1_EARLY)
|
||||
velo >>= 1;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if ((_drv->_out[i]->_assign != _id && _drv->_version != SCI_VERSION_1_EARLY) || _drv->_out[i]->_note != note)
|
||||
continue;
|
||||
_drv->_out[i]->_sustain = 0;
|
||||
_drv->_out[i]->noteOff();
|
||||
_drv->_out[i]->noteOn(note, velo);
|
||||
return;
|
||||
}
|
||||
|
||||
int chan = allocateChannel();
|
||||
if (chan != -1)
|
||||
_drv->_out[chan]->noteOn(note, velo);
|
||||
}
|
||||
|
||||
void TownsMidiPart::controlChangeVolume(uint8 vol) {
|
||||
if (_drv->_version == SCI_VERSION_1_EARLY)
|
||||
return;
|
||||
|
||||
_volume = vol >> 1;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_drv->_out[i]->_assign == _id)
|
||||
_drv->_out[i]->updateVolume();
|
||||
}
|
||||
}
|
||||
|
||||
void TownsMidiPart::controlChangeSustain(uint8 sus) {
|
||||
if (_drv->_version == SCI_VERSION_1_EARLY)
|
||||
return;
|
||||
|
||||
_sustain = sus;
|
||||
if (_sustain)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_drv->_out[i]->_assign == _id && _drv->_out[i]->_sustain) {
|
||||
_drv->_out[i]->_sustain = 0;
|
||||
_drv->_out[i]->noteOff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TownsMidiPart::controlChangePolyphony(uint8 numChan) {
|
||||
if (_drv->_version == SCI_VERSION_1_EARLY)
|
||||
return;
|
||||
|
||||
uint8 numAssigned = 0;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_drv->_out[i]->_assign == _id)
|
||||
numAssigned++;
|
||||
}
|
||||
|
||||
numAssigned += _chanMissing;
|
||||
if (numAssigned < numChan) {
|
||||
addChannels(numChan - numAssigned);
|
||||
} else if (numAssigned > numChan) {
|
||||
dropChannels(numAssigned - numChan);
|
||||
_drv->addMissingChannels();
|
||||
}
|
||||
}
|
||||
|
||||
void TownsMidiPart::controlChangeAllNotesOff() {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if ((_drv->_out[i]->_assign == _id || _drv->_version == SCI_VERSION_1_EARLY) && _drv->_out[i]->_note != 0xff)
|
||||
_drv->_out[i]->noteOff();
|
||||
}
|
||||
}
|
||||
|
||||
void TownsMidiPart::programChange(uint8 prg) {
|
||||
_program = prg;
|
||||
}
|
||||
|
||||
void TownsMidiPart::pitchBend(int16 val) {
|
||||
_pitchBend = val;
|
||||
val -= 0x2000;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
// Strangely, the early version driver applies the setting to channel 0 only.
|
||||
if (_drv->_out[i]->_assign == _id || (_drv->_version == SCI_VERSION_1_EARLY && i == 0))
|
||||
_drv->_out[i]->pitchBend(val);
|
||||
}
|
||||
}
|
||||
|
||||
void TownsMidiPart::addChannels(int num) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_drv->_out[i]->_assign != 0xff)
|
||||
continue;
|
||||
|
||||
_drv->_out[i]->_assign = _id;
|
||||
_drv->_out[i]->updateVolume();
|
||||
|
||||
if (_drv->_out[i]->_note != 0xff)
|
||||
_drv->_out[i]->noteOff();
|
||||
|
||||
if (!--num)
|
||||
break;
|
||||
}
|
||||
|
||||
_chanMissing += num;
|
||||
programChange(_program);
|
||||
pitchBend(_pitchBend);
|
||||
controlChangeVolume(_volume << 1);
|
||||
}
|
||||
|
||||
void TownsMidiPart::dropChannels(int num) {
|
||||
if (_chanMissing == num) {
|
||||
_chanMissing = 0;
|
||||
return;
|
||||
} else if (_chanMissing > num) {
|
||||
_chanMissing -= num;
|
||||
return;
|
||||
}
|
||||
|
||||
num -= _chanMissing;
|
||||
_chanMissing = 0;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_drv->_out[i]->_assign != _id || _drv->_out[i]->_note != 0xff)
|
||||
continue;
|
||||
_drv->_out[i]->_assign = 0xff;
|
||||
if (!--num)
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_drv->_out[i]->_assign != _id)
|
||||
continue;
|
||||
_drv->_out[i]->_sustain = 0;
|
||||
_drv->_out[i]->noteOff();
|
||||
_drv->_out[i]->_assign = 0xff;
|
||||
if (!--num)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint8 TownsMidiPart::currentProgram() const {
|
||||
return _program;
|
||||
}
|
||||
|
||||
int TownsMidiPart::allocateChannel() {
|
||||
int chan = _outChan;
|
||||
int ovrChan = 0;
|
||||
int ld = 0;
|
||||
bool found = false;
|
||||
|
||||
for (bool loop = true; loop; ) {
|
||||
if (++chan == 6)
|
||||
chan = 0;
|
||||
|
||||
if (chan == _outChan)
|
||||
loop = false;
|
||||
|
||||
if (_id == _drv->_out[chan]->_assign || _drv->_version == SCI_VERSION_1_EARLY) {
|
||||
if (_drv->_out[chan]->_note == 0xff) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (_drv->_out[chan]->_duration >= ld) {
|
||||
ld = _drv->_out[chan]->_duration;
|
||||
ovrChan = chan;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
if (!ld)
|
||||
return -1;
|
||||
chan = ovrChan;
|
||||
_drv->_out[chan]->_sustain = 0;
|
||||
_drv->_out[chan]->noteOff();
|
||||
}
|
||||
|
||||
_outChan = chan;
|
||||
return chan;
|
||||
}
|
||||
|
||||
MidiDriver_FMTowns::MidiDriver_FMTowns(Audio::Mixer *mixer, SciVersion version) : _version(version), _timerProc(nullptr), _timerProcPara(nullptr), _baseTempo(10080), _ready(false), _isOpen(false), _masterVolume(0x0f), _soundOn(true) {
|
||||
_intf = new TownsAudioInterface(mixer, this, true);
|
||||
_out = new TownsChannel*[6];
|
||||
for (int i = 0; i < 6; i++)
|
||||
_out[i] = new TownsChannel(this, i);
|
||||
_parts = new TownsMidiPart*[16];
|
||||
for (int i = 0; i < 16; i++)
|
||||
_parts[i] = new TownsMidiPart(this, i);
|
||||
}
|
||||
|
||||
MidiDriver_FMTowns::~MidiDriver_FMTowns() {
|
||||
delete _intf;
|
||||
|
||||
if (_parts) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
delete _parts[i];
|
||||
_parts[i] = nullptr;
|
||||
}
|
||||
delete[] _parts;
|
||||
_parts = nullptr;
|
||||
}
|
||||
|
||||
if (_out) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
delete _out[i];
|
||||
_out[i] = nullptr;
|
||||
}
|
||||
delete[] _out;
|
||||
_out = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int MidiDriver_FMTowns::open() {
|
||||
if (_isOpen)
|
||||
return MERR_ALREADY_OPEN;
|
||||
|
||||
if (!_ready) {
|
||||
if (!_intf->init())
|
||||
return MERR_CANNOT_CONNECT;
|
||||
|
||||
_intf->callback(0);
|
||||
|
||||
_intf->callback(21, 255, 1);
|
||||
_intf->callback(21, 0, 1);
|
||||
_intf->callback(22, 255, 221);
|
||||
|
||||
_intf->callback(33, 8);
|
||||
_intf->setSoundEffectChanMask(~0x3f);
|
||||
|
||||
_ready = true;
|
||||
}
|
||||
|
||||
_isOpen = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::loadInstruments(const SciSpan<const uint8> &data) {
|
||||
enum {
|
||||
fmDataSize = 48
|
||||
};
|
||||
|
||||
if (data.size()) {
|
||||
SciSpan<const uint8> instrumentData = data.subspan(6);
|
||||
for (int i = 0; i < 128; i++, instrumentData += fmDataSize) {
|
||||
_intf->callback(5, 0, i, instrumentData.getUnsafeDataAt(0, fmDataSize));
|
||||
}
|
||||
}
|
||||
|
||||
_intf->callback(70, 3);
|
||||
property(MIDI_PROP_MASTER_VOLUME, _masterVolume);
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::close() {
|
||||
_isOpen = false;
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::send(uint32 b) {
|
||||
if (!_isOpen)
|
||||
return;
|
||||
|
||||
byte para2 = (b >> 16) & 0xFF;
|
||||
byte para1 = (b >> 8) & 0xFF;
|
||||
byte cmd = b & 0xF0;
|
||||
|
||||
TownsMidiPart *chan = _parts[b & 0x0F];
|
||||
|
||||
switch (cmd) {
|
||||
case 0x80:
|
||||
chan->noteOff(para1);
|
||||
break;
|
||||
case 0x90:
|
||||
chan->noteOn(para1, para2);
|
||||
break;
|
||||
case 0xb0:
|
||||
switch (para1) {
|
||||
case 7:
|
||||
chan->controlChangeVolume(para2);
|
||||
break;
|
||||
case 64:
|
||||
chan->controlChangeSustain(para2);
|
||||
break;
|
||||
case SCI_MIDI_SET_POLYPHONY:
|
||||
chan->controlChangePolyphony(para2);
|
||||
break;
|
||||
case SCI_MIDI_CHANNEL_NOTES_OFF:
|
||||
chan->controlChangeAllNotesOff();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 0xc0:
|
||||
chan->programChange(para1);
|
||||
break;
|
||||
case 0xe0:
|
||||
chan->pitchBend(para1 | (para2 << 7));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 MidiDriver_FMTowns::property(int prop, uint32 param) {
|
||||
switch(prop) {
|
||||
case MIDI_PROP_MASTER_VOLUME:
|
||||
if (param != 0xffff) {
|
||||
_masterVolume = param;
|
||||
for (int i = 0; i < 6; i++)
|
||||
_out[i]->updateVolume();
|
||||
}
|
||||
return _masterVolume;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
|
||||
_timerProc = timer_proc;
|
||||
_timerProcPara = timer_param;
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::setSoundOn(bool toggle) {
|
||||
_soundOn = toggle;
|
||||
}
|
||||
|
||||
uint32 MidiDriver_FMTowns::getBaseTempo() {
|
||||
return _baseTempo;
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::timerCallback(int timerId) {
|
||||
if (!_isOpen)
|
||||
return;
|
||||
|
||||
switch (timerId) {
|
||||
case 1:
|
||||
updateParser();
|
||||
updateChannels();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int MidiDriver_FMTowns::getChannelVolume(uint8 midiPart) {
|
||||
static const uint8 volumeTable[] = { 0x00, 0x0D, 0x1B, 0x28, 0x36, 0x43, 0x51, 0x5F, 0x63, 0x67, 0x6B, 0x6F, 0x73, 0x77, 0x7B, 0x7F };
|
||||
int tableIndex = (_version == SCI_VERSION_1_EARLY) ? _masterVolume : (_parts[midiPart]->_volume * (_masterVolume + 1)) >> 6;
|
||||
assert(tableIndex < 16);
|
||||
return volumeTable[tableIndex];
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::addMissingChannels() {
|
||||
uint8 avlChan = 0;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_out[i]->_assign == 0xff)
|
||||
avlChan++;
|
||||
}
|
||||
|
||||
if (!avlChan)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (!_parts[i]->_chanMissing)
|
||||
continue;
|
||||
|
||||
if (_parts[i]->_chanMissing < avlChan) {
|
||||
avlChan -= _parts[i]->_chanMissing;
|
||||
uint8 m = _parts[i]->_chanMissing;
|
||||
_parts[i]->_chanMissing = 0;
|
||||
_parts[i]->addChannels(m);
|
||||
} else {
|
||||
_parts[i]->_chanMissing -= avlChan;
|
||||
_parts[i]->addChannels(avlChan);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::updateParser() {
|
||||
if (_timerProc)
|
||||
_timerProc(_timerProcPara);
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::updateChannels() {
|
||||
for (int i = 0; i < 6; i++)
|
||||
_out[i]->updateDuration();
|
||||
}
|
||||
|
||||
MidiPlayer_FMTowns::MidiPlayer_FMTowns(SciVersion version) : MidiPlayer(version) {
|
||||
_driver = _townsDriver = new MidiDriver_FMTowns(g_system->getMixer(), version);
|
||||
}
|
||||
|
||||
MidiPlayer_FMTowns::~MidiPlayer_FMTowns() {
|
||||
delete _driver;
|
||||
}
|
||||
|
||||
int MidiPlayer_FMTowns::open(ResourceManager *resMan) {
|
||||
int result = MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
||||
if (_townsDriver) {
|
||||
result = _townsDriver->open();
|
||||
if (!result && _version == SCI_VERSION_1_LATE) {
|
||||
Resource *res = resMan->findResource(ResourceId(kResourceTypePatch, 8), false);
|
||||
if (res != nullptr) {
|
||||
_townsDriver->loadInstruments(*res);
|
||||
} else {
|
||||
warning("MidiPlayer_FMTowns: Failed to open patch 8");
|
||||
result = MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MidiPlayer_FMTowns::hasRhythmChannel() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte MidiPlayer_FMTowns::getPlayId() const {
|
||||
return (_version == SCI_VERSION_1_EARLY) ? 0x00 : 0x16;
|
||||
}
|
||||
|
||||
int MidiPlayer_FMTowns::getPolyphony() const {
|
||||
// WORKAROUND:
|
||||
// I set the return value to 16 for SCI_VERSION_1_EARLY here, which fixes music playback in Mixed Up Mothergoose.
|
||||
// This has been broken since the introduction of SciMusic::remapChannels() and the corresponding code.
|
||||
// The original code of Mixed Up Mothergoose code doesn't have the remapping and doesn't seem to check the polyphony
|
||||
// setting ever. So the value of 1 was probably incorrect.
|
||||
return (_version == SCI_VERSION_1_EARLY) ? 16 : 6;
|
||||
}
|
||||
|
||||
void MidiPlayer_FMTowns::playSwitch(bool play) {
|
||||
if (_townsDriver)
|
||||
_townsDriver->setSoundOn(play);
|
||||
}
|
||||
|
||||
MidiPlayer *MidiPlayer_FMTowns_create(SciVersion _soundVersion) {
|
||||
return new MidiPlayer_FMTowns(_soundVersion);
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
223
engines/sci/sound/drivers/gm_names.h
Normal file
223
engines/sci/sound/drivers/gm_names.h
Normal file
@@ -0,0 +1,223 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_SOUND_DRIVERS_GM_NAMES_H
|
||||
#define SCI_SOUND_DRIVERS_GM_NAMES_H
|
||||
|
||||
namespace Sci {
|
||||
|
||||
// These tables are only used for debugging. Don't include them for devices
|
||||
// with not enough available memory (e.g. phones), where REDUCE_MEMORY_USAGE
|
||||
// is defined
|
||||
#ifndef REDUCE_MEMORY_USAGE
|
||||
|
||||
static const char *const GmInstrumentNames[] = {
|
||||
/*000*/ "Acoustic Grand Piano",
|
||||
/*001*/ "Bright Acoustic Piano",
|
||||
/*002*/ "Electric Grand Piano",
|
||||
/*003*/ "Honky-tonk Piano",
|
||||
/*004*/ "Electric Piano 1",
|
||||
/*005*/ "Electric Piano 2",
|
||||
/*006*/ "Harpsichord",
|
||||
/*007*/ "Clavinet",
|
||||
/*008*/ "Celesta",
|
||||
/*009*/ "Glockenspiel",
|
||||
/*010*/ "Music Box",
|
||||
/*011*/ "Vibraphone",
|
||||
/*012*/ "Marimba",
|
||||
/*013*/ "Xylophone",
|
||||
/*014*/ "Tubular Bells",
|
||||
/*015*/ "Dulcimer",
|
||||
/*016*/ "Drawbar Organ",
|
||||
/*017*/ "Percussive Organ",
|
||||
/*018*/ "Rock Organ",
|
||||
/*019*/ "Church Organ",
|
||||
/*020*/ "Reed Organ",
|
||||
/*021*/ "Accordion",
|
||||
/*022*/ "Harmonica",
|
||||
/*023*/ "Tango Accordion",
|
||||
/*024*/ "Acoustic Guitar (nylon)",
|
||||
/*025*/ "Acoustic Guitar (steel)",
|
||||
/*026*/ "Electric Guitar (jazz)",
|
||||
/*027*/ "Electric Guitar (clean)",
|
||||
/*028*/ "Electric Guitar (muted)",
|
||||
/*029*/ "Overdriven Guitar",
|
||||
/*030*/ "Distortion Guitar",
|
||||
/*031*/ "Guitar Harmonics",
|
||||
/*032*/ "Acoustic Bass",
|
||||
/*033*/ "Electric Bass (finger)",
|
||||
/*034*/ "Electric Bass (pick)",
|
||||
/*035*/ "Fretless Bass",
|
||||
/*036*/ "Slap Bass 1",
|
||||
/*037*/ "Slap Bass 2",
|
||||
/*038*/ "Synth Bass 1",
|
||||
/*039*/ "Synth Bass 2",
|
||||
/*040*/ "Violin",
|
||||
/*041*/ "Viola",
|
||||
/*042*/ "Cello",
|
||||
/*043*/ "Contrabass",
|
||||
/*044*/ "Tremolo Strings",
|
||||
/*045*/ "Pizzicato Strings",
|
||||
/*046*/ "Orchestral Harp",
|
||||
/*047*/ "Timpani",
|
||||
/*048*/ "String Ensemble 1",
|
||||
/*049*/ "String Ensemble 2",
|
||||
/*050*/ "SynthStrings 1",
|
||||
/*051*/ "SynthStrings 2",
|
||||
/*052*/ "Choir Aahs",
|
||||
/*053*/ "Voice Oohs",
|
||||
/*054*/ "Synth Voice",
|
||||
/*055*/ "Orchestra Hit",
|
||||
/*056*/ "Trumpet",
|
||||
/*057*/ "Trombone",
|
||||
/*058*/ "Tuba",
|
||||
/*059*/ "Muted Trumpet",
|
||||
/*060*/ "French Horn",
|
||||
/*061*/ "Brass Section",
|
||||
/*062*/ "SynthBrass 1",
|
||||
/*063*/ "SynthBrass 2",
|
||||
/*064*/ "Soprano Sax",
|
||||
/*065*/ "Alto Sax",
|
||||
/*066*/ "Tenor Sax",
|
||||
/*067*/ "Baritone Sax",
|
||||
/*068*/ "Oboe",
|
||||
/*069*/ "English Horn",
|
||||
/*070*/ "Bassoon",
|
||||
/*071*/ "Clarinet",
|
||||
/*072*/ "Piccolo",
|
||||
/*073*/ "Flute",
|
||||
/*074*/ "Recorder",
|
||||
/*075*/ "Pan Flute",
|
||||
/*076*/ "Blown Bottle",
|
||||
/*077*/ "Shakuhachi",
|
||||
/*078*/ "Whistle",
|
||||
/*079*/ "Ocarina",
|
||||
/*080*/ "Lead 1 (square)",
|
||||
/*081*/ "Lead 2 (sawtooth)",
|
||||
/*082*/ "Lead 3 (calliope)",
|
||||
/*083*/ "Lead 4 (chiff)",
|
||||
/*084*/ "Lead 5 (charang)",
|
||||
/*085*/ "Lead 6 (voice)",
|
||||
/*086*/ "Lead 7 (fifths)",
|
||||
/*087*/ "Lead 8 (bass+lead)",
|
||||
/*088*/ "Pad 1 (new age)",
|
||||
/*089*/ "Pad 2 (warm)",
|
||||
/*090*/ "Pad 3 (polysynth)",
|
||||
/*091*/ "Pad 4 (choir)",
|
||||
/*092*/ "Pad 5 (bowed)",
|
||||
/*093*/ "Pad 6 (metallic)",
|
||||
/*094*/ "Pad 7 (halo)",
|
||||
/*095*/ "Pad 8 (sweep)",
|
||||
/*096*/ "FX 1 (rain)",
|
||||
/*097*/ "FX 2 (soundtrack)",
|
||||
/*098*/ "FX 3 (crystal)",
|
||||
/*099*/ "FX 4 (atmosphere)",
|
||||
/*100*/ "FX 5 (brightness)",
|
||||
/*101*/ "FX 6 (goblins)",
|
||||
/*102*/ "FX 7 (echoes)",
|
||||
/*103*/ "FX 8 (sci-fi)",
|
||||
/*104*/ "Sitar",
|
||||
/*105*/ "Banjo",
|
||||
/*106*/ "Shamisen",
|
||||
/*107*/ "Koto",
|
||||
/*108*/ "Kalimba",
|
||||
/*109*/ "Bag pipe",
|
||||
/*110*/ "Fiddle",
|
||||
/*111*/ "Shannai",
|
||||
/*112*/ "Tinkle Bell",
|
||||
/*113*/ "Agogo",
|
||||
/*114*/ "Steel Drums",
|
||||
/*115*/ "Woodblock",
|
||||
/*116*/ "Taiko Drum",
|
||||
/*117*/ "Melodic Tom",
|
||||
/*118*/ "Synth Drum",
|
||||
/*119*/ "Reverse Cymbal",
|
||||
/*120*/ "Guitar Fret Noise",
|
||||
/*121*/ "Breath Noise",
|
||||
/*122*/ "Seashore",
|
||||
/*123*/ "Bird Tweet",
|
||||
/*124*/ "Telephone Ring",
|
||||
/*125*/ "Helicopter",
|
||||
/*126*/ "Applause",
|
||||
/*127*/ "Gunshot"
|
||||
};
|
||||
|
||||
// The GM Percussion map is downwards compatible to the MT32 map, which is used in SCI
|
||||
static const char *const GmPercussionNames[] = {
|
||||
/*00*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/*10*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/*20*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/*30*/ 0, 0, 0, 0, 0,
|
||||
// The preceding percussions are not covered by the GM standard
|
||||
/*35*/ "Acoustic Bass Drum",
|
||||
/*36*/ "Bass Drum 1",
|
||||
/*37*/ "Side Stick",
|
||||
/*38*/ "Acoustic Snare",
|
||||
/*39*/ "Hand Clap",
|
||||
/*40*/ "Electric Snare",
|
||||
/*41*/ "Low Floor Tom",
|
||||
/*42*/ "Closed Hi-Hat",
|
||||
/*43*/ "High Floor Tom",
|
||||
/*44*/ "Pedal Hi-Hat",
|
||||
/*45*/ "Low Tom",
|
||||
/*46*/ "Open Hi-Hat",
|
||||
/*47*/ "Low-Mid Tom",
|
||||
/*48*/ "Hi-Mid Tom",
|
||||
/*49*/ "Crash Cymbal 1",
|
||||
/*50*/ "High Tom",
|
||||
/*51*/ "Ride Cymbal 1",
|
||||
/*52*/ "Chinese Cymbal",
|
||||
/*53*/ "Ride Bell",
|
||||
/*54*/ "Tambourine",
|
||||
/*55*/ "Splash Cymbal",
|
||||
/*56*/ "Cowbell",
|
||||
/*57*/ "Crash Cymbal 2",
|
||||
/*58*/ "Vibraslap",
|
||||
/*59*/ "Ride Cymbal 2",
|
||||
/*60*/ "Hi Bongo",
|
||||
/*61*/ "Low Bongo",
|
||||
/*62*/ "Mute Hi Conga",
|
||||
/*63*/ "Open Hi Conga",
|
||||
/*64*/ "Low Conga",
|
||||
/*65*/ "High Timbale",
|
||||
/*66*/ "Low Timbale",
|
||||
/*67*/ "High Agogo",
|
||||
/*68*/ "Low Agogo",
|
||||
/*69*/ "Cabasa",
|
||||
/*70*/ "Maracas",
|
||||
/*71*/ "Short Whistle",
|
||||
/*72*/ "Long Whistle",
|
||||
/*73*/ "Short Guiro",
|
||||
/*74*/ "Long Guiro",
|
||||
/*75*/ "Claves",
|
||||
/*76*/ "Hi Wood Block",
|
||||
/*77*/ "Low Wood Block",
|
||||
/*78*/ "Mute Cuica",
|
||||
/*79*/ "Open Cuica",
|
||||
/*80*/ "Mute Triangle",
|
||||
/*81*/ "Open Triangle"
|
||||
};
|
||||
|
||||
#endif // REDUCE_MEMORY_USAGE
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_SOUND_DRIVERS_GM_NAMES_H
|
||||
272
engines/sci/sound/drivers/macmixer.h
Normal file
272
engines/sci/sound/drivers/macmixer.h
Normal file
@@ -0,0 +1,272 @@
|
||||
/* 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 "audio/audiostream.h"
|
||||
#include "audio/mixer.h"
|
||||
#include "common/frac.h"
|
||||
#include "common/mutex.h"
|
||||
#include "common/system.h"
|
||||
|
||||
#ifndef SCI_SOUND_DRIVERS_MACMIXER_H
|
||||
#define SCI_SOUND_DRIVERS_MACMIXER_H
|
||||
|
||||
namespace Sci {
|
||||
|
||||
// Unsigned version of frac_t
|
||||
typedef uint32 ufrac_t;
|
||||
static inline ufrac_t uintToUfrac(uint16 value) { return value << FRAC_BITS; }
|
||||
static inline uint16 ufracToUint(ufrac_t value) { return value >> FRAC_BITS; }
|
||||
|
||||
template <typename T>
|
||||
class Mixer_Mac : public Audio::AudioStream {
|
||||
public:
|
||||
enum {
|
||||
kChannels = 4,
|
||||
kInterruptFreq = 60
|
||||
};
|
||||
|
||||
enum Mode {
|
||||
kModeAuthentic,
|
||||
kModeHq,
|
||||
kModeHqStereo
|
||||
};
|
||||
|
||||
Mixer_Mac(Mode mode);
|
||||
void startMixer();
|
||||
void stopMixer();
|
||||
void setMixerVolume(byte volume) { _mixVolume = volume; }
|
||||
void resetChannel(uint channel);
|
||||
void resetChannels();
|
||||
// NOTE: Last sample accessed is data[endOffset + 1] in kModeHq(Stereo)
|
||||
void setChannelData(uint channel, const byte *data, uint16 startOffset, uint16 endOffset, uint16 loopLength = 0);
|
||||
void setChannelStep(uint channel, ufrac_t step);
|
||||
void setChannelVolume(uint channel, byte volume);
|
||||
void setChannelPan(uint channel, byte pan);
|
||||
|
||||
// AudioStream
|
||||
bool isStereo() const override { return _mode == kModeHqStereo; }
|
||||
int getRate() const override { return (_mode == kModeAuthentic ? 11127 : g_system->getMixer()->getOutputRate()); }
|
||||
int readBuffer(int16 *data, const int numSamples) override;
|
||||
bool endOfData() const override { return false; }
|
||||
|
||||
Common::Mutex _mutex;
|
||||
|
||||
private:
|
||||
template <Mode mode>
|
||||
void generateSamples(int16 *buf, int len);
|
||||
|
||||
struct Channel {
|
||||
ufrac_t pos;
|
||||
ufrac_t step;
|
||||
const byte *data;
|
||||
uint16 endOffset;
|
||||
uint16 loopLength;
|
||||
byte volume;
|
||||
int8 pan;
|
||||
};
|
||||
|
||||
ufrac_t _nextTick;
|
||||
ufrac_t _samplesPerTick;
|
||||
bool _isPlaying;
|
||||
const Mode _mode;
|
||||
Channel _mixChannels[kChannels];
|
||||
byte _mixVolume;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
Mixer_Mac<T>::Mixer_Mac(Mode mode) :
|
||||
_nextTick(0),
|
||||
_samplesPerTick(0),
|
||||
_mode(mode),
|
||||
_isPlaying(false),
|
||||
_mixChannels(),
|
||||
_mixVolume(8) {}
|
||||
|
||||
template <typename T>
|
||||
void Mixer_Mac<T>::startMixer() {
|
||||
_nextTick = _samplesPerTick = uintToUfrac(getRate() / kInterruptFreq) + uintToUfrac(getRate() % kInterruptFreq) / kInterruptFreq;
|
||||
|
||||
resetChannels();
|
||||
_isPlaying = true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Mixer_Mac<T>::stopMixer() {
|
||||
resetChannels();
|
||||
_isPlaying = false;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Mixer_Mac<T>::setChannelData(uint channel, const byte *data, uint16 startOffset, uint16 endOffset, uint16 loopLength) {
|
||||
assert(channel < kChannels);
|
||||
|
||||
Channel &ch = _mixChannels[channel];
|
||||
|
||||
ch.data = data;
|
||||
ch.pos = uintToUfrac(startOffset);
|
||||
ch.endOffset = endOffset;
|
||||
ch.loopLength = loopLength;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Mixer_Mac<T>::setChannelStep(uint channel, ufrac_t step) {
|
||||
assert(channel < kChannels);
|
||||
|
||||
if (_mode == kModeAuthentic) {
|
||||
_mixChannels[channel].step = step;
|
||||
} else {
|
||||
// We could take 11127Hz here, but it appears the original steps were
|
||||
// computed for 11000Hz
|
||||
// FIXME: One or two more bits of step precision might be nice here
|
||||
_mixChannels[channel].step = (ufrac_t)(step * 11000ULL / getRate());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Mixer_Mac<T>::setChannelVolume(uint channel, byte volume) {
|
||||
assert(channel < kChannels);
|
||||
_mixChannels[channel].volume = volume;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Mixer_Mac<T>::setChannelPan(uint channel, byte pan) {
|
||||
assert(channel < kChannels);
|
||||
_mixChannels[channel].pan = pan;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename Mixer_Mac<T>::Mode mode>
|
||||
void Mixer_Mac<T>::generateSamples(int16 *data, int len) {
|
||||
for (int i = 0; i < len; ++i) {
|
||||
int32 mixL = 0;
|
||||
int32 mixR = 0;
|
||||
|
||||
for (int ci = 0; ci < kChannels; ++ci) {
|
||||
Channel &ch = _mixChannels[ci];
|
||||
|
||||
if (!ch.data)
|
||||
continue;
|
||||
|
||||
const uint16 curOffset = ufracToUint(ch.pos);
|
||||
|
||||
if (mode == kModeHq || mode == kModeHqStereo) {
|
||||
int32 sample = (ch.data[curOffset] - 0x80) << 8;
|
||||
// Since _extraSamples > 0, we can safely access this sample
|
||||
const int32 sample2 = (ch.data[curOffset + 1] - 0x80) << 8;
|
||||
sample += fracToInt((sample2 - sample) * (ch.pos & FRAC_LO_MASK));
|
||||
sample *= ch.volume;
|
||||
|
||||
if (mode == kModeHqStereo) {
|
||||
mixL += sample * (127 - ch.pan) / (63 * 64);
|
||||
mixR += sample * ch.pan / (63 * 64);
|
||||
} else {
|
||||
mixL += sample / 63;
|
||||
}
|
||||
} else {
|
||||
mixL += static_cast<T *>(this)->applyChannelVolume(ch.volume, ch.data[curOffset]) << 8;
|
||||
}
|
||||
|
||||
ch.pos += ch.step;
|
||||
|
||||
if (ufracToUint(ch.pos) > ch.endOffset) {
|
||||
if (ch.loopLength > 0) {
|
||||
do {
|
||||
ch.pos -= uintToUfrac(ch.loopLength);
|
||||
} while (ufracToUint(ch.pos) > ch.endOffset);
|
||||
} else {
|
||||
static_cast<T *>(this)->onChannelFinished(ci);
|
||||
ch.data = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*data++ = (int16)CLIP<int32>(mixL, -32768, 32767) * _mixVolume / 8;
|
||||
if (mode == kModeHqStereo)
|
||||
*data++ = (int16)CLIP<int32>(mixR, -32768, 32767) * _mixVolume / 8;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int Mixer_Mac<T>::readBuffer(int16 *data, const int numSamples) {
|
||||
// Would probably be better inside generateSamples, but let's follow Audio::Paula
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
if (!_isPlaying) {
|
||||
memset(data, 0, numSamples * 2);
|
||||
return numSamples;
|
||||
}
|
||||
|
||||
const int stereoFactor = isStereo() ? 2 : 1;
|
||||
int len = numSamples / stereoFactor;
|
||||
|
||||
do {
|
||||
int step = len;
|
||||
if (step > ufracToUint(_nextTick))
|
||||
step = ufracToUint(_nextTick);
|
||||
|
||||
switch (_mode) {
|
||||
case kModeAuthentic:
|
||||
generateSamples<kModeAuthentic>(data, step);
|
||||
break;
|
||||
case kModeHq:
|
||||
generateSamples<kModeHq>(data, step);
|
||||
break;
|
||||
case kModeHqStereo:
|
||||
generateSamples<kModeHqStereo>(data, step);
|
||||
}
|
||||
|
||||
_nextTick -= uintToUfrac(step);
|
||||
if (ufracToUint(_nextTick) == 0) {
|
||||
static_cast<T *>(this)->interrupt();
|
||||
_nextTick += _samplesPerTick;
|
||||
}
|
||||
|
||||
data += step * stereoFactor;
|
||||
len -= step;
|
||||
} while (len);
|
||||
|
||||
return numSamples;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Mixer_Mac<T>::resetChannel(uint channel) {
|
||||
assert(channel < kChannels);
|
||||
|
||||
Channel &ch = _mixChannels[channel];
|
||||
|
||||
ch.pos = 0;
|
||||
ch.step = 0;
|
||||
ch.data = nullptr;
|
||||
ch.endOffset = 0;
|
||||
ch.loopLength = 0;
|
||||
ch.volume = 0;
|
||||
ch.pan = 64;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Mixer_Mac<T>::resetChannels() {
|
||||
for (uint ci = 0; ci < kChannels; ++ci)
|
||||
resetChannel(ci);
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_SOUND_DRIVERS_MACMIXER_H
|
||||
373
engines/sci/sound/drivers/map-mt32-to-gm.h
Normal file
373
engines/sci/sound/drivers/map-mt32-to-gm.h
Normal file
@@ -0,0 +1,373 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_SOUND_DRIVERS_MAP_MT32_TO_GM_H
|
||||
#define SCI_SOUND_DRIVERS_MAP_MT32_TO_GM_H
|
||||
|
||||
namespace Sci {
|
||||
|
||||
#include "common/list.h"
|
||||
|
||||
// Patch not mapped
|
||||
#define MIDI_UNMAPPED 0xff
|
||||
// Patch mapped to rhythm key
|
||||
#define MIDI_MAPPED_TO_RHYTHM 0xfe
|
||||
|
||||
struct Mt32ToGmMap {
|
||||
const char *name;
|
||||
uint8 gmInstr;
|
||||
uint8 gmRhythmKey;
|
||||
};
|
||||
|
||||
/*******************************************
|
||||
* Fancy instrument mappings begin here... *
|
||||
*******************************************/
|
||||
|
||||
|
||||
static const Mt32ToGmMap Mt32PresetTimbreMaps[] = {
|
||||
/*000*/ {"AcouPiano1", 0, MIDI_UNMAPPED},
|
||||
/*001*/ {"AcouPiano2", 1, MIDI_UNMAPPED},
|
||||
/*002*/ {"AcouPiano3", 0, MIDI_UNMAPPED},
|
||||
/*003*/ {"ElecPiano1", 4, MIDI_UNMAPPED},
|
||||
/*004*/ {"ElecPiano2", 5, MIDI_UNMAPPED},
|
||||
/*005*/ {"ElecPiano3", 4, MIDI_UNMAPPED},
|
||||
/*006*/ {"ElecPiano4", 5, MIDI_UNMAPPED},
|
||||
/*007*/ {"Honkytonk ", 3, MIDI_UNMAPPED},
|
||||
/*008*/ {"Elec Org 1", 16, MIDI_UNMAPPED},
|
||||
/*009*/ {"Elec Org 2", 17, MIDI_UNMAPPED},
|
||||
/*010*/ {"Elec Org 3", 18, MIDI_UNMAPPED},
|
||||
/*011*/ {"Elec Org 4", 18, MIDI_UNMAPPED},
|
||||
/*012*/ {"Pipe Org 1", 19, MIDI_UNMAPPED},
|
||||
/*013*/ {"Pipe Org 2", 19, MIDI_UNMAPPED},
|
||||
/*014*/ {"Pipe Org 3", 20, MIDI_UNMAPPED},
|
||||
/*015*/ {"Accordion ", 21, MIDI_UNMAPPED},
|
||||
/*016*/ {"Harpsi 1 ", 6, MIDI_UNMAPPED},
|
||||
/*017*/ {"Harpsi 2 ", 6, MIDI_UNMAPPED},
|
||||
/*018*/ {"Harpsi 3 ", 6, MIDI_UNMAPPED},
|
||||
/*019*/ {"Clavi 1 ", 7, MIDI_UNMAPPED},
|
||||
/*020*/ {"Clavi 2 ", 7, MIDI_UNMAPPED},
|
||||
/*021*/ {"Clavi 3 ", 7, MIDI_UNMAPPED},
|
||||
/*022*/ {"Celesta 1 ", 8, MIDI_UNMAPPED},
|
||||
/*023*/ {"Celesta 2 ", 8, MIDI_UNMAPPED},
|
||||
/*024*/ {"Syn Brass1", 62, MIDI_UNMAPPED},
|
||||
/*025*/ {"Syn Brass2", 63, MIDI_UNMAPPED},
|
||||
/*026*/ {"Syn Brass3", 62, MIDI_UNMAPPED},
|
||||
/*027*/ {"Syn Brass4", 63, MIDI_UNMAPPED},
|
||||
/*028*/ {"Syn Bass 1", 38, MIDI_UNMAPPED},
|
||||
/*029*/ {"Syn Bass 2", 39, MIDI_UNMAPPED},
|
||||
/*030*/ {"Syn Bass 3", 38, MIDI_UNMAPPED},
|
||||
/*031*/ {"Syn Bass 4", 39, MIDI_UNMAPPED},
|
||||
/*032*/ {"Fantasy ", 88, MIDI_UNMAPPED},
|
||||
/*033*/ {"Harmo Pan ", 89, MIDI_UNMAPPED},
|
||||
/*034*/ {"Chorale ", 52, MIDI_UNMAPPED},
|
||||
/*035*/ {"Glasses ", 98, MIDI_UNMAPPED},
|
||||
/*036*/ {"Soundtrack", 97, MIDI_UNMAPPED},
|
||||
/*037*/ {"Atmosphere", 99, MIDI_UNMAPPED},
|
||||
/*038*/ {"Warm Bell ", 89, MIDI_UNMAPPED},
|
||||
/*039*/ {"Funny Vox ", 85, MIDI_UNMAPPED},
|
||||
/*040*/ {"Echo Bell ", 39, MIDI_UNMAPPED},
|
||||
/*041*/ {"Ice Rain ", 101, MIDI_UNMAPPED},
|
||||
/*042*/ {"Oboe 2001 ", 68, MIDI_UNMAPPED},
|
||||
/*043*/ {"Echo Pan ", 87, MIDI_UNMAPPED},
|
||||
/*044*/ {"DoctorSolo", 86, MIDI_UNMAPPED},
|
||||
/*045*/ {"Schooldaze", 103, MIDI_UNMAPPED},
|
||||
/*046*/ {"BellSinger", 88, MIDI_UNMAPPED},
|
||||
/*047*/ {"SquareWave", 80, MIDI_UNMAPPED},
|
||||
/*048*/ {"Str Sect 1", 48, MIDI_UNMAPPED},
|
||||
/*049*/ {"Str Sect 2", 48, MIDI_UNMAPPED},
|
||||
/*050*/ {"Str Sect 3", 49, MIDI_UNMAPPED},
|
||||
/*051*/ {"Pizzicato ", 45, MIDI_UNMAPPED},
|
||||
/*052*/ {"Violin 1 ", 40, MIDI_UNMAPPED},
|
||||
/*053*/ {"Violin 2 ", 40, MIDI_UNMAPPED},
|
||||
/*054*/ {"Cello 1 ", 42, MIDI_UNMAPPED},
|
||||
/*055*/ {"Cello 2 ", 42, MIDI_UNMAPPED},
|
||||
/*056*/ {"Contrabass", 43, MIDI_UNMAPPED},
|
||||
/*057*/ {"Harp 1 ", 46, MIDI_UNMAPPED},
|
||||
/*058*/ {"Harp 2 ", 46, MIDI_UNMAPPED},
|
||||
/*059*/ {"Guitar 1 ", 24, MIDI_UNMAPPED},
|
||||
/*060*/ {"Guitar 2 ", 25, MIDI_UNMAPPED},
|
||||
/*061*/ {"Elec Gtr 1", 26, MIDI_UNMAPPED},
|
||||
/*062*/ {"Elec Gtr 2", 27, MIDI_UNMAPPED},
|
||||
/*063*/ {"Sitar ", 104, MIDI_UNMAPPED},
|
||||
/*064*/ {"Acou Bass1", 32, MIDI_UNMAPPED},
|
||||
/*065*/ {"Acou Bass2", 33, MIDI_UNMAPPED},
|
||||
/*066*/ {"Elec Bass1", 34, MIDI_UNMAPPED},
|
||||
/*067*/ {"Elec Bass2", 39, MIDI_UNMAPPED},
|
||||
/*068*/ {"Slap Bass1", 36, MIDI_UNMAPPED},
|
||||
/*069*/ {"Slap Bass2", 37, MIDI_UNMAPPED},
|
||||
/*070*/ {"Fretless 1", 35, MIDI_UNMAPPED},
|
||||
/*071*/ {"Fretless 2", 35, MIDI_UNMAPPED},
|
||||
/*072*/ {"Flute 1 ", 73, MIDI_UNMAPPED},
|
||||
/*073*/ {"Flute 2 ", 73, MIDI_UNMAPPED},
|
||||
/*074*/ {"Piccolo 1 ", 72, MIDI_UNMAPPED},
|
||||
/*075*/ {"Piccolo 2 ", 72, MIDI_UNMAPPED},
|
||||
/*076*/ {"Recorder ", 74, MIDI_UNMAPPED},
|
||||
/*077*/ {"Panpipes ", 75, MIDI_UNMAPPED},
|
||||
/*078*/ {"Sax 1 ", 64, MIDI_UNMAPPED},
|
||||
/*079*/ {"Sax 2 ", 65, MIDI_UNMAPPED},
|
||||
/*080*/ {"Sax 3 ", 66, MIDI_UNMAPPED},
|
||||
/*081*/ {"Sax 4 ", 67, MIDI_UNMAPPED},
|
||||
/*082*/ {"Clarinet 1", 71, MIDI_UNMAPPED},
|
||||
/*083*/ {"Clarinet 2", 71, MIDI_UNMAPPED},
|
||||
/*084*/ {"Oboe ", 68, MIDI_UNMAPPED},
|
||||
/*085*/ {"Engl Horn ", 69, MIDI_UNMAPPED},
|
||||
/*086*/ {"Bassoon ", 70, MIDI_UNMAPPED},
|
||||
/*087*/ {"Harmonica ", 22, MIDI_UNMAPPED},
|
||||
/*088*/ {"Trumpet 1 ", 56, MIDI_UNMAPPED},
|
||||
/*089*/ {"Trumpet 2 ", 56, MIDI_UNMAPPED},
|
||||
/*090*/ {"Trombone 1", 57, MIDI_UNMAPPED},
|
||||
/*091*/ {"Trombone 2", 57, MIDI_UNMAPPED},
|
||||
/*092*/ {"Fr Horn 1 ", 60, MIDI_UNMAPPED},
|
||||
/*093*/ {"Fr Horn 2 ", 60, MIDI_UNMAPPED},
|
||||
/*094*/ {"Tuba ", 58, MIDI_UNMAPPED},
|
||||
/*095*/ {"Brs Sect 1", 61, MIDI_UNMAPPED},
|
||||
/*096*/ {"Brs Sect 2", 61, MIDI_UNMAPPED},
|
||||
/*097*/ {"Vibe 1 ", 11, MIDI_UNMAPPED},
|
||||
/*098*/ {"Vibe 2 ", 11, MIDI_UNMAPPED},
|
||||
/*099*/ {"Syn Mallet", 15, MIDI_UNMAPPED},
|
||||
/*100*/ {"Wind Bell ", 88, MIDI_UNMAPPED},
|
||||
/*101*/ {"Glock ", 9, MIDI_UNMAPPED},
|
||||
/*102*/ {"Tube Bell ", 14, MIDI_UNMAPPED},
|
||||
/*103*/ {"Xylophone ", 13, MIDI_UNMAPPED},
|
||||
/*104*/ {"Marimba ", 12, MIDI_UNMAPPED},
|
||||
/*105*/ {"Koto ", 107, MIDI_UNMAPPED},
|
||||
/*106*/ {"Sho ", 111, MIDI_UNMAPPED},
|
||||
/*107*/ {"Shakuhachi", 77, MIDI_UNMAPPED},
|
||||
/*108*/ {"Whistle 1 ", 78, MIDI_UNMAPPED},
|
||||
/*109*/ {"Whistle 2 ", 78, MIDI_UNMAPPED},
|
||||
/*110*/ {"BottleBlow", 76, MIDI_UNMAPPED},
|
||||
/*111*/ {"BreathPipe", 121, MIDI_UNMAPPED},
|
||||
/*112*/ {"Timpani ", 47, MIDI_UNMAPPED},
|
||||
/*113*/ {"MelodicTom", 117, MIDI_UNMAPPED},
|
||||
/*114*/ {"Deep Snare", MIDI_MAPPED_TO_RHYTHM, 38},
|
||||
/*115*/ {"Elec Perc1", 115, MIDI_UNMAPPED}, // ?
|
||||
/*116*/ {"Elec Perc2", 118, MIDI_UNMAPPED}, // ?
|
||||
/*117*/ {"Taiko ", 116, MIDI_UNMAPPED},
|
||||
/*118*/ {"Taiko Rim ", 118, MIDI_UNMAPPED},
|
||||
/*119*/ {"Cymbal ", MIDI_MAPPED_TO_RHYTHM, 51},
|
||||
/*120*/ {"Castanets ", MIDI_MAPPED_TO_RHYTHM, 75}, // approximation
|
||||
/*121*/ {"Triangle ", 112, MIDI_UNMAPPED},
|
||||
/*122*/ {"Orche Hit ", 55, MIDI_UNMAPPED},
|
||||
/*123*/ {"Telephone ", 124, MIDI_UNMAPPED},
|
||||
/*124*/ {"Bird Tweet", 123, MIDI_UNMAPPED},
|
||||
/*125*/ {"OneNoteJam", 8, MIDI_UNMAPPED}, // approximation
|
||||
/*126*/ {"WaterBells", 98, MIDI_UNMAPPED},
|
||||
/*127*/ {"JungleTune", 75, MIDI_UNMAPPED} // approximation
|
||||
};
|
||||
|
||||
static const Mt32ToGmMap Mt32RhythmTimbreMaps[] = {
|
||||
/*00*/ {"Acou BD ", MIDI_MAPPED_TO_RHYTHM, 35},
|
||||
/*01*/ {"Acou SD ", MIDI_MAPPED_TO_RHYTHM, 38},
|
||||
/*02*/ {"Acou HiTom", 117, 50},
|
||||
/*03*/ {"AcouMidTom", 117, 47},
|
||||
/*04*/ {"AcouLowTom", 117, 41},
|
||||
/*05*/ {"Elec SD ", MIDI_MAPPED_TO_RHYTHM, 40},
|
||||
/*06*/ {"Clsd HiHat", MIDI_MAPPED_TO_RHYTHM, 42},
|
||||
/*07*/ {"OpenHiHat1", MIDI_MAPPED_TO_RHYTHM, 46},
|
||||
/*08*/ {"Crash Cym ", MIDI_MAPPED_TO_RHYTHM, 49},
|
||||
/*09*/ {"Ride Cym ", MIDI_MAPPED_TO_RHYTHM, 51},
|
||||
/*10*/ {"Rim Shot ", MIDI_MAPPED_TO_RHYTHM, 37},
|
||||
/*11*/ {"Hand Clap ", MIDI_MAPPED_TO_RHYTHM, 39},
|
||||
/*12*/ {"Cowbell ", MIDI_MAPPED_TO_RHYTHM, 56},
|
||||
/*13*/ {"Mt HiConga", MIDI_MAPPED_TO_RHYTHM, 62},
|
||||
/*14*/ {"High Conga", MIDI_MAPPED_TO_RHYTHM, 63},
|
||||
/*15*/ {"Low Conga ", MIDI_MAPPED_TO_RHYTHM, 64},
|
||||
/*16*/ {"Hi Timbale", MIDI_MAPPED_TO_RHYTHM, 65},
|
||||
/*17*/ {"LowTimbale", MIDI_MAPPED_TO_RHYTHM, 66},
|
||||
/*18*/ {"High Bongo", MIDI_MAPPED_TO_RHYTHM, 60},
|
||||
/*19*/ {"Low Bongo ", MIDI_MAPPED_TO_RHYTHM, 61},
|
||||
/*20*/ {"High Agogo", 113, 67},
|
||||
/*21*/ {"Low Agogo ", 113, 68},
|
||||
/*22*/ {"Tambourine", MIDI_MAPPED_TO_RHYTHM, 54},
|
||||
/*23*/ {"Claves ", MIDI_MAPPED_TO_RHYTHM, 75},
|
||||
/*24*/ {"Maracas ", MIDI_MAPPED_TO_RHYTHM, 70},
|
||||
/*25*/ {"SmbaWhis L", 78, 72},
|
||||
/*26*/ {"SmbaWhis S", 78, 71},
|
||||
/*27*/ {"Cabasa ", MIDI_MAPPED_TO_RHYTHM, 69},
|
||||
/*28*/ {"Quijada ", MIDI_MAPPED_TO_RHYTHM, 73},
|
||||
/*29*/ {"OpenHiHat2", MIDI_MAPPED_TO_RHYTHM, 44}
|
||||
};
|
||||
|
||||
static const uint8 Mt32PresetRhythmKeymap[] = {
|
||||
MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED,
|
||||
MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED,
|
||||
MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED,
|
||||
MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, 35, 36, 37, 38, 39,
|
||||
40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
|
||||
50, 51, MIDI_UNMAPPED, MIDI_UNMAPPED, 54, MIDI_UNMAPPED, 56, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED,
|
||||
60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
|
||||
70, 71, 72, 73, MIDI_UNMAPPED, 75, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED,
|
||||
MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED,
|
||||
MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED,
|
||||
MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED,
|
||||
MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED,
|
||||
MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED
|
||||
};
|
||||
|
||||
/* +++ - Don't change unless you've got a good reason
|
||||
++ - Looks good, sounds ok
|
||||
+ - Not too bad, but is it right?
|
||||
? - Where do I map this one?
|
||||
?? - Any good ideas?
|
||||
??? - I'm clueless?
|
||||
R - Rhythm...
|
||||
*/
|
||||
static const Mt32ToGmMap Mt32MemoryTimbreMaps[] = {
|
||||
{"AccPnoKA2 ", 1, MIDI_UNMAPPED}, // ++ (KQ1)
|
||||
{"Acou BD ", MIDI_MAPPED_TO_RHYTHM, 35}, // R (PQ2)
|
||||
{"Acou SD ", MIDI_MAPPED_TO_RHYTHM, 38}, // R (PQ2)
|
||||
{"AcouPnoKA ", 0, MIDI_UNMAPPED}, // ++ (KQ1)
|
||||
{"BASS ", 32, MIDI_UNMAPPED}, // + (LSL3)
|
||||
{"BASSOONPCM", 70, MIDI_UNMAPPED}, // + (LB1)
|
||||
{"BEACH WAVE", 122, MIDI_UNMAPPED}, // + (LSL3)
|
||||
{"BagPipes ", 109, MIDI_UNMAPPED},
|
||||
{"BassPizzMS", 45, MIDI_UNMAPPED}, // ++ (QFG1)
|
||||
{"BassoonKA ", 70, MIDI_UNMAPPED}, // ++ (KQ1)
|
||||
{"Bell MS", 112, MIDI_UNMAPPED}, // ++ (Iceman)
|
||||
{"Bells MS", 112, MIDI_UNMAPPED}, // + (QFG1)
|
||||
{"Big Bell ", 14, MIDI_UNMAPPED}, // + (LB1)
|
||||
{"Bird Tweet", 123, MIDI_UNMAPPED},
|
||||
{"BrsSect MS", 61, MIDI_UNMAPPED}, // +++ (Iceman)
|
||||
{"CLAPPING ", 126, MIDI_UNMAPPED}, // ++ (LSL3)
|
||||
{"Cabasa ", MIDI_MAPPED_TO_RHYTHM, 69}, // R (Hoyle)
|
||||
{"Calliope ", 82, MIDI_UNMAPPED}, // +++ (QFG1)
|
||||
{"CelticHarp", 46, MIDI_UNMAPPED}, // ++ (Camelot)
|
||||
{"Chicago MS", 1, MIDI_UNMAPPED}, // ++ (Iceman)
|
||||
{"Chop ", 117, MIDI_UNMAPPED},
|
||||
{"Chorale MS", 52, MIDI_UNMAPPED}, // + (Camelot)
|
||||
{"ClarinetMS", 71, MIDI_UNMAPPED},
|
||||
{"Claves ", MIDI_MAPPED_TO_RHYTHM, 75}, // R (PQ2)
|
||||
{"Claw MS", 118, MIDI_UNMAPPED}, // + (QFG1)
|
||||
{"ClockBell ", 14, MIDI_UNMAPPED}, // + (LB1)
|
||||
{"ConcertCym", MIDI_MAPPED_TO_RHYTHM, 55}, // R ? (KQ1)
|
||||
{"Conga MS", MIDI_MAPPED_TO_RHYTHM, 64}, // R (QFG1)
|
||||
{"CoolPhone ", 124, MIDI_UNMAPPED}, // ++ (LSL3)
|
||||
{"CracklesMS", 115, MIDI_UNMAPPED}, // ? (Camelot, QFG1)
|
||||
{"CreakyD MS", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ??? (KQ1)
|
||||
{"Cricket ", 120, MIDI_UNMAPPED}, // ? (LB1)
|
||||
{"CrshCymbMS", MIDI_MAPPED_TO_RHYTHM, 57}, // R +++ (Iceman)
|
||||
{"CstlGateMS", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (QFG1)
|
||||
{"CymSwellMS", MIDI_MAPPED_TO_RHYTHM, 55}, // R ? (Camelot, QFG1)
|
||||
{"CymbRollKA", MIDI_MAPPED_TO_RHYTHM, 57}, // R ? (KQ1)
|
||||
{"Cymbal Lo ", MIDI_UNMAPPED, MIDI_UNMAPPED}, // R ? (LSL3)
|
||||
{"card ", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (Hoyle)
|
||||
{"DirtGtr MS", 30, MIDI_UNMAPPED}, // + (Iceman)
|
||||
{"DirtGtr2MS", 29, MIDI_UNMAPPED}, // + (Iceman)
|
||||
{"E Bass MS", 33, MIDI_UNMAPPED}, // + (SQ3)
|
||||
{"ElecBassMS", 33, MIDI_UNMAPPED},
|
||||
{"ElecGtr MS", 27, MIDI_UNMAPPED}, // ++ (Iceman)
|
||||
{"EnglHornMS", 69, MIDI_UNMAPPED},
|
||||
{"FantasiaKA", 88, MIDI_UNMAPPED},
|
||||
{"Fantasy ", 99, MIDI_UNMAPPED}, // + (PQ2)
|
||||
{"Fantasy2MS", 99, MIDI_UNMAPPED}, // ++ (Camelot, QFG1)
|
||||
{"Filter MS", 95, MIDI_UNMAPPED}, // +++ (Iceman)
|
||||
{"Filter2 MS", 95, MIDI_UNMAPPED}, // ++ (Iceman)
|
||||
{"Flame2 MS", 121, MIDI_UNMAPPED}, // ? (QFG1)
|
||||
{"Flames MS", 121, MIDI_UNMAPPED}, // ? (QFG1)
|
||||
{"Flute MS", 73, MIDI_UNMAPPED}, // +++ (QFG1)
|
||||
{"FogHorn MS", 58, MIDI_UNMAPPED},
|
||||
{"FrHorn1 MS", 60, MIDI_UNMAPPED}, // +++ (QFG1)
|
||||
{"FunnyTrmp ", 56, MIDI_UNMAPPED}, // ++ (LB1)
|
||||
{"GameSnd MS", 80, MIDI_UNMAPPED},
|
||||
{"Glock MS", 9, MIDI_UNMAPPED}, // +++ (QFG1)
|
||||
{"Gunshot ", 127, MIDI_UNMAPPED}, // +++ (LB1)
|
||||
{"Hammer MS", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (QFG1)
|
||||
{"Harmonica2", 22, MIDI_UNMAPPED}, // +++ (LB1)
|
||||
{"Harpsi 1 ", 6, MIDI_UNMAPPED}, // + (Hoyle)
|
||||
{"Harpsi 2 ", 6, MIDI_UNMAPPED}, // +++ (LB1)
|
||||
{"Heart MS", 116, MIDI_UNMAPPED}, // ? (Iceman)
|
||||
{"Horse1 MS", 115, MIDI_UNMAPPED}, // ? (Camelot, QFG1)
|
||||
{"Horse2 MS", 115, MIDI_UNMAPPED}, // ? (Camelot, QFG1)
|
||||
{"InHale MS", 121, MIDI_UNMAPPED}, // ++ (Iceman)
|
||||
{"KNIFE ", 120, MIDI_UNMAPPED}, // ? (LSL3)
|
||||
{"KenBanjo ", 105, MIDI_UNMAPPED}, // +++ (LB1)
|
||||
{"Kiss MS", 25, MIDI_UNMAPPED}, // ++ (QFG1)
|
||||
{"KongHit ", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ??? (KQ1)
|
||||
{"Koto ", 107, MIDI_UNMAPPED}, // +++ (PQ2)
|
||||
{"Laser MS", 81, MIDI_UNMAPPED}, // ?? (QFG1)
|
||||
{"Meeps MS", 62, MIDI_UNMAPPED}, // ? (QFG1)
|
||||
{"MTrak MS", 62, MIDI_UNMAPPED}, // ?? (Iceman)
|
||||
{"MachGun MS", 127, MIDI_UNMAPPED}, // ? (Iceman)
|
||||
{"OCEANSOUND", 122, MIDI_UNMAPPED}, // + (LSL3)
|
||||
{"Oboe 2001 ", 68, MIDI_UNMAPPED}, // + (PQ2)
|
||||
{"Ocean MS", 122, MIDI_UNMAPPED}, // + (Iceman)
|
||||
{"PPG 2.3 MS", 75, MIDI_UNMAPPED}, // ? (Iceman)
|
||||
{"PianoCrank", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (LB1)
|
||||
{"PicSnareMS", MIDI_MAPPED_TO_RHYTHM, 40}, // R ? (Iceman)
|
||||
{"PiccoloKA ", 72, MIDI_UNMAPPED}, // +++ (KQ1)
|
||||
{"PinkBassMS", 39, MIDI_UNMAPPED},
|
||||
{"Pizz2 ", 45, MIDI_UNMAPPED}, // ++ (LB1)
|
||||
{"Portcullis", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (KQ1)
|
||||
{"Raspbry MS", 81, MIDI_UNMAPPED}, // ? (QFG1)
|
||||
{"RatSqueek ", 72, MIDI_UNMAPPED}, // ? (LauraBow1, Camelot)
|
||||
{"Record78 ", MIDI_UNMAPPED, MIDI_UNMAPPED}, // +++ (LB1)
|
||||
{"RecorderMS", 74, MIDI_UNMAPPED}, // +++ (Camelot)
|
||||
{"Red Baron ", 125, MIDI_UNMAPPED}, // ? (LB1)
|
||||
{"ReedPipMS ", 20, MIDI_UNMAPPED}, // +++ (Camelot)
|
||||
{"RevCymb MS", 119, MIDI_UNMAPPED},
|
||||
{"RifleShot ", 127, MIDI_UNMAPPED}, // + (LB1)
|
||||
{"RimShot MS", MIDI_MAPPED_TO_RHYTHM, 37}, // R
|
||||
{"SHOWER ", 52, MIDI_UNMAPPED}, // ? (LSL3)
|
||||
{"SQ Bass MS", 32, MIDI_UNMAPPED}, // + (SQ3)
|
||||
{"ShakuVibMS", 79, MIDI_UNMAPPED}, // + (Iceman)
|
||||
{"SlapBassMS", 36, MIDI_UNMAPPED}, // +++ (Iceman)
|
||||
{"Snare MS", MIDI_MAPPED_TO_RHYTHM, 38}, // R (QFG1)
|
||||
{"Some Birds", 123, MIDI_UNMAPPED}, // + (LB1)
|
||||
{"Sonar MS", 78, MIDI_UNMAPPED}, // ? (Iceman)
|
||||
{"Soundtrk2 ", 97, MIDI_UNMAPPED}, // +++ (LB1)
|
||||
{"Soundtrack", 97, MIDI_UNMAPPED}, // ++ (Camelot)
|
||||
{"SqurWaveMS", 80, MIDI_UNMAPPED},
|
||||
{"StabBassMS", 34, MIDI_UNMAPPED}, // + (Iceman)
|
||||
{"SteelDrmMS", 114, MIDI_UNMAPPED}, // +++ (Iceman)
|
||||
{"StrSect1MS", 48, MIDI_UNMAPPED}, // ++ (QFG1)
|
||||
{"String MS", 45, MIDI_UNMAPPED}, // + (Camelot)
|
||||
{"Syn-Choir ", 91, MIDI_UNMAPPED},
|
||||
{"Syn Brass4", 63, MIDI_UNMAPPED}, // ++ (PQ2)
|
||||
{"SynBass MS", 38, MIDI_UNMAPPED},
|
||||
{"SwmpBackgr", 120, MIDI_UNMAPPED}, // ?? (LB1, QFG1)
|
||||
{"T-Bone2 MS", 57, MIDI_UNMAPPED}, // +++ (QFG1)
|
||||
{"Taiko ", 116, 35}, // +++ (Camelot)
|
||||
{"Taiko Rim ", 118, 37}, // +++ (LSL3)
|
||||
{"Timpani1 ", 47, MIDI_UNMAPPED}, // +++ (LB1)
|
||||
{"Tom MS", 117, 48}, // +++ (Iceman)
|
||||
{"Toms MS", 117, 48}, // +++ (Camelot, QFG1)
|
||||
{"Tpt1prtl ", 56, MIDI_UNMAPPED}, // +++ (KQ1)
|
||||
{"TriangleMS", 112, 81}, // R (Camelot)
|
||||
{"Trumpet 1 ", 56, MIDI_UNMAPPED}, // +++ (Camelot)
|
||||
{"Type MS", MIDI_MAPPED_TO_RHYTHM, 39}, // + (Iceman)
|
||||
{"Warm Pad" , 89, MIDI_UNMAPPED}, // ++ (PQ3)
|
||||
{"WaterBells", 98, MIDI_UNMAPPED}, // + (PQ2)
|
||||
{"WaterFallK", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (KQ1)
|
||||
{"Whiporill ", 123, MIDI_UNMAPPED}, // + (LB1)
|
||||
{"Wind ", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (LB1)
|
||||
{"Wind MS", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (QFG1, Iceman)
|
||||
{"Wind2 MS", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (Camelot)
|
||||
{"Woodpecker", 115, MIDI_UNMAPPED}, // ? (LB1)
|
||||
{"WtrFall MS", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (Camelot, QFG1, Iceman)
|
||||
{0, 0, 0}
|
||||
};
|
||||
|
||||
typedef Common::List<Mt32ToGmMap> Mt32ToGmMapList;
|
||||
extern Mt32ToGmMapList *Mt32dynamicMappings;
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_SOUND_DRIVERS_MAP_MT32_TO_GM_H
|
||||
1484
engines/sci/sound/drivers/midi.cpp
Normal file
1484
engines/sci/sound/drivers/midi.cpp
Normal file
File diff suppressed because it is too large
Load Diff
155
engines/sci/sound/drivers/mididriver.h
Normal file
155
engines/sci/sound/drivers/mididriver.h
Normal file
@@ -0,0 +1,155 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_SOUND_DRIVERS_MIDIDRIVER_H
|
||||
#define SCI_SOUND_DRIVERS_MIDIDRIVER_H
|
||||
|
||||
#include "sci/sci.h"
|
||||
#include "sci/util.h"
|
||||
#include "audio/mididrv.h"
|
||||
#include "common/error.h"
|
||||
#include "common/platform.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
// Music patches in SCI games:
|
||||
// ===========================
|
||||
// 1.pat - MT-32 driver music patch
|
||||
// 2.pat - Yamaha FB01 driver music patch
|
||||
// 3.pat - Adlib driver music patch
|
||||
// 4.pat - Casio MT-540 (in earlier SCI0 games)
|
||||
// 4.pat - GM driver music patch (in later games that support GM)
|
||||
// 7.pat (newer) / patch.200 (older) - Mac driver music patch / Casio CSM-1
|
||||
// 9.pat (newer) / patch.005 (older) - Amiga driver music patch
|
||||
// 98.pat - Unknown, found in later SCI1.1 games. A MIDI format patch
|
||||
// 101.pat - CMS/PCjr driver music patch.
|
||||
// Only later PCjr drivers use this patch, earlier ones don't use a patch
|
||||
// bank.001 - older SCI0 Amiga instruments
|
||||
|
||||
class ResourceManager;
|
||||
|
||||
enum {
|
||||
MIDI_CHANNELS = 16,
|
||||
MIDI_PROP_MASTER_VOLUME = 0
|
||||
};
|
||||
|
||||
#define MIDI_RHYTHM_CHANNEL 9
|
||||
|
||||
/* Special SCI sound stuff */
|
||||
|
||||
#define SCI_MIDI_TIME_EXPANSION_PREFIX 0xF8
|
||||
#define SCI_MIDI_TIME_EXPANSION_LENGTH 240
|
||||
|
||||
#define SCI_MIDI_EOT 0xFC
|
||||
#define SCI_MIDI_SET_SIGNAL 0xCF
|
||||
#define SCI_MIDI_SET_POLYPHONY 0x4B
|
||||
#define SCI_MIDI_RESET_ON_SUSPEND 0x4C
|
||||
#define SCI_MIDI_CHANNEL_MUTE 0x4E
|
||||
#define SCI_MIDI_SET_REVERB 0x50
|
||||
#define SCI_MIDI_HOLD 0x52
|
||||
#define SCI_MIDI_CUMULATIVE_CUE 0x60
|
||||
#define SCI_MIDI_CHANNEL_SOUND_OFF 0x78 /* all-sound-off for Bn */
|
||||
#define SCI_MIDI_CHANNEL_NOTES_OFF 0x7B /* all-notes-off for Bn */
|
||||
|
||||
#define SCI_MIDI_SET_SIGNAL_LOOP 0x7F
|
||||
/* If this is the parameter of 0xCF, the loop point is set here */
|
||||
|
||||
#define SCI_MIDI_CONTROLLER(status) ((status & 0xF0) == 0xB0)
|
||||
|
||||
class MidiPlayer : public MidiDriver_BASE {
|
||||
protected:
|
||||
MidiDriver *_driver;
|
||||
int8 _reverb;
|
||||
|
||||
public:
|
||||
MidiPlayer(SciVersion version) : _driver(0), _reverb(-1), _version(version) { }
|
||||
|
||||
int open() {
|
||||
ResourceManager *resMan = g_sci->getResMan(); // HACK
|
||||
return open(resMan);
|
||||
}
|
||||
virtual int open(ResourceManager *resMan) { return _driver->open(); }
|
||||
virtual void close() { _driver->close(); }
|
||||
void send(uint32 b) override { _driver->send(b); }
|
||||
virtual uint32 getBaseTempo() { return _driver->getBaseTempo(); }
|
||||
virtual bool hasRhythmChannel() const = 0;
|
||||
virtual void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { _driver->setTimerCallback(timer_param, timer_proc); }
|
||||
|
||||
virtual byte getPlayId() const = 0;
|
||||
virtual int getPolyphony() const = 0;
|
||||
virtual int getFirstChannel() const { return 0; }
|
||||
virtual int getLastChannel() const { return 15; }
|
||||
|
||||
virtual void setVolume(byte volume) {
|
||||
if(_driver)
|
||||
_driver->property(MIDI_PROP_MASTER_VOLUME, volume);
|
||||
}
|
||||
|
||||
virtual int getVolume() {
|
||||
return _driver ? _driver->property(MIDI_PROP_MASTER_VOLUME, 0xffff) : 0;
|
||||
}
|
||||
|
||||
// Returns the current reverb, or -1 when no reverb is active
|
||||
int8 getReverb() const { return _reverb; }
|
||||
// Sets the current reverb, used mainly in MT-32
|
||||
virtual void setReverb(int8 reverb) { _reverb = reverb; }
|
||||
|
||||
virtual void playSwitch(bool play) {
|
||||
if (!play) {
|
||||
// Send "All Sound Off" on all channels
|
||||
for (int i = 0; i < MIDI_CHANNELS; ++i)
|
||||
_driver->send(0xb0 + i, SCI_MIDI_CHANNEL_NOTES_OFF, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Prepares the driver for the playback of SCI0 midi tracks.
|
||||
// The main purpose is the assignment of voices ("hardware" sound channels) to the 16 midi parts.
|
||||
// This is basically the predecessor of the 0x4B midi event.
|
||||
// Some drivers also do other things in here.
|
||||
virtual void initTrack(SciSpan<const byte> &) {}
|
||||
|
||||
// There are several sound drivers which weren' part of the
|
||||
// original game setup and came in the form of aftermarket patches.
|
||||
// This method allows each driver to report missing patch or other
|
||||
// required files which will then be displayed in an error dialog box.
|
||||
// The method returns only a single string (instead of a string list),
|
||||
// because no more than two files will be required.
|
||||
virtual const char *reportMissingFiles() { return 0; }
|
||||
|
||||
protected:
|
||||
SciVersion _version;
|
||||
};
|
||||
|
||||
extern MidiPlayer *MidiPlayer_AdLib_create(SciVersion version);
|
||||
extern MidiPlayer *MidiPlayer_AmigaMac0_create(SciVersion version, Common::Platform platform);
|
||||
extern MidiPlayer *MidiPlayer_AmigaMac1_create(SciVersion version, Common::Platform platform);
|
||||
extern MidiPlayer *MidiPlayer_PCJr_create(SciVersion version);
|
||||
extern MidiPlayer *MidiPlayer_PCSpeaker_create(SciVersion version);
|
||||
extern MidiPlayer *MidiPlayer_CMS_create(SciVersion version);
|
||||
extern MidiPlayer *MidiPlayer_Midi_create(SciVersion version);
|
||||
extern MidiPlayer *MidiPlayer_Fb01_create(SciVersion version);
|
||||
extern MidiPlayer *MidiPlayer_Casio_create(SciVersion version, MusicType midiType);
|
||||
extern MidiPlayer *MidiPlayer_FMTowns_create(SciVersion version);
|
||||
extern MidiPlayer *MidiPlayer_PC9801_create(SciVersion version);
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_SOUND_DRIVERS_MIDIDRIVER_H
|
||||
1718
engines/sci/sound/drivers/pc9801.cpp
Normal file
1718
engines/sci/sound/drivers/pc9801.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1020
engines/sci/sound/drivers/pcjr.cpp
Normal file
1020
engines/sci/sound/drivers/pcjr.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user