Initial commit
This commit is contained in:
915
engines/dgds/sound/drivers/adlib.cpp
Normal file
915
engines/dgds/sound/drivers/adlib.cpp
Normal file
@@ -0,0 +1,915 @@
|
||||
/* 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 "common/file.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
#include "audio/fmopl.h"
|
||||
#include "audio/mididrv.h"
|
||||
|
||||
#include "dgds/sound/resource/sci_resource.h"
|
||||
#include "dgds/sound/drivers/mididriver.h"
|
||||
#include "dgds/sound/scispan.h"
|
||||
#include "dgds/dgds.h"
|
||||
|
||||
namespace Dgds {
|
||||
|
||||
#ifdef __DC__
|
||||
#define STEREO false
|
||||
#else
|
||||
#define STEREO true
|
||||
#endif
|
||||
|
||||
class MidiDriver_AdLib : public MidiDriver {
|
||||
public:
|
||||
enum {
|
||||
kVoices = 9,
|
||||
kRhythmKeys = 62
|
||||
};
|
||||
|
||||
MidiDriver_AdLib() : _isSCI0(false), _playSwitch(true), _masterVolume(15),
|
||||
_numVoiceMax(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;
|
||||
|
||||
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;
|
||||
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() : MidiPlayer() { _driver = new MidiDriver_AdLib(); }
|
||||
~MidiPlayer_AdLib() override {
|
||||
delete _driver;
|
||||
_driver = nullptr;
|
||||
}
|
||||
|
||||
int open() 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); }
|
||||
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;
|
||||
|
||||
//debug(1, "midi send %02x %x %02x %02x", command, channel, op1, op2);
|
||||
|
||||
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::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) {
|
||||
// Search for unused voice
|
||||
for (const auto &voice : _voiceQueue) {
|
||||
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 (const auto &voice : _voiceQueue) {
|
||||
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 (const auto &voice : _voiceQueue) {
|
||||
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) {
|
||||
warning("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() {
|
||||
// Load up the patch.003 file, parse out the instruments
|
||||
SciResource *res = getMidiPatchData(3);
|
||||
bool ok = false;
|
||||
|
||||
if (res) {
|
||||
ok = static_cast<MidiDriver_AdLib *>(_driver)->loadResource(*res);
|
||||
delete res;
|
||||
|
||||
// WORKAROUND: The Willy Beamish interactive demo has empty Adlib patch
|
||||
// data. Ignore the failure there.
|
||||
const DgdsEngine *engine = DgdsEngine::getInstance();
|
||||
if (!ok && engine->getGameId() == GID_WILLY && engine->isDemo()) {
|
||||
warning("No ADLIB sound available for Willy Beamish demo");
|
||||
ok = true;
|
||||
}
|
||||
} 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 {
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
MidiPlayer *MidiPlayer_AdLib_create() {
|
||||
return new MidiPlayer_AdLib();
|
||||
}
|
||||
|
||||
} // End of namespace Dgds
|
||||
1295
engines/dgds/sound/drivers/amigamac1.cpp
Normal file
1295
engines/dgds/sound/drivers/amigamac1.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1269
engines/dgds/sound/drivers/cms.cpp
Normal file
1269
engines/dgds/sound/drivers/cms.cpp
Normal file
File diff suppressed because it is too large
Load Diff
667
engines/dgds/sound/drivers/fmtowns.cpp
Normal file
667
engines/dgds/sound/drivers/fmtowns.cpp
Normal file
@@ -0,0 +1,667 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sci/sci.h"
|
||||
|
||||
#include "common/file.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
#include "audio/softsynth/fmtowns_pc98/towns_audio.h"
|
||||
|
||||
#include "sci/sound/resource/sci_resource.h"
|
||||
#include "sci/sound/drivers/mididriver.h"
|
||||
|
||||
namespace Dgds {
|
||||
|
||||
class MidiDriver_FMTowns;
|
||||
|
||||
class TownsChannel {
|
||||
public:
|
||||
TownsChannel(MidiDriver_FMTowns *driver, uint8 id);
|
||||
~TownsChannel() {}
|
||||
|
||||
void noteOff();
|
||||
void noteOn(uint8 note, uint8 velo);
|
||||
void pitchBend(int16 val);
|
||||
void updateVolume();
|
||||
void updateDuration();
|
||||
|
||||
uint8 _assign;
|
||||
uint8 _note;
|
||||
uint8 _sustain;
|
||||
uint16 _duration;
|
||||
|
||||
private:
|
||||
uint8 _id;
|
||||
uint8 _velo;
|
||||
uint8 _program;
|
||||
|
||||
MidiDriver_FMTowns *_drv;
|
||||
};
|
||||
|
||||
class TownsMidiPart {
|
||||
friend class MidiDriver_FMTowns;
|
||||
public:
|
||||
TownsMidiPart(MidiDriver_FMTowns *driver, uint8 id);
|
||||
~TownsMidiPart() {}
|
||||
|
||||
void noteOff(uint8 note);
|
||||
void noteOn(uint8 note, uint8 velo);
|
||||
void controlChangeVolume(uint8 vol);
|
||||
void controlChangeSustain(uint8 sus);
|
||||
void controlChangePolyphony(uint8 numChan);
|
||||
void controlChangeAllNotesOff();
|
||||
void programChange(uint8 prg);
|
||||
void pitchBend(int16 val);
|
||||
|
||||
void addChannels(int num);
|
||||
void dropChannels(int num);
|
||||
|
||||
uint8 currentProgram() const;
|
||||
|
||||
private:
|
||||
int allocateChannel();
|
||||
|
||||
uint8 _id;
|
||||
uint8 _program;
|
||||
uint8 _volume;
|
||||
uint8 _sustain;
|
||||
uint8 _chanMissing;
|
||||
int16 _pitchBend;
|
||||
uint8 _outChan;
|
||||
|
||||
MidiDriver_FMTowns *_drv;
|
||||
};
|
||||
|
||||
class MidiDriver_FMTowns : public MidiDriver, public TownsAudioInterfacePluginDriver {
|
||||
friend class TownsChannel;
|
||||
friend class TownsMidiPart;
|
||||
public:
|
||||
MidiDriver_FMTowns(Audio::Mixer *mixer, SciVersion version);
|
||||
~MidiDriver_FMTowns() override;
|
||||
|
||||
int open() override;
|
||||
void loadInstruments(const SciSpan<const uint8> &data);
|
||||
bool isOpen() const override { return _isOpen; }
|
||||
void close() override;
|
||||
|
||||
void send(uint32 b) override;
|
||||
|
||||
uint32 property(int prop, uint32 param) override;
|
||||
void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override;
|
||||
|
||||
void setSoundOn(bool toggle);
|
||||
|
||||
uint32 getBaseTempo() override;
|
||||
MidiChannel *allocateChannel() override { return nullptr; }
|
||||
MidiChannel *getPercussionChannel() override { return nullptr; }
|
||||
|
||||
void timerCallback(int timerId) override;
|
||||
|
||||
private:
|
||||
int getChannelVolume(uint8 midiPart);
|
||||
void addMissingChannels();
|
||||
|
||||
void updateParser();
|
||||
void updateChannels();
|
||||
|
||||
Common::TimerManager::TimerProc _timerProc;
|
||||
void *_timerProcPara;
|
||||
|
||||
TownsMidiPart **_parts;
|
||||
TownsChannel **_out;
|
||||
|
||||
uint8 _masterVolume;
|
||||
|
||||
bool _soundOn;
|
||||
|
||||
bool _isOpen;
|
||||
bool _ready;
|
||||
|
||||
const uint16 _baseTempo;
|
||||
SciVersion _version;
|
||||
|
||||
TownsAudioInterface *_intf;
|
||||
};
|
||||
|
||||
class MidiPlayer_FMTowns : public MidiPlayer {
|
||||
public:
|
||||
MidiPlayer_FMTowns(SciVersion version);
|
||||
~MidiPlayer_FMTowns() override;
|
||||
|
||||
int open(ResourceManager *resMan) override;
|
||||
|
||||
bool hasRhythmChannel() const override;
|
||||
byte getPlayId() const override;
|
||||
int getPolyphony() const override;
|
||||
void playSwitch(bool play) override;
|
||||
|
||||
private:
|
||||
MidiDriver_FMTowns *_townsDriver;
|
||||
};
|
||||
|
||||
TownsChannel::TownsChannel(MidiDriver_FMTowns *driver, uint8 id) : _drv(driver), _id(id), _assign(0xff), _note(0xff), _velo(0), _sustain(0), _duration(0), _program(0xff) {
|
||||
}
|
||||
|
||||
void TownsChannel::noteOn(uint8 note, uint8 velo) {
|
||||
_duration = 0;
|
||||
|
||||
if (_drv->_version != SCI_VERSION_1_EARLY) {
|
||||
if (_program != _drv->_parts[_assign]->currentProgram() && _drv->_soundOn) {
|
||||
_program = _drv->_parts[_assign]->currentProgram();
|
||||
_drv->_intf->callback(4, _id, _program);
|
||||
}
|
||||
}
|
||||
|
||||
_note = note;
|
||||
_velo = velo;
|
||||
_drv->_intf->callback(1, _id, _note, _velo);
|
||||
}
|
||||
|
||||
void TownsChannel::noteOff() {
|
||||
if (_sustain)
|
||||
return;
|
||||
|
||||
_drv->_intf->callback(2, _id);
|
||||
_note = 0xff;
|
||||
_duration = 0;
|
||||
}
|
||||
|
||||
void TownsChannel::pitchBend(int16 val) {
|
||||
_drv->_intf->callback(7, _id, val);
|
||||
}
|
||||
|
||||
void TownsChannel::updateVolume() {
|
||||
if (_assign > 15 && _drv->_version != SCI_VERSION_1_EARLY)
|
||||
return;
|
||||
_drv->_intf->callback(8, _id, _drv->getChannelVolume((_drv->_version == SCI_VERSION_1_EARLY) ? 0 : _assign));
|
||||
}
|
||||
|
||||
void TownsChannel::updateDuration() {
|
||||
if (_note != 0xff)
|
||||
_duration++;
|
||||
}
|
||||
|
||||
TownsMidiPart::TownsMidiPart(MidiDriver_FMTowns *driver, uint8 id) : _drv(driver), _id(id), _program(0), _volume(0x3f), _sustain(0), _chanMissing(0), _pitchBend(0x2000), _outChan(0) {
|
||||
}
|
||||
|
||||
void TownsMidiPart::noteOff(uint8 note) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if ((_drv->_out[i]->_assign != _id && _drv->_version != SCI_VERSION_1_EARLY) || _drv->_out[i]->_note != note)
|
||||
continue;
|
||||
if (_sustain)
|
||||
_drv->_out[i]->_sustain = 1;
|
||||
else
|
||||
_drv->_out[i]->noteOff();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void TownsMidiPart::noteOn(uint8 note, uint8 velo) {
|
||||
if (note < 12 || note > 107)
|
||||
return;
|
||||
|
||||
if (velo == 0) {
|
||||
noteOff(note);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_drv->_version != SCI_VERSION_1_EARLY)
|
||||
velo >>= 1;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if ((_drv->_out[i]->_assign != _id && _drv->_version != SCI_VERSION_1_EARLY) || _drv->_out[i]->_note != note)
|
||||
continue;
|
||||
_drv->_out[i]->_sustain = 0;
|
||||
_drv->_out[i]->noteOff();
|
||||
_drv->_out[i]->noteOn(note, velo);
|
||||
return;
|
||||
}
|
||||
|
||||
int chan = allocateChannel();
|
||||
if (chan != -1)
|
||||
_drv->_out[chan]->noteOn(note, velo);
|
||||
}
|
||||
|
||||
void TownsMidiPart::controlChangeVolume(uint8 vol) {
|
||||
if (_drv->_version == SCI_VERSION_1_EARLY)
|
||||
return;
|
||||
|
||||
_volume = vol >> 1;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_drv->_out[i]->_assign == _id)
|
||||
_drv->_out[i]->updateVolume();
|
||||
}
|
||||
}
|
||||
|
||||
void TownsMidiPart::controlChangeSustain(uint8 sus) {
|
||||
if (_drv->_version == SCI_VERSION_1_EARLY)
|
||||
return;
|
||||
|
||||
_sustain = sus;
|
||||
if (_sustain)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_drv->_out[i]->_assign == _id && _drv->_out[i]->_sustain) {
|
||||
_drv->_out[i]->_sustain = 0;
|
||||
_drv->_out[i]->noteOff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TownsMidiPart::controlChangePolyphony(uint8 numChan) {
|
||||
if (_drv->_version == SCI_VERSION_1_EARLY)
|
||||
return;
|
||||
|
||||
uint8 numAssigned = 0;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_drv->_out[i]->_assign == _id)
|
||||
numAssigned++;
|
||||
}
|
||||
|
||||
numAssigned += _chanMissing;
|
||||
if (numAssigned < numChan) {
|
||||
addChannels(numChan - numAssigned);
|
||||
} else if (numAssigned > numChan) {
|
||||
dropChannels(numAssigned - numChan);
|
||||
_drv->addMissingChannels();
|
||||
}
|
||||
}
|
||||
|
||||
void TownsMidiPart::controlChangeAllNotesOff() {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if ((_drv->_out[i]->_assign == _id || _drv->_version == SCI_VERSION_1_EARLY) && _drv->_out[i]->_note != 0xff)
|
||||
_drv->_out[i]->noteOff();
|
||||
}
|
||||
}
|
||||
|
||||
void TownsMidiPart::programChange(uint8 prg) {
|
||||
_program = prg;
|
||||
}
|
||||
|
||||
void TownsMidiPart::pitchBend(int16 val) {
|
||||
_pitchBend = val;
|
||||
val -= 0x2000;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
// Strangely, the early version driver applies the setting to channel 0 only.
|
||||
if (_drv->_out[i]->_assign == _id || (_drv->_version == SCI_VERSION_1_EARLY && i == 0))
|
||||
_drv->_out[i]->pitchBend(val);
|
||||
}
|
||||
}
|
||||
|
||||
void TownsMidiPart::addChannels(int num) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_drv->_out[i]->_assign != 0xff)
|
||||
continue;
|
||||
|
||||
_drv->_out[i]->_assign = _id;
|
||||
_drv->_out[i]->updateVolume();
|
||||
|
||||
if (_drv->_out[i]->_note != 0xff)
|
||||
_drv->_out[i]->noteOff();
|
||||
|
||||
if (!--num)
|
||||
break;
|
||||
}
|
||||
|
||||
_chanMissing += num;
|
||||
programChange(_program);
|
||||
pitchBend(_pitchBend);
|
||||
controlChangeVolume(_volume << 1);
|
||||
}
|
||||
|
||||
void TownsMidiPart::dropChannels(int num) {
|
||||
if (_chanMissing == num) {
|
||||
_chanMissing = 0;
|
||||
return;
|
||||
} else if (_chanMissing > num) {
|
||||
_chanMissing -= num;
|
||||
return;
|
||||
}
|
||||
|
||||
num -= _chanMissing;
|
||||
_chanMissing = 0;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_drv->_out[i]->_assign != _id || _drv->_out[i]->_note != 0xff)
|
||||
continue;
|
||||
_drv->_out[i]->_assign = 0xff;
|
||||
if (!--num)
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_drv->_out[i]->_assign != _id)
|
||||
continue;
|
||||
_drv->_out[i]->_sustain = 0;
|
||||
_drv->_out[i]->noteOff();
|
||||
_drv->_out[i]->_assign = 0xff;
|
||||
if (!--num)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint8 TownsMidiPart::currentProgram() const {
|
||||
return _program;
|
||||
}
|
||||
|
||||
int TownsMidiPart::allocateChannel() {
|
||||
int chan = _outChan;
|
||||
int ovrChan = 0;
|
||||
int ld = 0;
|
||||
bool found = false;
|
||||
|
||||
for (bool loop = true; loop; ) {
|
||||
if (++chan == 6)
|
||||
chan = 0;
|
||||
|
||||
if (chan == _outChan)
|
||||
loop = false;
|
||||
|
||||
if (_id == _drv->_out[chan]->_assign || _drv->_version == SCI_VERSION_1_EARLY) {
|
||||
if (_drv->_out[chan]->_note == 0xff) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (_drv->_out[chan]->_duration >= ld) {
|
||||
ld = _drv->_out[chan]->_duration;
|
||||
ovrChan = chan;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
if (!ld)
|
||||
return -1;
|
||||
chan = ovrChan;
|
||||
_drv->_out[chan]->_sustain = 0;
|
||||
_drv->_out[chan]->noteOff();
|
||||
}
|
||||
|
||||
_outChan = chan;
|
||||
return chan;
|
||||
}
|
||||
|
||||
MidiDriver_FMTowns::MidiDriver_FMTowns(Audio::Mixer *mixer, SciVersion version) : _version(version), _timerProc(nullptr), _timerProcPara(nullptr), _baseTempo(10080), _ready(false), _isOpen(false), _masterVolume(0x0f), _soundOn(true) {
|
||||
_intf = new TownsAudioInterface(mixer, this, true);
|
||||
_out = new TownsChannel*[6];
|
||||
for (int i = 0; i < 6; i++)
|
||||
_out[i] = new TownsChannel(this, i);
|
||||
_parts = new TownsMidiPart*[16];
|
||||
for (int i = 0; i < 16; i++)
|
||||
_parts[i] = new TownsMidiPart(this, i);
|
||||
}
|
||||
|
||||
MidiDriver_FMTowns::~MidiDriver_FMTowns() {
|
||||
delete _intf;
|
||||
|
||||
if (_parts) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
delete _parts[i];
|
||||
_parts[i] = nullptr;
|
||||
}
|
||||
delete[] _parts;
|
||||
_parts = nullptr;
|
||||
}
|
||||
|
||||
if (_out) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
delete _out[i];
|
||||
_out[i] = nullptr;
|
||||
}
|
||||
delete[] _out;
|
||||
_out = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int MidiDriver_FMTowns::open() {
|
||||
if (_isOpen)
|
||||
return MERR_ALREADY_OPEN;
|
||||
|
||||
if (!_ready) {
|
||||
if (!_intf->init())
|
||||
return MERR_CANNOT_CONNECT;
|
||||
|
||||
_intf->callback(0);
|
||||
|
||||
_intf->callback(21, 255, 1);
|
||||
_intf->callback(21, 0, 1);
|
||||
_intf->callback(22, 255, 221);
|
||||
|
||||
_intf->callback(33, 8);
|
||||
_intf->setSoundEffectChanMask(~0x3f);
|
||||
|
||||
_ready = true;
|
||||
}
|
||||
|
||||
_isOpen = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::loadInstruments(const SciSpan<const uint8> &data) {
|
||||
enum {
|
||||
fmDataSize = 48
|
||||
};
|
||||
|
||||
if (data.size()) {
|
||||
SciSpan<const uint8> instrumentData = data.subspan(6);
|
||||
for (int i = 0; i < 128; i++, instrumentData += fmDataSize) {
|
||||
_intf->callback(5, 0, i, instrumentData.getUnsafeDataAt(0, fmDataSize));
|
||||
}
|
||||
}
|
||||
|
||||
_intf->callback(70, 3);
|
||||
property(MIDI_PROP_MASTER_VOLUME, _masterVolume);
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::close() {
|
||||
_isOpen = false;
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::send(uint32 b) {
|
||||
if (!_isOpen)
|
||||
return;
|
||||
|
||||
byte para2 = (b >> 16) & 0xFF;
|
||||
byte para1 = (b >> 8) & 0xFF;
|
||||
byte cmd = b & 0xF0;
|
||||
|
||||
TownsMidiPart *chan = _parts[b & 0x0F];
|
||||
|
||||
switch (cmd) {
|
||||
case 0x80:
|
||||
chan->noteOff(para1);
|
||||
break;
|
||||
case 0x90:
|
||||
chan->noteOn(para1, para2);
|
||||
break;
|
||||
case 0xb0:
|
||||
switch (para1) {
|
||||
case 7:
|
||||
chan->controlChangeVolume(para2);
|
||||
break;
|
||||
case 64:
|
||||
chan->controlChangeSustain(para2);
|
||||
break;
|
||||
case SCI_MIDI_SET_POLYPHONY:
|
||||
chan->controlChangePolyphony(para2);
|
||||
break;
|
||||
case SCI_MIDI_CHANNEL_NOTES_OFF:
|
||||
chan->controlChangeAllNotesOff();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 0xc0:
|
||||
chan->programChange(para1);
|
||||
break;
|
||||
case 0xe0:
|
||||
chan->pitchBend(para1 | (para2 << 7));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 MidiDriver_FMTowns::property(int prop, uint32 param) {
|
||||
switch(prop) {
|
||||
case MIDI_PROP_MASTER_VOLUME:
|
||||
if (param != 0xffff) {
|
||||
_masterVolume = param;
|
||||
for (int i = 0; i < 6; i++)
|
||||
_out[i]->updateVolume();
|
||||
}
|
||||
return _masterVolume;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
|
||||
_timerProc = timer_proc;
|
||||
_timerProcPara = timer_param;
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::setSoundOn(bool toggle) {
|
||||
_soundOn = toggle;
|
||||
}
|
||||
|
||||
uint32 MidiDriver_FMTowns::getBaseTempo() {
|
||||
return _baseTempo;
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::timerCallback(int timerId) {
|
||||
if (!_isOpen)
|
||||
return;
|
||||
|
||||
switch (timerId) {
|
||||
case 1:
|
||||
updateParser();
|
||||
updateChannels();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int MidiDriver_FMTowns::getChannelVolume(uint8 midiPart) {
|
||||
static const uint8 volumeTable[] = { 0x00, 0x0D, 0x1B, 0x28, 0x36, 0x43, 0x51, 0x5F, 0x63, 0x67, 0x6B, 0x6F, 0x73, 0x77, 0x7B, 0x7F };
|
||||
int tableIndex = (_version == SCI_VERSION_1_EARLY) ? _masterVolume : (_parts[midiPart]->_volume * (_masterVolume + 1)) >> 6;
|
||||
assert(tableIndex < 16);
|
||||
return volumeTable[tableIndex];
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::addMissingChannels() {
|
||||
uint8 avlChan = 0;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_out[i]->_assign == 0xff)
|
||||
avlChan++;
|
||||
}
|
||||
|
||||
if (!avlChan)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (!_parts[i]->_chanMissing)
|
||||
continue;
|
||||
|
||||
if (_parts[i]->_chanMissing < avlChan) {
|
||||
avlChan -= _parts[i]->_chanMissing;
|
||||
uint8 m = _parts[i]->_chanMissing;
|
||||
_parts[i]->_chanMissing = 0;
|
||||
_parts[i]->addChannels(m);
|
||||
} else {
|
||||
_parts[i]->_chanMissing -= avlChan;
|
||||
_parts[i]->addChannels(avlChan);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::updateParser() {
|
||||
if (_timerProc)
|
||||
_timerProc(_timerProcPara);
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::updateChannels() {
|
||||
for (int i = 0; i < 6; i++)
|
||||
_out[i]->updateDuration();
|
||||
}
|
||||
|
||||
MidiPlayer_FMTowns::MidiPlayer_FMTowns(SciVersion version) : MidiPlayer(version) {
|
||||
_driver = _townsDriver = new MidiDriver_FMTowns(g_system->getMixer(), version);
|
||||
}
|
||||
|
||||
MidiPlayer_FMTowns::~MidiPlayer_FMTowns() {
|
||||
delete _driver;
|
||||
}
|
||||
|
||||
int MidiPlayer_FMTowns::open(ResourceManager *resMan) {
|
||||
int result = MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
||||
if (_townsDriver) {
|
||||
result = _townsDriver->open();
|
||||
if (!result && _version == SCI_VERSION_1_LATE) {
|
||||
Resource *res = resMan->findResource(ResourceId(kResourceTypePatch, 8), false);
|
||||
if (res != nullptr) {
|
||||
_townsDriver->loadInstruments(*res);
|
||||
} else {
|
||||
warning("MidiPlayer_FMTowns: Failed to open patch 8");
|
||||
result = MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MidiPlayer_FMTowns::hasRhythmChannel() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte MidiPlayer_FMTowns::getPlayId() const {
|
||||
return (_version == SCI_VERSION_1_EARLY) ? 0x00 : 0x16;
|
||||
}
|
||||
|
||||
int MidiPlayer_FMTowns::getPolyphony() const {
|
||||
// WORKAROUND:
|
||||
// I set the return value to 16 for SCI_VERSION_1_EARLY here, which fixes music playback in Mixed Up Mothergoose.
|
||||
// This has been broken since the introduction of SciMusic::remapChannels() and the corresponding code.
|
||||
// The original code of Mixed Up Mothergoose code doesn't have the remapping and doesn't seem to check the polyphony
|
||||
// setting ever. So the value of 1 was probably incorrect.
|
||||
return (_version == SCI_VERSION_1_EARLY) ? 16 : 6;
|
||||
}
|
||||
|
||||
void MidiPlayer_FMTowns::playSwitch(bool play) {
|
||||
if (_townsDriver)
|
||||
_townsDriver->setSoundOn(play);
|
||||
}
|
||||
|
||||
MidiPlayer *MidiPlayer_FMTowns_create(SciVersion _soundVersion) {
|
||||
return new MidiPlayer_FMTowns(_soundVersion);
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
223
engines/dgds/sound/drivers/gm_names.h
Normal file
223
engines/dgds/sound/drivers/gm_names.h
Normal file
@@ -0,0 +1,223 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DGDS_SOUND_DRIVERS_GM_NAMES_H
|
||||
#define DGDS_SOUND_DRIVERS_GM_NAMES_H
|
||||
|
||||
namespace Dgds {
|
||||
|
||||
// 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 Dgds
|
||||
|
||||
#endif // DGDS_SOUND_DRIVERS_GM_NAMES_H
|
||||
272
engines/dgds/sound/drivers/macmixer.h
Normal file
272
engines/dgds/sound/drivers/macmixer.h
Normal file
@@ -0,0 +1,272 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/mixer.h"
|
||||
#include "common/frac.h"
|
||||
#include "common/mutex.h"
|
||||
#include "common/system.h"
|
||||
|
||||
#ifndef DGDS_SOUND_DRIVERS_MACMIXER_H
|
||||
#define DGDS_SOUND_DRIVERS_MACMIXER_H
|
||||
|
||||
namespace Dgds {
|
||||
|
||||
// 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 Dgds
|
||||
|
||||
#endif // DGDS_SOUND_DRIVERS_MACMIXER_H
|
||||
371
engines/dgds/sound/drivers/map-mt32-to-gm.h
Normal file
371
engines/dgds/sound/drivers/map-mt32-to-gm.h
Normal file
@@ -0,0 +1,371 @@
|
||||
/* 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 DGDS_SOUND_DRIVERS_MAP_MT32_TO_GM_H
|
||||
#define DGDS_SOUND_DRIVERS_MAP_MT32_TO_GM_H
|
||||
|
||||
namespace Dgds {
|
||||
|
||||
// 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 Dgds
|
||||
|
||||
#endif // DGDS_SOUND_DRIVERS_MAP_MT32_TO_GM_H
|
||||
1085
engines/dgds/sound/drivers/midi.cpp
Normal file
1085
engines/dgds/sound/drivers/midi.cpp
Normal file
File diff suppressed because it is too large
Load Diff
130
engines/dgds/sound/drivers/mididriver.h
Normal file
130
engines/dgds/sound/drivers/mididriver.h
Normal file
@@ -0,0 +1,130 @@
|
||||
/* 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 DGDS_SOUND_DRIVERS_MIDIDRIVER_H
|
||||
#define DGDS_SOUND_DRIVERS_MIDIDRIVER_H
|
||||
|
||||
#include "audio/mididrv.h"
|
||||
#include "common/platform.h"
|
||||
|
||||
namespace Dgds {
|
||||
|
||||
// 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() : _driver(nullptr), _reverb(-1) {}
|
||||
|
||||
virtual int open() { 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SciResource;
|
||||
|
||||
extern SciResource *getMidiPatchData(int num);
|
||||
|
||||
extern MidiPlayer *MidiPlayer_AdLib_create();
|
||||
extern MidiPlayer *MidiPlayer_AmigaMac1_create(Common::Platform platform);
|
||||
extern MidiPlayer *MidiPlayer_CMS_create();
|
||||
extern MidiPlayer *MidiPlayer_PCJr_create();
|
||||
extern MidiPlayer *MidiPlayer_PCSpeaker_create();
|
||||
extern MidiPlayer *MidiPlayer_Midi_create();
|
||||
|
||||
} // End of namespace Dgds
|
||||
|
||||
#endif // DGDS_SOUND_DRIVERS_MIDIDRIVER_H
|
||||
117
engines/dgds/sound/drivers/midipatch.cpp
Normal file
117
engines/dgds/sound/drivers/midipatch.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
/* 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 "common/config-manager.h"
|
||||
#include "common/file.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/ptr.h"
|
||||
|
||||
#include "dgds/sound/resource/sci_resource.h"
|
||||
#include "dgds/dgds.h"
|
||||
#include "dgds/includes.h"
|
||||
|
||||
namespace Dgds {
|
||||
|
||||
//
|
||||
// Unlike the other files in this directory, this is not part of the SCI
|
||||
// engine. This is a single function to load patch data from the DGDS
|
||||
// resource system.
|
||||
//
|
||||
|
||||
static const char *PATCH_RESOURCES[] = {
|
||||
"SXTITLE.OVL", // dragon
|
||||
"SXCODE1.OVL", // hoc (TODO: when do we load SXCODE2 - maybe when global 0x37 changes)
|
||||
"SX.OVL", // newer games (beamish, sq5 demo)
|
||||
};
|
||||
|
||||
SciResource *getMidiPatchData(int num) {
|
||||
assert(num < 999);
|
||||
|
||||
DgdsEngine *engine = DgdsEngine::getInstance();
|
||||
ResourceManager *resource = engine->getResourceManager();
|
||||
Decompressor *decomp = engine->getDecompressor();
|
||||
Common::ScopedPtr<ResourceManager> fddMgr;
|
||||
Common::ScopedPtr<Common::SeekableReadStream> ovlStream;
|
||||
|
||||
int resNum = 0;
|
||||
for (; resNum < ARRAYSIZE(PATCH_RESOURCES); resNum++) {
|
||||
ovlStream.reset(resource->getResource(PATCH_RESOURCES[resNum]));
|
||||
if (ovlStream)
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
// WORKAROUND: The MT-32 patch data in Willy Beamish CD version is corrupted.
|
||||
// If the FDD version is avaialble in the "FDD" directory, use that instead.
|
||||
// This is how the data comes arranged in the GOG version.
|
||||
//
|
||||
if (num == 1 && engine->getGameId() == GID_WILLY) {
|
||||
fddMgr.reset(new ResourceManager("FDD"));
|
||||
if (fddMgr.get()->hasResource("SX.OVL")) {
|
||||
debug("Overriding MT32 patch data with patches from FDD version.");
|
||||
resNum = 2;
|
||||
ovlStream.reset(fddMgr.get()->getResource("SX.OVL"));
|
||||
}
|
||||
}
|
||||
|
||||
if (!ovlStream) {
|
||||
warning("Couldn't load DGDS midi patch data from any known OVL file");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DgdsChunkReader chunk(ovlStream.get());
|
||||
|
||||
const Common::String targetSection = Common::String::format("%03d:", num);
|
||||
|
||||
while (chunk.readNextHeader(EX_OVL, PATCH_RESOURCES[resNum])) {
|
||||
if (chunk.isContainer()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chunk.isSection(targetSection)) {
|
||||
chunk.readContent(decomp);
|
||||
Common::SeekableReadStream *stream = chunk.getContent();
|
||||
|
||||
byte magic = stream->readSByte(); // always 137?
|
||||
byte strLen = stream->readSByte(); // header string len
|
||||
char *buf = new char[strLen + 1];
|
||||
stream->read(buf, strLen);
|
||||
buf[strLen] = '\0';
|
||||
|
||||
int dataLen = stream->size() - (strLen + 2);
|
||||
byte *data = new byte[dataLen];
|
||||
debug(1, "midi patch %s loading magic %d str '%s'", targetSection.c_str(), magic, buf);
|
||||
delete [] buf;
|
||||
stream->read(data, dataLen);
|
||||
return new SciResource(data, dataLen, num);
|
||||
} else {
|
||||
chunk.skipContent();
|
||||
}
|
||||
}
|
||||
|
||||
warning("Didn't find section %s in midi patch resource %s", targetSection.c_str(), PATCH_RESOURCES[resNum]);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
} // end namespace Dgds
|
||||
833
engines/dgds/sound/midiparser_sci.cpp
Normal file
833
engines/dgds/sound/midiparser_sci.cpp
Normal file
@@ -0,0 +1,833 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// This file was mostly copied from SCI with light modifications.
|
||||
|
||||
#include "dgds/dgds.h"
|
||||
#include "dgds/sound/midiparser_sci.h"
|
||||
#include "dgds/sound/drivers/mididriver.h"
|
||||
|
||||
namespace Dgds {
|
||||
|
||||
static const int nMidiParams[] = { 2, 2, 2, 2, 1, 1, 2, 0 };
|
||||
|
||||
enum SciMidiCommands {
|
||||
kSetSignalLoop = 0x7F,
|
||||
kEndOfTrack = 0xFC,
|
||||
kSetReverb = 0x50,
|
||||
kMidiHold = 0x52,
|
||||
kUpdateCue = 0x60,
|
||||
kResetOnPause = 0x4C
|
||||
};
|
||||
|
||||
// MidiParser_SCI
|
||||
//
|
||||
MidiParser_SCI::MidiParser_SCI(SciMusic *music) :
|
||||
MidiParser() {
|
||||
_music = music;
|
||||
// mididata contains delta in 1/60th second
|
||||
// values of ppqn and tempo are found experimentally and may be wrong
|
||||
_ppqn = 1;
|
||||
MidiParser::setTempo(16667);
|
||||
|
||||
_track = nullptr;
|
||||
_pSnd = nullptr;
|
||||
_loopTick = 0;
|
||||
_masterVolume = 15;
|
||||
_volume = 127;
|
||||
|
||||
_resetOnPause = false;
|
||||
|
||||
_mainThreadCalled = false;
|
||||
|
||||
resetStateTracking();
|
||||
}
|
||||
|
||||
MidiParser_SCI::~MidiParser_SCI() {
|
||||
MidiParser_SCI::unloadMusic();
|
||||
// we do this, so that MidiParser won't be able to call his own ::allNotesOff()
|
||||
// this one would affect all channels and we can't let that happen
|
||||
_driver = nullptr;
|
||||
}
|
||||
|
||||
void MidiParser_SCI::mainThreadBegin() {
|
||||
assert(!_mainThreadCalled);
|
||||
_mainThreadCalled = true;
|
||||
}
|
||||
|
||||
void MidiParser_SCI::mainThreadEnd() {
|
||||
assert(_mainThreadCalled);
|
||||
_mainThreadCalled = false;
|
||||
}
|
||||
|
||||
bool MidiParser_SCI::loadMusic(SoundResource::Track *track, MusicEntry *psnd, int channelFilterMask) {
|
||||
unloadMusic();
|
||||
_track = track;
|
||||
_pSnd = psnd;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
_channelUsed[i] = false;
|
||||
_channelVolume[i] = 127;
|
||||
_channelRemap[i] = -1;
|
||||
}
|
||||
|
||||
midiMixChannels();
|
||||
|
||||
_numTracks = 1;
|
||||
_numSubtracks[0] = 1;
|
||||
_tracks[0][0] = const_cast<byte *>(_mixedData->data());
|
||||
if (_pSnd)
|
||||
setTrack(0);
|
||||
_loopTick = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
byte MidiParser_SCI::midiGetNextChannel(long ticker) {
|
||||
byte curr = 0xFF;
|
||||
long closest = ticker + 1000000, next = 0;
|
||||
|
||||
for (int i = 0; i < _track->channelCount; i++) {
|
||||
if (_track->channels[i].time == -1) // channel ended
|
||||
continue;
|
||||
SoundResource::Channel *curChannel = &_track->channels[i];
|
||||
if (curChannel->curPos >= curChannel->data.size())
|
||||
continue;
|
||||
next = curChannel->data[curChannel->curPos]; // when the next event should occur
|
||||
if (next == 0xF8) // 0xF8 means 240 ticks delay
|
||||
next = 240;
|
||||
next += _track->channels[i].time;
|
||||
if (next < closest) {
|
||||
curr = i;
|
||||
closest = next;
|
||||
}
|
||||
}
|
||||
|
||||
return curr;
|
||||
}
|
||||
|
||||
static inline bool validateNextRead(const SoundResource::Channel *channel) {
|
||||
if (channel->data.size() <= channel->curPos) {
|
||||
warning("Unexpected end of %s. Music may sound wrong due to game resource corruption", channel->data.name().c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MidiParser_SCI::midiMixChannels() {
|
||||
int totalSize = 0;
|
||||
|
||||
for (int i = 0; i < _track->channelCount; i++) {
|
||||
_track->channels[i].time = 0;
|
||||
_track->channels[i].prev = 0;
|
||||
_track->channels[i].curPos = 0;
|
||||
// Ignore the digital channel data, if it exists - it's not MIDI data
|
||||
if (i == _track->digitalChannelNr)
|
||||
continue;
|
||||
totalSize += _track->channels[i].data.size();
|
||||
}
|
||||
|
||||
SciSpan<byte> outData = _mixedData->allocate(totalSize * 2, Common::String::format("mixed sound.%d", _pSnd ? _pSnd->resourceId : -1)); // FIXME: creates overhead and still may be not enough to hold all data
|
||||
|
||||
long ticker = 0;
|
||||
byte channelNr, curDelta;
|
||||
byte midiCommand = 0, midiParam, globalPrev = 0;
|
||||
long newDelta;
|
||||
SoundResource::Channel *channel;
|
||||
bool breakOut = false;
|
||||
|
||||
while ((channelNr = midiGetNextChannel(ticker)) != 0xFF) { // there is still an active channel
|
||||
channel = &_track->channels[channelNr];
|
||||
if (!validateNextRead(channel))
|
||||
break;
|
||||
curDelta = channel->data[channel->curPos++];
|
||||
channel->time += (curDelta == 0xF8 ? 240 : curDelta); // when the command is supposed to occur
|
||||
if (curDelta == 0xF8)
|
||||
continue;
|
||||
newDelta = channel->time - ticker;
|
||||
ticker += newDelta;
|
||||
|
||||
if (channelNr == _track->digitalChannelNr)
|
||||
continue;
|
||||
if (!validateNextRead(channel))
|
||||
break;
|
||||
midiCommand = channel->data[channel->curPos++];
|
||||
if (midiCommand != kEndOfTrack) {
|
||||
// Write delta
|
||||
while (newDelta > 240) {
|
||||
*outData++ = 0xF8;
|
||||
newDelta -= 240;
|
||||
}
|
||||
*outData++ = (byte)newDelta;
|
||||
}
|
||||
|
||||
// Write command
|
||||
switch (midiCommand) {
|
||||
case 0xF0: // sysEx
|
||||
*outData++ = midiCommand;
|
||||
do {
|
||||
if (!validateNextRead(channel)) {
|
||||
breakOut = true;
|
||||
break;
|
||||
}
|
||||
midiParam = channel->data[channel->curPos++];
|
||||
*outData++ = midiParam;
|
||||
} while (midiParam != 0xF7);
|
||||
break;
|
||||
case kEndOfTrack: // end of channel
|
||||
channel->time = -1;
|
||||
break;
|
||||
default: // MIDI command
|
||||
if (midiCommand & 0x80) {
|
||||
if (!validateNextRead(channel)) {
|
||||
breakOut = true;
|
||||
break;
|
||||
}
|
||||
midiParam = channel->data[channel->curPos++];
|
||||
} else {// running status
|
||||
midiParam = midiCommand;
|
||||
midiCommand = channel->prev;
|
||||
}
|
||||
|
||||
// remember which channel got used for channel remapping
|
||||
byte midiChannel = midiCommand & 0xF;
|
||||
_channelUsed[midiChannel] = true;
|
||||
|
||||
if (midiCommand != globalPrev)
|
||||
*outData++ = midiCommand;
|
||||
*outData++ = midiParam;
|
||||
if (nMidiParams[(midiCommand >> 4) - 8] == 2) {
|
||||
if (!validateNextRead(channel)) {
|
||||
breakOut = true;
|
||||
break;
|
||||
}
|
||||
*outData++ = channel->data[channel->curPos++];
|
||||
}
|
||||
channel->prev = midiCommand;
|
||||
globalPrev = midiCommand;
|
||||
}
|
||||
|
||||
if (breakOut)
|
||||
break;
|
||||
}
|
||||
|
||||
// Insert stop event
|
||||
*outData++ = 0; // Delta
|
||||
*outData++ = 0xFF; // Meta event
|
||||
*outData++ = 0x2F; // End of track (EOT)
|
||||
*outData++ = 0x00;
|
||||
*outData++ = 0x00;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static inline bool validateNextRead(const SciSpan<const byte> &channelData, const SciSpan<const byte>::size_type size = 1) {
|
||||
if (channelData.size() < size) {
|
||||
warning("Unexpected end of %s. Music may sound wrong due to game resource corruption", channelData.name().c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void MidiParser_SCI::resetStateTracking() {
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
ChannelState &s = _channelState[i];
|
||||
s._modWheel = 0;
|
||||
s._pan = 64;
|
||||
s._patch = 0; // TODO: Initialize properly (from data in LoadMusic?)
|
||||
s._note = -1;
|
||||
s._sustain = false;
|
||||
s._pitchWheel = 0x2000;
|
||||
s._voices = 0;
|
||||
|
||||
_channelVolume[i] = 127;
|
||||
}
|
||||
}
|
||||
|
||||
void MidiParser_SCI::initTrack() {
|
||||
return;
|
||||
}
|
||||
|
||||
void MidiParser_SCI::sendInitCommands() {
|
||||
resetStateTracking();
|
||||
|
||||
// reset our "global" volume
|
||||
_volume = 127;
|
||||
|
||||
// Set initial voice count
|
||||
if (_pSnd) {
|
||||
for (int i = 0; i < _track->channelCount; ++i) {
|
||||
byte voiceCount = _track->channels[i].poly;
|
||||
byte num = _track->channels[i].number;
|
||||
// TODO: Should we skip the control channel?
|
||||
sendToDriver(0xB0 | num, 0x4B, voiceCount);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset all the parameters of the channels used by this song
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
if (_channelUsed[i]) {
|
||||
sendToDriver(0xB0 | i, 0x07, 127); // Reset volume to maximum
|
||||
sendToDriver(0xB0 | i, 0x0A, 64); // Reset panning to center
|
||||
sendToDriver(0xB0 | i, 0x40, 0); // Reset hold pedal to none
|
||||
sendToDriver(0xE0 | i, 0, 64); // Reset pitch wheel to center
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiParser_SCI::unloadMusic() {
|
||||
if (_pSnd) {
|
||||
resetTracking();
|
||||
allNotesOff();
|
||||
// Pending track init commands have to be removed from the queue,
|
||||
// since the sound thread will otherwise continue to try executing these.
|
||||
_music->removeTrackInitCommandsFromQueue(_pSnd);
|
||||
}
|
||||
_numTracks = 0;
|
||||
_pSnd = nullptr;
|
||||
_track = nullptr;
|
||||
_activeTrack = 255;
|
||||
_resetOnPause = false;
|
||||
_mixedData.clear();
|
||||
}
|
||||
|
||||
// this is used for scripts sending midi commands to us. we verify in that case that the channel is actually
|
||||
// used, so that channel remapping will work as well and then send them on
|
||||
void MidiParser_SCI::sendFromScriptToDriver(uint32 midi) {
|
||||
byte midiChannel = midi & 0xf;
|
||||
|
||||
if (!_channelUsed[midiChannel]) {
|
||||
// trying to send to an unused channel
|
||||
// this happens for cmdSendMidi at least in sq1vga right at the start, it's a script issue
|
||||
return;
|
||||
}
|
||||
|
||||
if ((midi & 0xFFF0) == 0x4EB0 && DgdsEngine::getInstance()->getGameId() != GID_DRAGON) {
|
||||
// We have to handle this here instead of inside the trackState() method (which handles the input from
|
||||
// the actual midi data). The mute command when sent from the script is independent from the mute
|
||||
// command sent by the actual midi data. The script mute is stacked on the high nibble, while the midi
|
||||
// data mute is stored on the low nibble. So the script cannot undo a mute set by the midi data and vice
|
||||
// versa.
|
||||
byte channel = midi & 0xf;
|
||||
bool op = (midi >> 16) & 0x7f;
|
||||
uint8 m = _pSnd->_chan[channel]._mute;
|
||||
|
||||
if (op && _pSnd->_chan[channel]._mute < 0xF0)
|
||||
_pSnd->_chan[channel]._mute += 0x10;
|
||||
else if (!op && _pSnd->_chan[channel]._mute >= 0x10)
|
||||
_pSnd->_chan[channel]._mute -= 0x10;
|
||||
|
||||
if (_pSnd->_chan[channel]._mute != m) {
|
||||
// CHECKME: Should we directly call remapChannels() if _mainThreadCalled?
|
||||
_music->needsRemap();
|
||||
debugC(2, kDebugLevelSound, "Dynamic mute change (arg = %d, mainThread = %d)", m, _mainThreadCalled);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sendToDriver(midi);
|
||||
}
|
||||
|
||||
void MidiParser_SCI::sendToDriver(uint32 midi) {
|
||||
byte midiChannel = midi & 0xf;
|
||||
|
||||
// State tracking
|
||||
if (!_pSnd->_chan[midiChannel]._dontMap)
|
||||
trackState(midi);
|
||||
|
||||
if ((midi & 0xFFF0) == 0x4EB0 && DgdsEngine::getInstance()->getGameId() != GID_DRAGON) {
|
||||
// Mute. Handled in trackState()/sendFromScriptToDriver().
|
||||
return;
|
||||
}
|
||||
|
||||
if ((midi & 0xFFF0) == 0x07B0) {
|
||||
// someone trying to set channel volume?
|
||||
int channelVolume = (midi >> 16) & 0xFF;
|
||||
// Adjust volume accordingly to current local volume
|
||||
channelVolume = channelVolume * _volume / 127;
|
||||
midi = (midi & 0xFFFF) | ((channelVolume & 0xFF) << 16);
|
||||
}
|
||||
|
||||
// Channel remapping
|
||||
uint8 msg = (midi & 0xF0);
|
||||
int16 realChannel = _channelRemap[midiChannel];
|
||||
if (_pSnd->_chan[midiChannel]._dontMap) {
|
||||
// The dontMap channel is supposed to have limited access, if the device channel is already in use.
|
||||
// It probably won't happen, but the original does these checks...
|
||||
if (!_music->isDeviceChannelMapped(midiChannel) || (msg != 0xB0 && msg != 0xC0 && msg != 0xE0))
|
||||
realChannel = midiChannel;
|
||||
}
|
||||
|
||||
if (realChannel == -1)
|
||||
return;
|
||||
|
||||
midi = (midi & 0xFFFFFFF0) | realChannel;
|
||||
sendToDriver_raw(midi);
|
||||
}
|
||||
|
||||
void MidiParser_SCI::sendToDriver_raw(uint32 midi) {
|
||||
if (_mainThreadCalled)
|
||||
_music->putMidiCommandInQueue(midi);
|
||||
else
|
||||
_driver->send(midi);
|
||||
}
|
||||
|
||||
void MidiParser_SCI::trackState(uint32 b) {
|
||||
// We keep track of most of the state of a midi channel, so we can
|
||||
// at any time reset the device to the current state, even if the
|
||||
// channel has been temporarily disabled due to remapping.
|
||||
|
||||
byte command = b & 0xf0;
|
||||
byte channel = b & 0xf;
|
||||
byte op1 = (b >> 8) & 0x7f;
|
||||
byte op2 = (b >> 16) & 0x7f;
|
||||
|
||||
ChannelState &s = _channelState[channel];
|
||||
|
||||
switch (command) {
|
||||
case 0x90:
|
||||
if (op2 != 0) {
|
||||
// note on
|
||||
s._note = op1;
|
||||
break;
|
||||
}
|
||||
// else, fall-through
|
||||
case 0x80:
|
||||
// note off
|
||||
if (s._note == op1)
|
||||
s._note = -1;
|
||||
break;
|
||||
case 0xB0:
|
||||
// control change
|
||||
switch (op1) {
|
||||
case 0x01: // mod wheel
|
||||
s._modWheel = op2;
|
||||
break;
|
||||
case 0x07: // channel volume
|
||||
_channelVolume[channel] = op2;
|
||||
break;
|
||||
case 0x0A: // pan
|
||||
s._pan = op2;
|
||||
break;
|
||||
case 0x40: // sustain
|
||||
s._sustain = (op2 != 0);
|
||||
break;
|
||||
case 0x4B: // voices
|
||||
if (s._voices != op2) {
|
||||
// CHECKME: Should we directly call remapChannels() if _mainThreadCalled?
|
||||
debugC(2, kDebugLevelSound, "Dynamic voice change (%d to %d)", s._voices, op2);
|
||||
_music->needsRemap();
|
||||
}
|
||||
s._voices = op2;
|
||||
_pSnd->_chan[channel]._voices = op2; // Also sync our MusicEntry
|
||||
break;
|
||||
case 0x4E: // mute
|
||||
// This is channel mute only for HOC+.
|
||||
if (DgdsEngine::getInstance()->getGameId() != GID_DRAGON) {
|
||||
// This is handled slightly differently than what we do in sendFromScriptToDriver(). The script mute is stacked
|
||||
// on the high nibble, while the midi data mute (this one here) is stored on the low nibble. So the script cannot
|
||||
// undo a mute set by the midi data and vice versa.
|
||||
uint8 m = (_pSnd->_chan[channel]._mute & 0xf0) | (op2 & 1);
|
||||
if (_pSnd->_chan[channel]._mute != m) {
|
||||
_pSnd->_chan[channel]._mute = m;
|
||||
// CHECKME: Should we directly call remapChannels() if _mainThreadCalled?
|
||||
_music->needsRemap();
|
||||
debugC(2, kDebugLevelSound, "Dynamic mute change (arg = %d, mainThread = %d)", m, _mainThreadCalled);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 0xC0:
|
||||
// program change
|
||||
s._patch = op1;
|
||||
break;
|
||||
case 0xE0:
|
||||
// pitchwheel
|
||||
s._pitchWheel = (op2 << 7) | op1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MidiParser_SCI::parseNextEvent(EventInfo &info) {
|
||||
const byte *playPos = _position._subtracks[0]._playPos;
|
||||
|
||||
info.start = playPos;
|
||||
info.delta = 0;
|
||||
while (*playPos == 0xF8) {
|
||||
info.delta += 240;
|
||||
playPos++;
|
||||
}
|
||||
info.delta += *(playPos++);
|
||||
|
||||
// Process the next info.
|
||||
if ((playPos[0] & 0xF0) >= 0x80)
|
||||
info.event = *(playPos++);
|
||||
else
|
||||
info.event = _position._subtracks[0]._runningStatus;
|
||||
if (info.event < 0x80) {
|
||||
_position._subtracks[0]._playPos = playPos;
|
||||
return;
|
||||
}
|
||||
|
||||
_position._subtracks[0]._runningStatus = info.event;
|
||||
switch (info.command()) {
|
||||
case 0xC:
|
||||
case 0xD:
|
||||
info.basic.param1 = *(playPos++);
|
||||
info.basic.param2 = 0;
|
||||
break;
|
||||
|
||||
case 0xB:
|
||||
info.basic.param1 = *(playPos++);
|
||||
info.basic.param2 = *(playPos++);
|
||||
info.length = 0;
|
||||
break;
|
||||
|
||||
case 0x8:
|
||||
case 0x9:
|
||||
case 0xA:
|
||||
case 0xE:
|
||||
info.basic.param1 = *(playPos++);
|
||||
info.basic.param2 = *(playPos++);
|
||||
if (info.command() == 0x9 && info.basic.param2 == 0) {
|
||||
// NoteOn with param2==0 is a NoteOff
|
||||
info.event = info.channel() | 0x80;
|
||||
}
|
||||
info.length = 0;
|
||||
break;
|
||||
|
||||
case 0xF: // System Common, Meta or SysEx event
|
||||
switch (info.event & 0x0F) {
|
||||
case 0x2: // Song Position Pointer
|
||||
info.basic.param1 = *(playPos++);
|
||||
info.basic.param2 = *(playPos++);
|
||||
break;
|
||||
|
||||
case 0x3: // Song Select
|
||||
info.basic.param1 = *(playPos++);
|
||||
info.basic.param2 = 0;
|
||||
break;
|
||||
|
||||
case 0x6:
|
||||
case 0x8:
|
||||
case 0xA:
|
||||
case 0xB:
|
||||
case 0xC:
|
||||
case 0xE:
|
||||
info.basic.param1 = info.basic.param2 = 0;
|
||||
break;
|
||||
|
||||
case 0x0: // SysEx
|
||||
info.length = readVLQ(playPos);
|
||||
info.ext.data = playPos;
|
||||
playPos += info.length;
|
||||
break;
|
||||
|
||||
case 0xF: // META event
|
||||
info.ext.type = *(playPos++);
|
||||
info.length = readVLQ(playPos);
|
||||
info.ext.data = playPos;
|
||||
playPos += info.length;
|
||||
break;
|
||||
default:
|
||||
warning(
|
||||
"MidiParser_SCI::parseNextEvent: Unsupported event code %x",
|
||||
info.event);
|
||||
} // // System Common, Meta or SysEx event
|
||||
|
||||
default:
|
||||
break;
|
||||
}// switch (info.command())
|
||||
|
||||
_position._subtracks[0]._playPos = playPos;
|
||||
}
|
||||
|
||||
bool MidiParser_SCI::processEvent(const EventInfo &info, bool fireEvents) {
|
||||
if (!fireEvents) {
|
||||
// We don't do any processing that should be done while skipping events
|
||||
return MidiParser::processEvent(info, fireEvents);
|
||||
}
|
||||
|
||||
switch (info.command()) {
|
||||
case 0xC:
|
||||
if (info.channel() == 0xF) {// SCI special case
|
||||
if (info.basic.param1 == kSetSignalLoop) {
|
||||
_loopTick = _position._lastEventTick + info.delta;
|
||||
return true;
|
||||
}
|
||||
|
||||
// At least in kq5/french&mac the first scene in the intro has
|
||||
// a song that sets signal to 4 immediately on tick 0. Signal
|
||||
// isn't set at that point by sierra sci and it would cause the
|
||||
// castle daventry text to get immediately removed, so we
|
||||
// currently filter it. Sierra SCI ignores them as well at that
|
||||
// time. However, this filtering should only be performed for
|
||||
// SCI1 and newer games. Signaling is done differently in SCI0
|
||||
// though, so ignoring these signals in SCI0 games will result
|
||||
// in glitches (e.g. the intro of LB1 Amiga gets stuck - bug
|
||||
// #5693). Refer to MusicEntry::setSignal() in sound/music.cpp.
|
||||
// FIXME: SSCI doesn't start playing at the very beginning
|
||||
// of the stream, but at a fixed location a few commands later.
|
||||
// That is probably why this signal isn't triggered
|
||||
// immediately there.
|
||||
bool skipSignal = false;
|
||||
if (!_position._playTick) {
|
||||
skipSignal = true;
|
||||
}
|
||||
if (!skipSignal) {
|
||||
if (!_jumpingToTick) {
|
||||
_pSnd->setSignal(info.basic.param1);
|
||||
debugC(4, kDebugLevelSound, "signal %04x", info.basic.param1);
|
||||
}
|
||||
}
|
||||
|
||||
// Done with this event.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Break to let parent handle the rest.
|
||||
break;
|
||||
case 0xB:
|
||||
// Reference for some events:
|
||||
// https://wiki.scummvm.org/index.php/SCI/Specifications/Sound/SCI0_Resource_Format#Status_Reference
|
||||
// Handle common special events
|
||||
switch (info.basic.param1) {
|
||||
case kSetReverb:
|
||||
if (info.basic.param2 == 127) // Set global reverb instead
|
||||
_pSnd->reverb = _music->getGlobalReverb();
|
||||
else
|
||||
_pSnd->reverb = info.basic.param2;
|
||||
|
||||
((MidiPlayer *)_driver)->setReverb(_pSnd->reverb);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle events sent to the SCI special channel (15)
|
||||
if (info.channel() == 0xF) {
|
||||
switch (info.basic.param1) {
|
||||
case kSetReverb:
|
||||
// Already handled above
|
||||
return true;
|
||||
case kMidiHold:
|
||||
// Check if the hold ID marker is the same as the hold ID
|
||||
// marker set for that song by cmdSetSoundHold.
|
||||
// If it is, loop back, but don't stop notes when jumping.
|
||||
if (info.basic.param2 == _pSnd->hold) {
|
||||
jumpToTick(_loopTick, false, false);
|
||||
// Done with this event.
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
case kUpdateCue:
|
||||
if (!_jumpingToTick) {
|
||||
_pSnd->dataInc += 1;
|
||||
debugC(4, "datainc %04x", 1);
|
||||
|
||||
}
|
||||
return true;
|
||||
case kResetOnPause:
|
||||
_resetOnPause = info.basic.param2;
|
||||
return true;
|
||||
// Unhandled SCI commands
|
||||
case 0x46: // LSL3 - binoculars
|
||||
case 0x61: // Iceman (AdLib?)
|
||||
case 0x73: // Hoyle
|
||||
case 0xD1: // KQ4, when riding the unicorn
|
||||
// Obscure SCI commands - ignored
|
||||
return true;
|
||||
// Standard MIDI commands
|
||||
case 0x01: // mod wheel
|
||||
case 0x04: // foot controller
|
||||
case 0x07: // channel volume
|
||||
case 0x0A: // pan
|
||||
case 0x0B: // expression
|
||||
case 0x40: // sustain
|
||||
case 0x79: // reset all
|
||||
case 0x7B: // notes off
|
||||
// These are all handled by the music driver, so ignore them
|
||||
break;
|
||||
case 0x4B: // voice mapping
|
||||
// TODO: is any support for this needed at the MIDI parser level?
|
||||
warning("Unhanded SCI MIDI command 0x%x - voice mapping (parameter %d)", info.basic.param1, info.basic.param2);
|
||||
return true;
|
||||
default:
|
||||
warning("Unhandled SCI MIDI command 0x%x (parameter %d)", info.basic.param1, info.basic.param2);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Break to let parent handle the rest.
|
||||
break;
|
||||
case 0xF: // META event
|
||||
if (info.ext.type == 0x2F) {// end of track reached
|
||||
// QFG3 abuses the hold flag. Its scripts call kDoSoundSetHold,
|
||||
// but sometimes there's no hold marker in the associated songs
|
||||
// (e.g. song 110, during the intro). The original interpreter
|
||||
// treats this case as an infinite loop (bug #5744).
|
||||
if (_pSnd->loop || _pSnd->hold > 0) {
|
||||
// Change from SCI: Don't stop current notes on loop.
|
||||
if (!jumpToTick(_loopTick, false, false)) {
|
||||
// Jumping to the specified tick failed. This means play
|
||||
// position is still at the end of the MIDI data. Stop the
|
||||
// sound to prevent reading out of bounds.
|
||||
warning("Jump to tick at end of looping sound failed! Stopping sound.");
|
||||
_music->soundStop(_pSnd);
|
||||
}
|
||||
|
||||
// Done with this event.
|
||||
return true;
|
||||
|
||||
} else {
|
||||
_pSnd->setSignal(0xffff); // SIGNAL_OFFSET
|
||||
|
||||
debugC(4, kDebugLevelSound, "signal EOT");
|
||||
}
|
||||
}
|
||||
|
||||
// Break to let parent handle the rest.
|
||||
break;
|
||||
|
||||
default:
|
||||
// Break to let parent handle the rest.
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Let parent handle the rest
|
||||
return MidiParser::processEvent(info, fireEvents);
|
||||
}
|
||||
|
||||
byte MidiParser_SCI::getSongReverb() {
|
||||
assert(_track);
|
||||
|
||||
for (int i = 0; i < _track->channelCount; i++) {
|
||||
SoundResource::Channel &channel = _track->channels[i];
|
||||
// Peek ahead in the control channel to get the default reverb setting
|
||||
if (channel.number == 15 && channel.data.size() >= 7)
|
||||
return channel.data[6];
|
||||
}
|
||||
|
||||
return 127;
|
||||
}
|
||||
|
||||
void MidiParser_SCI::allNotesOff() {
|
||||
if (!_driver)
|
||||
return;
|
||||
|
||||
int i, j;
|
||||
|
||||
// Turn off all active notes
|
||||
for (i = 0; i < 128; ++i) {
|
||||
for (j = 0; j < 16; ++j) {
|
||||
if ((_activeNotes[i] & (1 << j)) && (_channelRemap[j] != -1)){
|
||||
sendToDriver(0x80 | j, i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Turn off all hanging notes
|
||||
for (i = 0; i < ARRAYSIZE(_hangingNotes); i++) {
|
||||
byte midiChannel = _hangingNotes[i].channel;
|
||||
if ((_hangingNotes[i].timeLeft) && (_channelRemap[midiChannel] != -1)) {
|
||||
sendToDriver(0x80 | midiChannel, _hangingNotes[i].note, 0);
|
||||
_hangingNotes[i].timeLeft = 0;
|
||||
}
|
||||
}
|
||||
_hangingNotesCount = 0;
|
||||
|
||||
// To be sure, send an "All Note Off" event (but not all MIDI devices
|
||||
// support this...).
|
||||
|
||||
for (i = 0; i < 16; ++i) {
|
||||
if (_channelRemap[i] != -1) {
|
||||
sendToDriver(0xB0 | i, 0x7b, 0); // All notes off
|
||||
sendToDriver(0xB0 | i, 0x40, 0); // Also send a sustain off event (bug #5524)
|
||||
}
|
||||
}
|
||||
|
||||
memset(_activeNotes, 0, sizeof(_activeNotes));
|
||||
}
|
||||
|
||||
void MidiParser_SCI::setMasterVolume(byte masterVolume) {
|
||||
assert(masterVolume <= MUSIC_MASTERVOLUME_MAX);
|
||||
_masterVolume = masterVolume;
|
||||
((MidiPlayer *)_driver)->setVolume(masterVolume);
|
||||
}
|
||||
|
||||
void MidiParser_SCI::setVolume(byte volume) {
|
||||
assert(volume <= MUSIC_VOLUME_MAX);
|
||||
_volume = volume;
|
||||
|
||||
for (int i = 0; i < 15; i++)
|
||||
if (_channelRemap[i] != -1)
|
||||
sendToDriver(0xB0 + i, 7, _channelVolume[i]);
|
||||
}
|
||||
|
||||
void MidiParser_SCI::remapChannel(int channel, int devChannel) {
|
||||
if (_channelRemap[channel] == devChannel)
|
||||
return;
|
||||
|
||||
_channelRemap[channel] = devChannel;
|
||||
|
||||
if (devChannel == -1)
|
||||
return;
|
||||
|
||||
// debug(1, " restoring state: channel %d on devChannel %d", channel, devChannel);
|
||||
|
||||
// restore state
|
||||
ChannelState &s = _channelState[channel];
|
||||
|
||||
int channelVolume = _channelVolume[channel];
|
||||
channelVolume = (channelVolume * _volume / 127) & 0xFF;
|
||||
byte pitch1 = s._pitchWheel & 0x7F;
|
||||
byte pitch2 = (s._pitchWheel >> 7) & 0x7F;
|
||||
|
||||
sendToDriver_raw(0x0040B0 | devChannel); // sustain off
|
||||
sendToDriver_raw(0x004BB0 | devChannel | (s._voices << 16));
|
||||
sendToDriver_raw(0x0000C0 | devChannel | (s._patch << 8));
|
||||
sendToDriver_raw(0x0007B0 | devChannel | (channelVolume << 16));
|
||||
sendToDriver_raw(0x000AB0 | devChannel | (s._pan << 16));
|
||||
sendToDriver_raw(0x0001B0 | devChannel | (s._modWheel << 16));
|
||||
sendToDriver_raw(0x0040B0 | devChannel | (s._sustain ? 0x7F0000 : 0));
|
||||
sendToDriver_raw(0x0000E0 | devChannel | (pitch1 << 8) | (pitch2 << 16));
|
||||
|
||||
// CHECKME: Some SSCI version send a control change 0x4E with s._note as
|
||||
// parameter.
|
||||
// We need to investigate how (and if) drivers should act on this.
|
||||
// Related: controller 0x4E is used for 'mute' in the midiparser.
|
||||
// This could be a bug in SSCI that went unnoticed because few (or no?)
|
||||
// drivers implement controller 0x4E
|
||||
|
||||
// NB: The line below is _not_ valid since s._note can be 0xFF.
|
||||
// SSCI handles this out of band in the driver interface.
|
||||
// sendToDriver_raw(0x004EB0 | devChannel | (s._note << 16);
|
||||
}
|
||||
|
||||
} // End of namespace Dgds
|
||||
132
engines/dgds/sound/midiparser_sci.h
Normal file
132
engines/dgds/sound/midiparser_sci.h
Normal file
@@ -0,0 +1,132 @@
|
||||
/* 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 DGDS_SOUND_MIDIPARSER_H
|
||||
#define DGDS_SOUND_MIDIPARSER_H
|
||||
|
||||
#include "dgds/sound/music.h"
|
||||
#include "dgds/sound/resource/sci_resource.h"
|
||||
#include "audio/midiparser.h"
|
||||
|
||||
/*
|
||||
Sound drivers info: (from driver cmd0)
|
||||
AdLib/SB : track 0 , voices 9 , patch 3 ah=1
|
||||
ProAudioSp: track 0 , voices 9 , patch 3 ah=17
|
||||
GenerlMIDI: track 7 , voices 32, patch 4 ah=1 SCI1.1
|
||||
Game Blast: track 9 , voices 12, patch 101 ah=1
|
||||
MT-32 : track 12, voices 32, patch 1 ah=1
|
||||
PC Speaker: track 18, voices 1 , patch 0xFF ah=1
|
||||
Tandy : track 19, voices 3 , patch 101 ah=1
|
||||
IBM PS/1 : track 19, voices 3 , patch 101 ah=1
|
||||
*/
|
||||
|
||||
namespace Dgds {
|
||||
|
||||
/**
|
||||
* An extended standard MIDI (SMF) parser. Sierra used an extra channel
|
||||
* with special commands for extended functionality and animation syncing.
|
||||
* Refer to MidiParser_SMF() in /sound/midiparser_smf.cpp for the standard
|
||||
* MIDI (SMF) parser functionality that the SCI MIDI parser is based on
|
||||
*/
|
||||
class MidiParser_SCI : public MidiParser {
|
||||
public:
|
||||
MidiParser_SCI(SciMusic *music);
|
||||
~MidiParser_SCI() override;
|
||||
|
||||
void mainThreadBegin();
|
||||
void mainThreadEnd();
|
||||
|
||||
bool loadMusic(SoundResource::Track *track, MusicEntry *psnd, int channelFilterMask);
|
||||
bool loadMusic(const byte *, uint32) override {
|
||||
return false;
|
||||
}
|
||||
void initTrack();
|
||||
void sendInitCommands();
|
||||
void unloadMusic() override;
|
||||
void setMasterVolume(byte masterVolume);
|
||||
void setVolume(byte volume);
|
||||
void stop() {
|
||||
_abortParse = true;
|
||||
allNotesOff();
|
||||
}
|
||||
void pause() {
|
||||
allNotesOff();
|
||||
if (_resetOnPause)
|
||||
jumpToTick(0);
|
||||
}
|
||||
|
||||
void allNotesOff() override;
|
||||
|
||||
const SciSpan<const byte> &getMixedData() const { return *_mixedData; }
|
||||
byte getSongReverb();
|
||||
|
||||
void sendFromScriptToDriver(uint32 midi);
|
||||
void sendToDriver(uint32 midi) override;
|
||||
void sendToDriver(byte status, byte firstOp, byte secondOp) {
|
||||
sendToDriver(status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16));
|
||||
}
|
||||
|
||||
void remapChannel(int channel, int devChannel);
|
||||
|
||||
protected:
|
||||
void parseNextEvent(EventInfo &info) override;
|
||||
bool processEvent(const EventInfo &info, bool fireEvents = true) override;
|
||||
void midiMixChannels();
|
||||
byte midiGetNextChannel(long ticker);
|
||||
void resetStateTracking();
|
||||
void trackState(uint32 midi);
|
||||
void sendToDriver_raw(uint32 midi);
|
||||
|
||||
SciMusic *_music;
|
||||
|
||||
// this is set, when main thread calls us -> we send commands to queue instead to driver
|
||||
bool _mainThreadCalled;
|
||||
|
||||
Common::SpanOwner<SciSpan<const byte> > _mixedData;
|
||||
SoundResource::Track *_track;
|
||||
MusicEntry *_pSnd;
|
||||
uint32 _loopTick;
|
||||
byte _masterVolume; // the overall master volume (same for all tracks)
|
||||
byte _volume; // the global volume of the current track
|
||||
|
||||
bool _resetOnPause;
|
||||
|
||||
bool _channelUsed[16];
|
||||
int16 _channelRemap[16];
|
||||
byte _channelVolume[16];
|
||||
|
||||
struct ChannelState {
|
||||
int8 _modWheel;
|
||||
int8 _pan;
|
||||
int8 _patch;
|
||||
int8 _note;
|
||||
bool _sustain;
|
||||
int16 _pitchWheel;
|
||||
int8 _voices;
|
||||
};
|
||||
|
||||
ChannelState _channelState[16];
|
||||
|
||||
};
|
||||
|
||||
} // End of namespace Dgds
|
||||
|
||||
#endif // DGDS_SOUND_MIDIPARSER_H
|
||||
1455
engines/dgds/sound/music.cpp
Normal file
1455
engines/dgds/sound/music.cpp
Normal file
File diff suppressed because it is too large
Load Diff
316
engines/dgds/sound/music.h
Normal file
316
engines/dgds/sound/music.h
Normal file
@@ -0,0 +1,316 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file and all files in this directory were imported from the SCI
|
||||
* engine and lightly modified to match DGDS, removing:
|
||||
* * SCI0, SCI2, and SCI32 code
|
||||
* * game-specific code
|
||||
* * some unsupported devices
|
||||
*
|
||||
* The original games also used lightly modified SCI midi drivers.
|
||||
*/
|
||||
|
||||
#ifndef DGDS_SOUND_MUSIC_H
|
||||
#define DGDS_SOUND_MUSIC_H
|
||||
|
||||
#include "common/mutex.h"
|
||||
|
||||
#include "audio/mixer.h"
|
||||
|
||||
#include "dgds/console.h"
|
||||
#include "dgds/sound/drivers/mididriver.h"
|
||||
#include "dgds/sound/resource/sci_resource.h"
|
||||
|
||||
namespace Audio {
|
||||
class LoopingAudioStream;
|
||||
class RewindableAudioStream;
|
||||
}
|
||||
|
||||
namespace Dgds {
|
||||
|
||||
enum SoundStatus {
|
||||
kSoundStopped = 0,
|
||||
kSoundInitialized = 1,
|
||||
kSoundPaused = 2,
|
||||
kSoundPlaying = 3
|
||||
};
|
||||
|
||||
#define MUSIC_VOLUME_DEFAULT 127
|
||||
#define MUSIC_VOLUME_MAX 127
|
||||
#define MUSIC_MASTERVOLUME_DEFAULT 15
|
||||
#define MUSIC_MASTERVOLUME_MAX 15
|
||||
|
||||
class MidiParser_SCI;
|
||||
|
||||
typedef Common::Array<uint16> SignalQueue;
|
||||
|
||||
|
||||
struct MusicEntryChannel {
|
||||
// Channel info
|
||||
int8 _prio; // 0 = essential; lower is higher priority
|
||||
int8 _voices;
|
||||
bool _dontRemap;
|
||||
bool _dontMap;
|
||||
uint8 _mute;
|
||||
};
|
||||
|
||||
|
||||
class MusicEntry {
|
||||
public:
|
||||
// Do not get these directly for the sound objects!
|
||||
// It's a bad idea, as the sound code (i.e. the SciMusic
|
||||
// class) should be as separate as possible from the rest
|
||||
// of the engine
|
||||
uint32 soundObj;
|
||||
|
||||
SoundResource *soundRes;
|
||||
int32 resourceId;
|
||||
|
||||
int time; // "tim"estamp to indicate in which order songs have been added
|
||||
|
||||
uint16 dataInc;
|
||||
uint16 ticker;
|
||||
uint16 signal;
|
||||
int16 priority; // must be int16, at least in Laura Bow 1, main music (object conMusic) uses priority -1
|
||||
uint16 loop;
|
||||
int16 volume;
|
||||
int16 hold;
|
||||
int8 reverb;
|
||||
bool playBed;
|
||||
bool overridePriority; // Use soundObj's priority instead of resource's
|
||||
|
||||
int16 pauseCounter;
|
||||
uint sampleLoopCounter;
|
||||
|
||||
byte fadeTo;
|
||||
short fadeStep;
|
||||
uint32 fadeTicker;
|
||||
uint32 fadeTickerStep;
|
||||
bool fadeSetVolume;
|
||||
bool fadeCompleted;
|
||||
bool stopAfterFading;
|
||||
|
||||
SoundStatus status;
|
||||
|
||||
Audio::Mixer::SoundType soundType;
|
||||
|
||||
int _usedChannels[16];
|
||||
MusicEntryChannel _chan[16];
|
||||
MidiParser_SCI *pMidiParser;
|
||||
|
||||
// this is used for storing signals, when the current signal is not yet
|
||||
// sent to the scripts. We shouldn't need to save it, this normally only
|
||||
// happens in rare situations like lb1, knocking on the door in the attic
|
||||
SignalQueue signalQueue;
|
||||
|
||||
// TODO: We need to revise how we store the different
|
||||
// audio stream objects we require.
|
||||
Audio::RewindableAudioStream *pStreamAud;
|
||||
Audio::LoopingAudioStream *pLoopStream;
|
||||
Audio::SoundHandle hCurrentAud;
|
||||
bool isSample;
|
||||
|
||||
public:
|
||||
MusicEntry();
|
||||
~MusicEntry();
|
||||
|
||||
void doFade();
|
||||
void onTimer();
|
||||
void setSignal(int signal);
|
||||
};
|
||||
|
||||
struct DeviceChannelUsage {
|
||||
MusicEntry *_song;
|
||||
int _channel;
|
||||
bool operator==(const DeviceChannelUsage& other) const { return _song == other._song && _channel == other._channel; }
|
||||
bool operator!=(const DeviceChannelUsage& other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
struct ChannelRemapping {
|
||||
DeviceChannelUsage _map[16];
|
||||
int _prio[16];
|
||||
int _voices[16];
|
||||
bool _dontRemap[16];
|
||||
int _freeVoices;
|
||||
|
||||
void clear();
|
||||
void swap(int i, int j);
|
||||
void evict(int i);
|
||||
ChannelRemapping& operator=(ChannelRemapping& other);
|
||||
int lowestPrio() const;
|
||||
};
|
||||
|
||||
struct MidiCommand {
|
||||
enum CmdType {
|
||||
kTypeMidiMessage = 0,
|
||||
kTypeTrackInit
|
||||
};
|
||||
// Keeping this very simple, due to the very limited purpose of it.
|
||||
MidiCommand(CmdType type, uint32 val) : _type(type), _dataPtr(0), _dataVal(val) {}
|
||||
MidiCommand(CmdType type, void *ptr) : _type(type), _dataPtr(ptr), _dataVal(0) {}
|
||||
CmdType _type;
|
||||
void *_dataPtr;
|
||||
uint32 _dataVal;
|
||||
};
|
||||
|
||||
typedef Common::Array<MusicEntry *> MusicList;
|
||||
typedef Common::Array<MidiCommand> MidiCommandQueue;
|
||||
|
||||
class SciMusic {
|
||||
|
||||
public:
|
||||
SciMusic(bool useDigitalSFX);
|
||||
~SciMusic();
|
||||
|
||||
void init();
|
||||
|
||||
void onTimer();
|
||||
void putMidiCommandInQueue(byte status, byte firstOp, byte secondOp);
|
||||
void putMidiCommandInQueue(uint32 midi);
|
||||
void putTrackInitCommandInQueue(MusicEntry *psnd);
|
||||
void removeTrackInitCommandsFromQueue(MusicEntry *psnd);
|
||||
private:
|
||||
static void miditimerCallback(void *p);
|
||||
void sendMidiCommandsFromQueue();
|
||||
|
||||
public:
|
||||
void clearPlayList();
|
||||
void pauseAll(bool pause);
|
||||
bool isAllPaused() const { return (_globalPause > 0); }
|
||||
void resetGlobalPauseCounter();
|
||||
void stopAll();
|
||||
void stopAllSamples();
|
||||
void stopMusic();
|
||||
void pauseMusic();
|
||||
void resumeMusic();
|
||||
void stopSFX();
|
||||
|
||||
// sound and midi functions
|
||||
void soundInitSnd(MusicEntry *pSnd);
|
||||
void soundPlay(MusicEntry *pSnd, bool restoring = false);
|
||||
void soundStop(MusicEntry *pSnd);
|
||||
void soundKill(MusicEntry *pSnd);
|
||||
void soundPause(MusicEntry *pSnd);
|
||||
void soundResume(MusicEntry *pSnd);
|
||||
void soundToggle(MusicEntry *pSnd, bool pause);
|
||||
void soundSetVolume(MusicEntry *pSnd, byte volume);
|
||||
void soundSetSampleVolume(MusicEntry *pSnd, byte volume);
|
||||
void soundSetPriority(MusicEntry *pSnd, byte prio);
|
||||
uint16 soundGetMasterVolume();
|
||||
void soundSetMasterVolume(uint16 vol);
|
||||
uint16 soundGetSoundOn() const { return _soundOn; }
|
||||
void soundSetSoundOn(bool soundOnFlag);
|
||||
uint16 soundGetVoices();
|
||||
uint32 soundGetTempo() const { return _dwTempo; }
|
||||
MusicType soundGetMusicType() const { return _musicType; }
|
||||
|
||||
bool isSoundActive(MusicEntry *pSnd) {
|
||||
assert(pSnd->pStreamAud != 0);
|
||||
return _pMixer->isSoundHandleActive(pSnd->hCurrentAud);
|
||||
}
|
||||
|
||||
void updateAudioStreamTicker(MusicEntry *pSnd) {
|
||||
assert(pSnd->pStreamAud != 0);
|
||||
pSnd->ticker = (uint16)(_pMixer->getSoundElapsedTime(pSnd->hCurrentAud) * 0.06);
|
||||
}
|
||||
|
||||
MusicEntry *getSlot(uint32 obj);
|
||||
MusicEntry *getFirstSlotWithStatus(SoundStatus status);
|
||||
|
||||
void pushBackSlot(MusicEntry *slotEntry) {
|
||||
Common::StackLock lock(_mutex);
|
||||
_playList.push_back(slotEntry);
|
||||
}
|
||||
|
||||
void printPlayList(Console *con);
|
||||
void printSongInfo(uint32 obj, Console *con);
|
||||
|
||||
// The following two methods are NOT thread safe - make sure that
|
||||
// the mutex is always locked before calling them
|
||||
MusicList::iterator getPlayListStart() { return _playList.begin(); }
|
||||
MusicList::iterator getPlayListEnd() { return _playList.end(); }
|
||||
|
||||
//void sendMidiCommand(uint32 cmd);
|
||||
//void sendMidiCommand(MusicEntry *pSnd, uint32 cmd);
|
||||
|
||||
void setGlobalReverb(int8 reverb);
|
||||
int8 getGlobalReverb() { return _globalReverb; }
|
||||
|
||||
byte getCurrentReverb();
|
||||
|
||||
void needsRemap() { _needsRemap = true; }
|
||||
|
||||
// Mutex for music code. Used to guard access to the song playlist, to the
|
||||
// MIDI parser and to the MIDI driver/player. We use a reference to
|
||||
// the mixer's internal mutex to avoid deadlocks which sometimes occur when
|
||||
// different threads lock each other up in different mutexes.
|
||||
Common::Mutex &_mutex;
|
||||
|
||||
protected:
|
||||
void sortPlayList();
|
||||
|
||||
Audio::Mixer *_pMixer;
|
||||
MidiPlayer *_pMidiDrv;
|
||||
|
||||
uint32 _dwTempo;
|
||||
// If true and a sound has a digital track, the sound from the AdLib track is played
|
||||
bool _useDigitalSFX;
|
||||
|
||||
// remapping:
|
||||
void remapChannels(bool mainThread = true);
|
||||
ChannelRemapping *determineChannelMap();
|
||||
void resetDeviceChannel(int devChannel, bool mainThread);
|
||||
|
||||
public:
|
||||
// The parsers need to know this for the dontMap channels...
|
||||
bool isDeviceChannelMapped(int devChannel) const;
|
||||
|
||||
bool isDigitalSamplePlaying() const;
|
||||
|
||||
private:
|
||||
MusicList _playList;
|
||||
bool _soundOn;
|
||||
byte _masterVolume;
|
||||
MusicEntry *_usedChannel[16];
|
||||
int8 _channelRemap[16];
|
||||
int8 _globalReverb;
|
||||
bool _needsRemap;
|
||||
int _globalPause;
|
||||
bool _needsResume;
|
||||
|
||||
DeviceChannelUsage _channelMap[16];
|
||||
|
||||
MidiCommandQueue _queuedCommands;
|
||||
MusicType _musicType;
|
||||
|
||||
int _driverFirstChannel;
|
||||
int _driverLastChannel;
|
||||
|
||||
MusicEntry *_currentlyPlayingSample;
|
||||
|
||||
int _timeCounter; // Used to keep track of the order in which MusicEntries
|
||||
// are added, for priority purposes.
|
||||
};
|
||||
|
||||
} // End of namespace Dgds
|
||||
|
||||
#endif // DGDS_SOUND_MUSIC_H
|
||||
188
engines/dgds/sound/resource/resource_audio.cpp
Normal file
188
engines/dgds/sound/resource/resource_audio.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Resource library
|
||||
|
||||
#include "common/archive.h"
|
||||
#include "common/file.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
#include "dgds/sound/resource/sci_resource.h"
|
||||
#include "dgds/sound/scispan.h"
|
||||
|
||||
namespace Dgds {
|
||||
|
||||
|
||||
SoundResource::SoundResource(uint32 resourceNr, const byte *rawData, int dataSz) :
|
||||
_trackCount(0), _tracks(nullptr), _soundPriority(0xFF) {
|
||||
_resource = new SciResource(rawData, dataSz, resourceNr);
|
||||
|
||||
Channel *channel;
|
||||
|
||||
SciSpan<const byte> data = *_resource;
|
||||
// Count # of tracks
|
||||
_trackCount = 0;
|
||||
while ((*data++) != 0xFF) {
|
||||
_trackCount++;
|
||||
while (*data != 0xFF)
|
||||
data += 6;
|
||||
++data;
|
||||
}
|
||||
_tracks = new Track[_trackCount];
|
||||
data = *_resource;
|
||||
|
||||
for (int trackNr = 0; trackNr < _trackCount; trackNr++) {
|
||||
// Track info starts with track type:BYTE
|
||||
// Then the channel information gets appended Unknown:WORD, ChannelOffset:WORD, ChannelSize:WORD
|
||||
// 0xFF:BYTE as terminator to end that track and begin with another track type
|
||||
// Track type 0xFF is the marker signifying the end of the tracks
|
||||
|
||||
_tracks[trackNr].type = *data++;
|
||||
// Counting # of channels used
|
||||
SciSpan<const byte> data2 = data;
|
||||
byte channelCount = 0;
|
||||
while (*data2 != 0xFF) {
|
||||
data2 += 6;
|
||||
channelCount++;
|
||||
}
|
||||
_tracks[trackNr].channels = new Channel[channelCount];
|
||||
_tracks[trackNr].channelCount = 0;
|
||||
_tracks[trackNr].digitalChannelNr = -1; // No digital sound associated
|
||||
_tracks[trackNr].digitalSampleRate = 0;
|
||||
_tracks[trackNr].digitalSampleSize = 0;
|
||||
_tracks[trackNr].digitalSampleStart = 0;
|
||||
_tracks[trackNr].digitalSampleEnd = 0;
|
||||
if (_tracks[trackNr].type != 0xF0) { // Digital track marker - not supported currently
|
||||
int channelNr = 0;
|
||||
while (channelCount--) {
|
||||
channel = &_tracks[trackNr].channels[channelNr];
|
||||
const uint16 dataOffset = data.getUint16LEAt(2);
|
||||
|
||||
if (dataOffset >= _resource->size()) {
|
||||
warning("Invalid offset inside sound resource %d: track %d, channel %d", resourceNr, trackNr, channelNr);
|
||||
data += 6;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint16 size = data.getUint16LEAt(4);
|
||||
|
||||
if ((uint32)dataOffset + size > _resource->size()) {
|
||||
warning("Invalid size inside sound resource %d: track %d, channel %d", resourceNr, trackNr, channelNr);
|
||||
size = _resource->size() - dataOffset;
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
warning("Empty channel in sound resource %d: track %d, channel %d", resourceNr, trackNr, channelNr);
|
||||
data += 6;
|
||||
continue;
|
||||
}
|
||||
|
||||
channel->data = _resource->subspan(dataOffset, size);
|
||||
|
||||
channel->curPos = 0;
|
||||
channel->number = channel->data[0];
|
||||
|
||||
channel->poly = channel->data[1] & 0x0F;
|
||||
channel->prio = channel->data[1] >> 4;
|
||||
channel->time = channel->prev = 0;
|
||||
channel->data += 2; // skip over header
|
||||
if (channel->number == 0xFE) { // Digital channel
|
||||
_tracks[trackNr].digitalChannelNr = channelNr;
|
||||
_tracks[trackNr].digitalSampleRate = channel->data.getUint16LEAt(0);
|
||||
_tracks[trackNr].digitalSampleSize = channel->data.getUint16LEAt(2);
|
||||
_tracks[trackNr].digitalSampleStart = channel->data.getUint16LEAt(4);
|
||||
_tracks[trackNr].digitalSampleEnd = channel->data.getUint16LEAt(6);
|
||||
channel->data += 8; // Skip over header
|
||||
channel->flags = 0;
|
||||
} else {
|
||||
channel->flags = channel->number >> 4;
|
||||
channel->number = channel->number & 0x0F;
|
||||
// Flag 1: Channel start offset is 0 instead of 10 (currently: everything 0)
|
||||
// Also: Don't map the channel to the device at all, but still play it.
|
||||
// It doesn't stop other sounds playing sounds on that channel, it even
|
||||
// allows other sounds to map to this channel (in that case the dontmap
|
||||
// channel has limited access, it can't send control change, program
|
||||
// change and pitch wheel messages.
|
||||
// This is basically a marker for the channel as a "real" channel
|
||||
// (used mostly for rhythm channels on devices that have one). These
|
||||
// channels will also consequently start the parsing at offset 0 instead
|
||||
// of 10: Normal channels would read the parameters of the first couple of
|
||||
// events into the channel structs, but the "real" channels have to
|
||||
// send these to the device right away, since they don't use the stored
|
||||
// data.
|
||||
// Very early games like KQ5 (but including the DOS CD version) and SQ2
|
||||
// have support for this flag, only. It isn't even a flag there, since
|
||||
// all these games do is check for a channel number below 0x10.
|
||||
//
|
||||
// Flag 2: Don't remap the channel. It is placed in the map, but only in the
|
||||
// exact matching slot of the channel number. All the other games except
|
||||
// the very early ones use this flag to mark the rhythm channels. I
|
||||
// haven't seen any usage of flag 1 in any of these games. They all use
|
||||
// flag 2 instead, but still have full support of flag 1 in the code.
|
||||
// Using this flag is really preferable, since there can't be conflicts
|
||||
// with different sounds playing on the channel.
|
||||
//
|
||||
// Flag 4: Start up muted. The channel won't be mapped (and thus, not have any
|
||||
// output), until the mute gets removed.
|
||||
}
|
||||
_tracks[trackNr].channelCount++;
|
||||
channelNr++;
|
||||
data += 6;
|
||||
}
|
||||
} else {
|
||||
// The first byte of the 0xF0 track's channel list is priority
|
||||
_soundPriority = *data;
|
||||
|
||||
// Skip over digital track
|
||||
data += 6;
|
||||
}
|
||||
++data; // Skipping 0xFF that closes channels list
|
||||
}
|
||||
}
|
||||
|
||||
SoundResource::~SoundResource() {
|
||||
if (_tracks != nullptr) {
|
||||
for (int trackNr = 0; trackNr < _trackCount; trackNr++)
|
||||
delete[] _tracks[trackNr].channels;
|
||||
delete[] _tracks;
|
||||
}
|
||||
|
||||
delete _resource;
|
||||
}
|
||||
|
||||
|
||||
SoundResource::Track *SoundResource::getTrackByType(byte type) {
|
||||
for (int trackNr = 0; trackNr < _trackCount; trackNr++) {
|
||||
if (_tracks[trackNr].type == type)
|
||||
return &_tracks[trackNr];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SoundResource::Track *SoundResource::getDigitalTrack() {
|
||||
for (int trackNr = 0; trackNr < _trackCount; trackNr++) {
|
||||
if (_tracks[trackNr].digitalChannelNr != -1)
|
||||
return &_tracks[trackNr];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // End of namespace Dgds
|
||||
43
engines/dgds/sound/resource/sci_resource.cpp
Normal file
43
engines/dgds/sound/resource/sci_resource.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Resource library
|
||||
|
||||
#include "common/file.h"
|
||||
|
||||
#include "dgds/sound/resource/sci_resource.h"
|
||||
#include "dgds/sound/scispan.h"
|
||||
|
||||
namespace Dgds {
|
||||
|
||||
// A cut-down version of SciResource so we don't have to change
|
||||
// the midi code too much.
|
||||
SciResource::SciResource(const byte *data, int dataSz, int16 id)
|
||||
: SciSpan<const byte>(data, dataSz, Common::String::format("%d", id)) {
|
||||
}
|
||||
|
||||
SciResource::~SciResource() {
|
||||
}
|
||||
|
||||
void SciResource::unalloc() {
|
||||
}
|
||||
|
||||
} // End of namespace Dgds
|
||||
99
engines/dgds/sound/resource/sci_resource.h
Normal file
99
engines/dgds/sound/resource/sci_resource.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/* 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 DGDS_SOUND_RESOURCE_SCI_RESOURCE_H
|
||||
#define DGDS_SOUND_RESOURCE_SCI_RESOURCE_H
|
||||
|
||||
#include "dgds/sound/scispan.h"
|
||||
|
||||
namespace Common {
|
||||
class File;
|
||||
class FSList;
|
||||
class FSNode;
|
||||
class WriteStream;
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Dgds {
|
||||
|
||||
/** Class for storing resources in memory */
|
||||
class SciResource : public SciSpan<const byte> {
|
||||
public:
|
||||
SciResource(const byte *data, int dataSz, int16 id);
|
||||
~SciResource();
|
||||
void unalloc();
|
||||
};
|
||||
|
||||
class SoundResource {
|
||||
public:
|
||||
struct Channel {
|
||||
byte number;
|
||||
byte flags;
|
||||
byte poly;
|
||||
uint16 prio;
|
||||
SciSpan<const byte> data;
|
||||
uint16 curPos;
|
||||
long time;
|
||||
byte prev;
|
||||
|
||||
Channel() :
|
||||
number(0),
|
||||
flags(0),
|
||||
poly(0),
|
||||
prio(0),
|
||||
data(),
|
||||
curPos(0) {
|
||||
time = 0;
|
||||
prev = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct Track {
|
||||
byte type;
|
||||
byte channelCount;
|
||||
SciSpan<const byte> header;
|
||||
Channel *channels;
|
||||
int16 digitalChannelNr;
|
||||
uint16 digitalSampleRate;
|
||||
uint16 digitalSampleSize;
|
||||
uint16 digitalSampleStart;
|
||||
uint16 digitalSampleEnd;
|
||||
};
|
||||
public:
|
||||
SoundResource(uint32 resNumberu, const byte *data, int dataSz);
|
||||
~SoundResource();
|
||||
|
||||
Track *getTrackByType(byte type);
|
||||
Track *getDigitalTrack();
|
||||
|
||||
byte getSoundPriority() const { return _soundPriority; }
|
||||
bool exists() const { return _resource != nullptr; }
|
||||
|
||||
private:
|
||||
int _trackCount;
|
||||
Track *_tracks;
|
||||
SciResource *_resource;
|
||||
byte _soundPriority;
|
||||
};
|
||||
|
||||
} // End of namespace Dgds
|
||||
|
||||
#endif // DGDS_SOUND_RESOURCE_SCI_RESOURCE_H
|
||||
187
engines/dgds/sound/scispan.h
Normal file
187
engines/dgds/sound/scispan.h
Normal file
@@ -0,0 +1,187 @@
|
||||
/* 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 DGDS_UTIL_H
|
||||
#define DGDS_UTIL_H
|
||||
|
||||
#include "common/span.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/file.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/scummsys.h"
|
||||
#include "common/str.h"
|
||||
|
||||
namespace Dgds {
|
||||
|
||||
/** Engine debug levels */
|
||||
enum kSciDebugLevels {
|
||||
kDebugLevelSound = 1,
|
||||
};
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark SciSpanImpl - SciSpanIterator
|
||||
|
||||
namespace SciSpanInternal {
|
||||
template <typename Span, bool IsConst>
|
||||
class SciSpanIterator : public Common::SpanInternal::SpanIterator<Span, IsConst> {
|
||||
typedef typename Common::SpanInternal::SpanIterator<Span, IsConst> super_type;
|
||||
typedef typename Span::value_type span_value_type;
|
||||
typedef typename Common::conditional<IsConst, const Span, Span>::type span_type;
|
||||
|
||||
public:
|
||||
typedef typename Span::difference_type difference_type;
|
||||
typedef typename Common::remove_const<span_value_type>::type value_type;
|
||||
typedef typename Common::conditional<IsConst, const span_value_type, span_value_type>::type *pointer;
|
||||
typedef typename Common::conditional<IsConst, const span_value_type, span_value_type>::type &reference;
|
||||
|
||||
inline SciSpanIterator() : super_type() {}
|
||||
|
||||
inline SciSpanIterator(span_type *span, const difference_type index) : super_type(span, index) {}
|
||||
|
||||
inline SciSpanIterator(const SciSpanIterator &other) : super_type(other) {}
|
||||
|
||||
inline SciSpanIterator &operator=(const SciSpanIterator &other) {
|
||||
super_type::operator=(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline SciSpanIterator operator+(const difference_type delta) const {
|
||||
SciSpanIterator it(*this);
|
||||
it += delta;
|
||||
return it;
|
||||
}
|
||||
|
||||
};
|
||||
} // End of namespace SciSpanInternal
|
||||
|
||||
/**
|
||||
* Bounds-checked random access into a contiguous block of memory, with
|
||||
* extra methods that automatically read and write using the correct endianness
|
||||
* for the currently loaded game.
|
||||
*/
|
||||
template <typename ValueType, template <typename> class Derived>
|
||||
class SciSpanImpl : public Common::NamedSpanImpl<ValueType, Derived> {
|
||||
typedef Common::NamedSpanImpl<ValueType, Derived> super_type;
|
||||
typedef Derived<ValueType> derived_type;
|
||||
|
||||
template <typename T, template <typename> class U> friend class SciSpanImpl;
|
||||
#if defined(CXXTEST_RUNNING) && CXXTEST_RUNNING
|
||||
friend class ::SpanTestSuite;
|
||||
#endif
|
||||
|
||||
public:
|
||||
typedef typename super_type::value_type value_type;
|
||||
typedef typename super_type::difference_type difference_type;
|
||||
typedef typename super_type::index_type index_type;
|
||||
typedef typename super_type::size_type size_type;
|
||||
typedef SciSpanInternal::SciSpanIterator<derived_type, true> const_iterator;
|
||||
typedef SciSpanInternal::SciSpanIterator<derived_type, false> iterator;
|
||||
typedef typename super_type::pointer pointer;
|
||||
typedef typename super_type::const_pointer const_pointer;
|
||||
typedef typename super_type::reference reference;
|
||||
typedef typename super_type::const_reference const_reference;
|
||||
|
||||
inline SciSpanImpl() : super_type() {}
|
||||
|
||||
inline SciSpanImpl(const pointer data_,
|
||||
const size_type size_,
|
||||
const Common::String &name_ = Common::String(),
|
||||
const size_type sourceByteOffset_ = 0) :
|
||||
super_type(data_, size_, name_, sourceByteOffset_) {}
|
||||
|
||||
template <typename Other>
|
||||
inline SciSpanImpl(const Other &other) : super_type(other) {}
|
||||
|
||||
inline const_iterator cbegin() const { return const_iterator(&this->impl(), 0); }
|
||||
inline const_iterator cend() const { return const_iterator(&this->impl(), this->size()); }
|
||||
inline const_iterator begin() const { return const_iterator(&this->impl(), 0); }
|
||||
inline const_iterator end() const { return const_iterator(&this->impl(), this->size()); }
|
||||
inline iterator begin() { return iterator(&this->impl(), 0); }
|
||||
inline iterator end() { return iterator(&this->impl(), this->size()); }
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark SciSpanImpl - ForwardIterator
|
||||
|
||||
// Spans that are used as ForwardIterators must not be allowed inside of
|
||||
// SpanOwner, since this will result in the wrong pointer to memory to be
|
||||
// deleted
|
||||
private:
|
||||
typedef typename Common::remove_const<Derived<ValueType> >::type mutable_derived_type;
|
||||
|
||||
public:
|
||||
|
||||
inline const_reference operator*() const {
|
||||
this->validate(0, sizeof(value_type));
|
||||
return *this->_data;
|
||||
}
|
||||
|
||||
inline reference operator*() {
|
||||
this->validate(0, sizeof(value_type));
|
||||
return *this->_data;
|
||||
}
|
||||
|
||||
inline mutable_derived_type &operator+=(const difference_type delta) {
|
||||
this->validate(0, delta * sizeof(value_type), Common::kValidateSeek);
|
||||
this->_data += delta;
|
||||
this->_size -= delta;
|
||||
return this->impl();
|
||||
}
|
||||
|
||||
inline mutable_derived_type &operator++() {
|
||||
return operator+=(1);
|
||||
}
|
||||
|
||||
inline mutable_derived_type operator++(int) {
|
||||
mutable_derived_type span(this->impl());
|
||||
operator+=(1);
|
||||
return span;
|
||||
}
|
||||
|
||||
inline mutable_derived_type operator+(const difference_type delta) const {
|
||||
mutable_derived_type span(this->impl());
|
||||
span += delta;
|
||||
return span;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ValueType>
|
||||
class SciSpan : public SciSpanImpl<ValueType, SciSpan> {
|
||||
typedef SciSpanImpl<ValueType, ::Dgds::SciSpan> super_type;
|
||||
|
||||
public:
|
||||
COMMON_SPAN_TYPEDEFS;
|
||||
|
||||
inline SciSpan() : super_type() {}
|
||||
|
||||
inline SciSpan(const pointer data_,
|
||||
const size_type size_,
|
||||
const Common::String &name_ = Common::String(),
|
||||
const size_type sourceByteOffset_ = 0) :
|
||||
super_type(data_, size_, name_, sourceByteOffset_) {}
|
||||
|
||||
template <typename Other>
|
||||
inline SciSpan(const Other &other) : super_type(other) {}
|
||||
};
|
||||
|
||||
} // End of namespace Dgds
|
||||
|
||||
#endif // DGDS_UTIL_H
|
||||
Reference in New Issue
Block a user