Initial commit

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

View File

@@ -0,0 +1,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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,667 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sci/sci.h"
#include "common/file.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "audio/softsynth/fmtowns_pc98/towns_audio.h"
#include "sci/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

View File

@@ -0,0 +1,223 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef 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

View File

@@ -0,0 +1,272 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "common/frac.h"
#include "common/mutex.h"
#include "common/system.h"
#ifndef 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

316
engines/dgds/sound/music.h Normal file
View 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

View 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

View 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

View 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

View 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