aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/sound/drivers/adlib.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci/sound/drivers/adlib.cpp')
-rw-r--r--engines/sci/sound/drivers/adlib.cpp841
1 files changed, 841 insertions, 0 deletions
diff --git a/engines/sci/sound/drivers/adlib.cpp b/engines/sci/sound/drivers/adlib.cpp
new file mode 100644
index 0000000000..76e615dce0
--- /dev/null
+++ b/engines/sci/sound/drivers/adlib.cpp
@@ -0,0 +1,841 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "sci/sci.h"
+
+#include "sound/fmopl.h"
+#include "sound/softsynth/emumidi.h"
+
+#include "sci/resource.h"
+#include "sci/sound/drivers/mididriver.h"
+
+namespace Sci {
+
+#ifdef __DC__
+#define STEREO false
+#else
+#define STEREO true
+#endif
+
+// FIXME: We don't seem to be sending the polyphony init data, so disable this for now
+#define ADLIB_DISABLE_VOICE_MAPPING
+
+class MidiDriver_AdLib : public MidiDriver_Emulated {
+public:
+ enum {
+ kVoices = 9,
+ kRhythmKeys = 62
+ };
+
+ MidiDriver_AdLib(Audio::Mixer *mixer) : MidiDriver_Emulated(mixer), _playSwitch(true), _masterVolume(15), _rhythmKeyMap(0), _opl(0) { }
+ virtual ~MidiDriver_AdLib() { }
+
+ // MidiDriver
+ int open(bool isSCI0);
+ void close();
+ void send(uint32 b);
+ MidiChannel *allocateChannel() { return NULL; }
+ MidiChannel *getPercussionChannel() { return NULL; }
+
+ // AudioStream
+ bool isStereo() const { return _stereo; }
+ int getRate() const { return _mixer->getOutputRate(); }
+
+ // MidiDriver_Emulated
+ void generateSamples(int16 *buf, int len);
+
+ void setVolume(byte volume);
+ void playSwitch(bool play);
+ bool loadResource(const byte *data, uint size);
+ virtual uint32 property(int prop, uint32 param);
+
+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)
+
+ Channel() : patch(0), volume(63), pan(64), holdPedal(0), extraVoices(0),
+ pitchWheel(8192), lastVoice(0), enableVelocity(false) { }
+ };
+
+ struct AdLibVoice {
+ int8 channel; // MIDI channel that this voice is assigned 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), note(-1), patch(-1), velocity(0), isSustained(false), age(0) { }
+ };
+
+ bool _stereo;
+ bool _isSCI0;
+ OPL::OPL *_opl;
+ bool _playSwitch;
+ int _masterVolume;
+ Channel _channels[MIDI_CHANNELS];
+ AdLibVoice _voices[kVoices];
+ byte *_rhythmKeyMap;
+ Common::Array<AdLibPatch> _patches;
+
+ void loadInstrument(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);
+ void voiceMapping(int channel, int voices);
+ void assignVoices(int channel, int voices);
+ void releaseVoices(int channel, int voices);
+ void donateVoices();
+ int findVoiceBasic(int channel);
+ void setVelocityReg(int regOffset, int velocity, int kbScaleLevel, int pan);
+ int calcVelocity(int voice, int op);
+};
+
+class MidiPlayer_AdLib : public MidiPlayer {
+public:
+ MidiPlayer_AdLib() { _driver = new MidiDriver_AdLib(g_system->getMixer()); }
+ ~MidiPlayer_AdLib() {
+ delete _driver;
+ _driver = 0;
+ }
+
+ int open(ResourceManager *resMan);
+ void close();
+
+ byte getPlayId(SciVersion soundVersion);
+ int getPolyphony() const { return MidiDriver_AdLib::kVoices; }
+ bool hasRhythmChannel() const { return false; }
+ void setVolume(byte volume) { static_cast<MidiDriver_AdLib *>(_driver)->setVolume(volume); }
+ void playSwitch(bool play) { static_cast<MidiDriver_AdLib *>(_driver)->playSwitch(play); }
+ void loadInstrument(int idx, byte *data);
+};
+
+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
+};
+
+static const int ym3812_note[13] = {
+ 0x157, 0x16b, 0x181, 0x198, 0x1b0, 0x1ca,
+ 0x1e5, 0x202, 0x220, 0x241, 0x263, 0x287,
+ 0x2ae
+};
+
+int MidiDriver_AdLib::open(bool isSCI0) {
+ int rate = _mixer->getOutputRate();
+
+ _stereo = STEREO;
+
+ debug(3, "ADLIB: Starting driver in %s mode", (isSCI0 ? "SCI0" : "SCI1"));
+ _isSCI0 = isSCI0;
+
+ _opl = OPL::Config::create(isStereo() ? OPL::Config::kDualOpl2 : OPL::Config::kOpl2);
+
+ // Try falling back to mono, thus plain OPL2 emualtor, when no Dual OPL2 is available.
+ if (!_opl && _stereo) {
+ _stereo = false;
+ _opl = OPL::Config::create(OPL::Config::kOpl2);
+ }
+
+ if (!_opl)
+ return -1;
+
+ _opl->init(rate);
+
+ setRegister(0xBD, 0);
+ setRegister(0x08, 0);
+ setRegister(0x01, 0x20);
+
+ MidiDriver_Emulated::open();
+
+ _mixer->playInputStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO);
+
+ return 0;
+}
+
+void MidiDriver_AdLib::close() {
+ _mixer->stopHandle(_mixerSoundHandle);
+
+ delete _opl;
+ delete[] _rhythmKeyMap;
+}
+
+void MidiDriver_AdLib::setVolume(byte volume) {
+ _masterVolume = volume;
+ renewNotes(-1, true);
+}
+
+// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
+void MidiDriver_AdLib::send(uint32 b) {
+ byte command = b & 0xf0;
+ byte channel = b & 0xf;
+ byte op1 = (b >> 8) & 0xff;
+ byte op2 = (b >> 16) & 0xff;
+
+ switch (command) {
+ case 0x80:
+ noteOff(channel, op1);
+ break;
+ case 0x90:
+ noteOn(channel, op1, op2);
+ break;
+ case 0xe0:
+ _channels[channel].pitchWheel = (op1 & 0x7f) | ((op2 & 0x7f) << 7);
+ renewNotes(channel, true);
+ 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 0xf0: // SysEx, ignore it
+ break;
+ default:
+ warning("ADLIB: Unknown event %02x", command);
+ }
+}
+
+void MidiDriver_AdLib::generateSamples(int16 *data, int len) {
+ if (isStereo())
+ len <<= 1;
+ _opl->readBuffer(data, len);
+
+ // 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 byte *ins) {
+ AdLibPatch patch;
+
+ // Set data for the operators
+ for (int i = 0; i < 2; i++) {
+ const byte *op = ins + i * 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 < kVoices; i++)
+ if (_voices[i].channel == 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 < kVoices; i++)
+ if (_voices[i].channel == -1) {
+ _voices[i].channel = channel;
+ if (--voices == 0)
+ return;
+ }
+
+ _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 < kVoices; i++) {
+ if ((_voices[i].channel == channel) && (_voices[i].note == -1)) {
+ _voices[i].channel = -1;
+ if (--voices == 0)
+ return;
+ }
+ }
+
+ for (int i = 0; i < kVoices; i++) {
+ if (_voices[i].channel == channel) {
+ voiceOff(i);
+ _voices[i].channel = -1;
+ if (--voices == 0)
+ return;
+ }
+ }
+}
+
+void MidiDriver_AdLib::donateVoices() {
+ int freeVoices = 0;
+
+ for (int i = 0; i < kVoices; i++)
+ if (_voices[i].channel == -1)
+ freeVoices++;
+
+ if (freeVoices == 0)
+ return;
+
+ for (int i = 0; i < MIDI_CHANNELS; i++) {
+ if (_channels[i].extraVoices >= freeVoices) {
+ assignVoices(i, freeVoices);
+ _channels[i].extraVoices -= freeVoices;
+ return;
+ } else if (_channels[i].extraVoices > 0) {
+ assignVoices(i, _channels[i].extraVoices);
+ freeVoices -= _channels[i].extraVoices;
+ _channels[i].extraVoices = 0;
+ }
+ }
+}
+
+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;
+ }
+ }
+
+#ifdef ADLIB_DISABLE_VOICE_MAPPING
+ int voice = findVoiceBasic(channel);
+#else
+ int voice = findVoice(channel);
+#endif
+
+ if (voice == -1) {
+ debug(3, "ADLIB: failed to find free voice assigned to channel %i", channel);
+ return;
+ }
+
+ voiceOn(voice, note, velocity);
+}
+
+// FIXME: Temporary, see comment at top of file regarding ADLIB_DISABLE_VOICE_MAPPING
+int MidiDriver_AdLib::findVoiceBasic(int channel) {
+ int voice = -1;
+ int oldestVoice = -1;
+ int oldestAge = -1;
+
+ // 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].note == -1) {
+ voice = v;
+ break;
+ }
+
+ // We also keep track of the oldest note in case the search fails
+ if (_voices[v].age > oldestAge) {
+ oldestAge = _voices[v].age;
+ oldestVoice = v;
+ }
+ }
+
+ if (voice == -1) {
+ if (oldestVoice != -1) {
+ voiceOff(oldestVoice);
+ voice = oldestVoice;
+ } else {
+ return -1;
+ }
+ }
+
+ _voices[voice].channel = channel;
+ _channels[channel].lastVoice = voice;
+ return voice;
+}
+
+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].channel == channel) {
+ if (_voices[v].note == -1) {
+ voice = v;
+ break;
+ }
+
+ // We also keep track of the oldest note in case the search fails
+ // Notes started in the current time slice will not be selected
+ if (_voices[v].age > oldestAge) {
+ oldestAge = _voices[v].age;
+ oldestVoice = v;
+ }
+ }
+ }
+
+ if (voice == -1) {
+ if (oldestVoice != -1) {
+ voiceOff(oldestVoice);
+ voice = oldestVoice;
+ } else {
+ return -1;
+ }
+ }
+
+ _channels[channel].lastVoice = voice;
+ return 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;
+
+ _voices[voice].age = 0;
+
+ if ((channel == 9) && _rhythmKeyMap) {
+ patch = CLIP(note, 27, 88) + 101;
+ } else {
+ patch = _channels[channel].patch;
+ }
+
+ // 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) {
+ _voices[voice].isSustained = false;
+ setNote(voice, _voices[voice].note, 0);
+ _voices[voice].note = -1;
+ _voices[voice].age = 0;
+}
+
+void MidiDriver_AdLib::setNote(int voice, int note, bool key) {
+ int channel = _voices[voice].channel;
+ int n, fre, oct;
+ float delta;
+ int bend = _channels[channel].pitchWheel;
+
+ if ((channel == 9) && _rhythmKeyMap) {
+ note = _rhythmKeyMap[CLIP(note, 27, 88) - 27];
+ }
+
+ _voices[voice].note = note;
+
+ delta = 0;
+
+ n = note % 12;
+
+ if (bend < 8192)
+ bend = 8192 - bend;
+ delta = (float)pow(2.0, (bend % 8192) / 8192.0);
+
+ if (bend > 8192)
+ fre = (int)(ym3812_note[n] * delta);
+ else
+ fre = (int)(ym3812_note[n] / delta);
+
+ oct = note / 12 - 1;
+
+ if (oct < 0)
+ oct = 0;
+
+ if (oct > 7)
+ oct = 7;
+
+ setRegister(0xA0 + voice, fre & 0xff);
+ setRegister(0xB0 + voice, (key << 5) | (oct << 2) | (fre >> 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 (isStereo()) {
+ 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);
+ 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 (isStereo()) {
+ 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 byte *data, uint size) {
+ if ((size != 1344) && (size != 2690) && (size != 5382)) {
+ warning("ADLIB: Unsupported patch format (%i bytes)", size);
+ return false;
+ }
+
+ for (int i = 0; i < 48; i++)
+ loadInstrument(data + (28 * i));
+
+ if (size == 2690) {
+ for (int i = 48; i < 96; i++)
+ loadInstrument(data + 2 + (28 * i));
+ } else if (size == 5382) {
+ for (int i = 48; i < 190; i++)
+ loadInstrument(data + (28 * i));
+ _rhythmKeyMap = new byte[kRhythmKeys];
+ memcpy(_rhythmKeyMap, data + 5320, kRhythmKeys);
+ }
+
+ return true;
+}
+
+uint32 MidiDriver_AdLib::property(int prop, uint32 param) {
+ switch(prop) {
+ case MIDI_PROP_MASTER_VOLUME:
+ if (param != 0xffff)
+ _masterVolume = param;
+ return _masterVolume;
+ default:
+ break;
+ }
+ return 0;
+}
+
+
+int MidiPlayer_AdLib::open(ResourceManager *resMan) {
+ assert(resMan != NULL);
+
+ // Load up the patch.003 file, parse out the instruments
+ Resource *res = resMan->findResource(ResourceId(kResourceTypePatch, 3), 0);
+ bool ok = false;
+
+ if (res) {
+ ok = static_cast<MidiDriver_AdLib *>(_driver)->loadResource(res->data, res->size);
+ } 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;
+
+ if ((size == 5684) || (size == 5720) || (size == 5727)) {
+ byte *buf = new byte[patchSize];
+
+ if (f.seek(0x45a) && (f.read(buf, patchSize) == patchSize))
+ ok = static_cast<MidiDriver_AdLib *>(_driver)->loadResource(buf, patchSize);
+
+ delete[] buf;
+ }
+ }
+ }
+
+ if (!ok) {
+ warning("ADLIB: Failed to load patch.003");
+ return -1;
+ }
+
+ return static_cast<MidiDriver_AdLib *>(_driver)->open(getSciVersion() <= SCI_VERSION_0_LATE);
+}
+
+void MidiPlayer_AdLib::close() {
+ if (_driver) {
+ _driver->close();
+ }
+}
+
+byte MidiPlayer_AdLib::getPlayId(SciVersion soundVersion) {
+ switch (soundVersion) {
+ case SCI_VERSION_0_EARLY:
+ return 0x01;
+ case SCI_VERSION_0_LATE:
+ return 0x04;
+ default:
+ return 0x00;
+ }
+}
+
+MidiPlayer *MidiPlayer_AdLib_create() {
+ return new MidiPlayer_AdLib();
+}
+
+} // End of namespace Sci