Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff