diff options
author | athrxx | 2019-04-14 21:50:26 +0200 |
---|---|---|
committer | athrxx | 2019-04-15 21:51:47 +0200 |
commit | 9144b8894e53aa12ecd4f313d52aaeed604a40b2 (patch) | |
tree | 68550eff40b5db17a3ea14df4aadd4562e9eb473 /engines/kyra/sound/drivers | |
parent | 9d527463343c627eac305d8b17a273c0583ffa63 (diff) | |
download | scummvm-rg350-9144b8894e53aa12ecd4f313d52aaeed604a40b2.tar.gz scummvm-rg350-9144b8894e53aa12ecd4f313d52aaeed604a40b2.tar.bz2 scummvm-rg350-9144b8894e53aa12ecd4f313d52aaeed604a40b2.zip |
KYRA: sound files/classes reorganization step #4
Separate drivers from their wrapper classes and move them into their own files
Diffstat (limited to 'engines/kyra/sound/drivers')
-rw-r--r-- | engines/kyra/sound/drivers/adlib.cpp | 1951 | ||||
-rw-r--r-- | engines/kyra/sound/drivers/adlib.h | 393 | ||||
-rw-r--r-- | engines/kyra/sound/drivers/audstream.cpp | 320 | ||||
-rw-r--r-- | engines/kyra/sound/drivers/midi.cpp | 355 | ||||
-rw-r--r-- | engines/kyra/sound/drivers/midi.h | 108 | ||||
-rw-r--r-- | engines/kyra/sound/drivers/pcspeaker.cpp | 366 |
6 files changed, 3493 insertions, 0 deletions
diff --git a/engines/kyra/sound/drivers/adlib.cpp b/engines/kyra/sound/drivers/adlib.cpp new file mode 100644 index 0000000000..de6b8a461a --- /dev/null +++ b/engines/kyra/sound/drivers/adlib.cpp @@ -0,0 +1,1951 @@ +/* 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. + * + * LGPL License + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + + +#include "kyra/sound/drivers/adlib.h" +#include "audio/fmopl.h" + + +#define CALLBACKS_PER_SECOND 72 + +namespace Kyra { + +AdLibDriver::AdLibDriver(Audio::Mixer *mixer, int version) { + setupParserOpcodeTable(); + + _version = version; + _numPrograms = (_version == 1) ? 150 : ((_version == 4) ? 500 : 250); + + _mixer = mixer; + + _adlib = OPL::Config::create(); + if (!_adlib || !_adlib->init()) + error("Failed to create OPL"); + + memset(_channels, 0, sizeof(_channels)); + _soundData = 0; + _soundDataSize = 0; + + _vibratoAndAMDepthBits = _curRegOffset = 0; + + _curChannel = _rhythmSectionBits = 0; + _rnd = 0x1234; + + _tempo = 0; + _soundTrigger = 0; + _programStartTimeout = 0; + + _callbackTimer = 0xFF; + _unkValue1 = _unkValue2 = _unkValue4 = _unkValue5 = 0; + _unkValue6 = _unkValue7 = _unkValue8 = _unkValue9 = _unkValue10 = 0; + _unkValue11 = _unkValue12 = _unkValue13 = _unkValue14 = _unkValue15 = + _unkValue16 = _unkValue17 = _unkValue18 = _unkValue19 = _unkValue20 = 0; + + _tablePtr1 = _tablePtr2 = 0; + + _syncJumpMask = 0; + + _musicVolume = 0; + _sfxVolume = 0; + + _sfxPointer = 0; + + _programQueueStart = _programQueueEnd = 0; + _retrySounds = false; + + _adlib->start(new Common::Functor0Mem<void, AdLibDriver>(this, &AdLibDriver::callback), CALLBACKS_PER_SECOND); +} + +AdLibDriver::~AdLibDriver() { + delete _adlib; + _adlib = 0; +} + +void AdLibDriver::setMusicVolume(uint8 volume) { + Common::StackLock lock(_mutex); + + _musicVolume = volume; + + for (uint i = 0; i < 6; ++i) { + Channel &chan = _channels[i]; + chan.volumeModifier = volume; + + const uint8 regOffset = _regOffset[i]; + + // Level Key Scaling / Total Level + writeOPL(0x40 + regOffset, calculateOpLevel1(chan)); + writeOPL(0x43 + regOffset, calculateOpLevel2(chan)); + } + + // For now we use the music volume for both sfx and music in Kyra1 and EoB + if (_version < 4) { + _sfxVolume = volume; + + for (uint i = 6; i < 9; ++i) { + Channel &chan = _channels[i]; + chan.volumeModifier = volume; + + const uint8 regOffset = _regOffset[i]; + + // Level Key Scaling / Total Level + writeOPL(0x40 + regOffset, calculateOpLevel1(chan)); + writeOPL(0x43 + regOffset, calculateOpLevel2(chan)); + } + } +} + +void AdLibDriver::setSfxVolume(uint8 volume) { + // We only support sfx volume in version 4 games. + if (_version < 4) + return; + + Common::StackLock lock(_mutex); + + _sfxVolume = volume; + + for (uint i = 6; i < 9; ++i) { + Channel &chan = _channels[i]; + chan.volumeModifier = volume; + + const uint8 regOffset = _regOffset[i]; + + // Level Key Scaling / Total Level + writeOPL(0x40 + regOffset, calculateOpLevel1(chan)); + writeOPL(0x43 + regOffset, calculateOpLevel2(chan)); + } +} + +void AdLibDriver::initDriver() { + Common::StackLock lock(_mutex); + resetAdLibState(); +} + +void AdLibDriver::setSoundData(uint8 *data, uint32 size) { + Common::StackLock lock(_mutex); + + // Drop all tracks that are still queued. These would point to the old + // sound data. + _programQueueStart = _programQueueEnd = 0; + memset(_programQueue, 0, sizeof(_programQueue)); + + if (_soundData) { + delete[] _soundData; + _soundData = _sfxPointer = 0; + } + + _soundData = data; + _soundDataSize = size; +} + +void AdLibDriver::queueTrack(int track, int volume) { + Common::StackLock lock(_mutex); + + uint8 *trackData = getProgram(track); + if (!trackData) + return; + + // Don't drop tracks in EoB. The queue is always full there if a couple of monsters are around. + // If we drop the incoming tracks we get no sound effects, but tons of warnings instead. + if (_version >= 3 && _programQueueEnd == _programQueueStart && _programQueue[_programQueueEnd].data != 0) { + warning("AdLibDriver: Program queue full, dropping track %d", track); + return; + } + + _programQueue[_programQueueEnd] = QueueEntry(trackData, track, volume); + _programQueueEnd = (_programQueueEnd + 1) & 15; +} + +bool AdLibDriver::isChannelPlaying(int channel) const { + Common::StackLock lock(_mutex); + + assert(channel >= 0 && channel <= 9); + return (_channels[channel].dataptr != 0); +} + +void AdLibDriver::stopAllChannels() { + Common::StackLock lock(_mutex); + + for (int channel = 0; channel <= 9; ++channel) { + _curChannel = channel; + + Channel &chan = _channels[_curChannel]; + chan.priority = 0; + chan.dataptr = 0; + + if (channel != 9) + noteOff(chan); + } + _retrySounds = false; +} + +// timer callback + +void AdLibDriver::callback() { + Common::StackLock lock(_mutex); + if (_programStartTimeout) + --_programStartTimeout; + else + setupPrograms(); + executePrograms(); + + uint8 temp = _callbackTimer; + _callbackTimer += _tempo; + if (_callbackTimer < temp) { + if (!(--_unkValue2)) { + _unkValue2 = _unkValue1; + ++_unkValue4; + } + } +} + +void AdLibDriver::setupPrograms() { + // If there is no program queued, we skip this. + if (_programQueueStart == _programQueueEnd) + return; + + uint8 *ptr = _programQueue[_programQueueStart].data; + + // The AdLib driver (in its old versions used for EOB) is not suitable for modern (fast) CPUs. + // The stop sound track (track 0 which has a priority of 50) will often still be busy when the + // next sound (with a lower priority) starts which will cause that sound to be skipped. We simply + // restart incoming sounds during stop sound execution. + // UPDATE: This stilly applies after introduction of the _programQueue. + QueueEntry retrySound; + if (_version < 3 && _programQueue[_programQueueStart].id == 0) + _retrySounds = true; + else if (_retrySounds) + retrySound = _programQueue[_programQueueStart]; + + // Adjust data in case we hit a sound effect. + adjustSfxData(ptr, _programQueue[_programQueueStart].volume); + + // Clear the queue entry + _programQueue[_programQueueStart].data = 0; + _programQueueStart = (_programQueueStart + 1) & 15; + + const int chan = *ptr++; + const int priority = *ptr++; + + // Only start this sound if its priority is higher than the one + // already playing. + + Channel &channel = _channels[chan]; + + if (priority >= channel.priority) { + initChannel(channel); + channel.priority = priority; + channel.dataptr = ptr; + channel.tempo = 0xFF; + channel.position = 0xFF; + channel.duration = 1; + + if (chan <= 5) + channel.volumeModifier = _musicVolume; + else + channel.volumeModifier = _sfxVolume; + + unkOutput2(chan); + + // We need to wait two callback calls till we can start another track. + // This is (probably) required to assure that the sfx are started with + // the correct priority and velocity. + _programStartTimeout = 2; + + retrySound = QueueEntry(); + } + + if (retrySound.data) { + debugC(9, kDebugLevelSound, "AdLibDriver::setupPrograms(): WORKAROUND - Restarting skipped sound %d)", retrySound.id); + queueTrack(retrySound.id, retrySound.volume); + } +} + +void AdLibDriver::adjustSfxData(uint8 *ptr, int volume) { + // Check whether we need to reset the data of an old sfx which has been + // started. + if (_sfxPointer) { + _sfxPointer[1] = _sfxPriority; + _sfxPointer[3] = _sfxVelocity; + _sfxPointer = 0; + } + + // Only music tracks are started on channel 9, thus we need to make sure + // we do not have a music track here. + if (*ptr == 9) + return; + + // Store the pointer so we can reset the data when a new program is started. + _sfxPointer = ptr; + + // Store the old values. + _sfxPriority = ptr[1]; + _sfxVelocity = ptr[3]; + + // Adjust the values. + if (volume != 0xFF) { + if (_version >= 3) { + int newVal = ((((ptr[3]) + 63) * volume) >> 8) & 0xFF; + ptr[3] = -newVal + 63; + ptr[1] = ((ptr[1] * volume) >> 8) & 0xFF; + } else { + int newVal = ((_sfxVelocity << 2) ^ 0xFF) * volume; + ptr[3] = (newVal >> 10) ^ 0x3F; + ptr[1] = newVal >> 11; + } + } +} + +// A few words on opcode parsing and timing: +// +// First of all, We simulate a timer callback 72 times per second. Each timeout +// we update each channel that has something to play. +// +// Each channel has its own individual tempo, which is added to its position. +// This will frequently cause the position to "wrap around" but that is +// intentional. In fact, it's the signal to go ahead and do more stuff with +// that channel. +// +// Each channel also has a duration, indicating how much time is left on the +// its current task. This duration is decreased by one. As long as it still has +// not reached zero, the only thing that can happen is that the note is turned +// off depending on manual or automatic note spacing. Once the duration reaches +// zero, a new set of musical opcodes are executed. +// +// An opcode is one byte, followed by a variable number of parameters. Since +// most opcodes have at least one one-byte parameter, we read that as well. Any +// opcode that doesn't have that one parameter is responsible for moving the +// data pointer back again. +// +// If the most significant bit of the opcode is 1, it's a function; call it. +// The opcode functions return either 0 (continue), 1 (stop) or 2 (stop, and do +// not run the effects callbacks). +// +// If the most significant bit of the opcode is 0, it's a note, and the first +// parameter is its duration. (There are cases where the duration is modified +// but that's an exception.) The note opcode is assumed to return 1, and is the +// last opcode unless its duration is zero. +// +// Finally, most of the times that the callback is called, it will invoke the +// effects callbacks. The final opcode in a set can prevent this, if it's a +// function and it returns anything other than 1. + +void AdLibDriver::executePrograms() { + // Each channel runs its own program. There are ten channels: One for + // each AdLib channel (0-8), plus one "control channel" (9) which is + // the one that tells the other channels what to do. + + // This is where we ensure that channels that are made to jump "in + // sync" do so. + + if (_syncJumpMask) { + bool forceUnlock = true; + + for (_curChannel = 9; _curChannel >= 0; --_curChannel) { + if ((_syncJumpMask & (1 << _curChannel)) == 0) + continue; + + if (_channels[_curChannel].dataptr && !_channels[_curChannel].lock) + forceUnlock = false; + } + + if (forceUnlock) { + for (_curChannel = 9; _curChannel >= 0; --_curChannel) + if (_syncJumpMask & (1 << _curChannel)) + _channels[_curChannel].lock = false; + } + } + + for (_curChannel = 9; _curChannel >= 0; --_curChannel) { + int result = 1; + + if (!_channels[_curChannel].dataptr) + continue; + + if (_channels[_curChannel].lock && (_syncJumpMask & (1 << _curChannel))) + continue; + + Channel &channel = _channels[_curChannel]; + if (_curChannel == 9) + _curRegOffset = 0; + else + _curRegOffset = _regOffset[_curChannel]; + + if (channel.tempoReset) + channel.tempo = _tempo; + + uint8 backup = channel.position; + channel.position += channel.tempo; + if (channel.position < backup) { + if (--channel.duration) { + if (channel.duration == channel.spacing2) + noteOff(channel); + if (channel.duration == channel.spacing1 && _curChannel != 9) + noteOff(channel); + } else { + // An opcode is not allowed to modify its own + // data pointer except through the 'dataptr' + // parameter. To enforce that, we have to work + // on a copy of the data pointer. + // + // This fixes a subtle music bug where the + // wrong music would play when getting the + // quill in Kyra 1. + const uint8 *dataptr = channel.dataptr; + while (dataptr) { + uint8 opcode = *dataptr++; + uint8 param = *dataptr++; + + if (opcode & 0x80) { + opcode &= 0x7F; + if (opcode >= _parserOpcodeTableSize) + opcode = _parserOpcodeTableSize - 1; + debugC(9, kDebugLevelSound, "Calling opcode '%s' (%d) (channel: %d)", _parserOpcodeTable[opcode].name, opcode, _curChannel); + result = (this->*(_parserOpcodeTable[opcode].function))(dataptr, channel, param); + channel.dataptr = dataptr; + if (result) + break; + } else { + debugC(9, kDebugLevelSound, "Note on opcode 0x%02X (duration: %d) (channel: %d)", opcode, param, _curChannel); + setupNote(opcode, channel); + noteOn(channel); + setupDuration(param, channel); + if (param) { + // We need to make sure we are always running the + // effects after this. Otherwise some sounds are + // wrong. Like the sfx when bumping into a wall in + // LoL. + result = 1; + channel.dataptr = dataptr; + break; + } + } + } + } + } + + if (result == 1) { + if (channel.primaryEffect) + (this->*(channel.primaryEffect))(channel); + if (channel.secondaryEffect) + (this->*(channel.secondaryEffect))(channel); + } + } +} + +// + +void AdLibDriver::resetAdLibState() { + debugC(9, kDebugLevelSound, "resetAdLibState()"); + _rnd = 0x1234; + + // Authorize the control of the waveforms + writeOPL(0x01, 0x20); + + // Select FM music mode + writeOPL(0x08, 0x00); + + // I would guess the main purpose of this is to turn off the rhythm, + // thus allowing us to use 9 melodic voices instead of 6. + writeOPL(0xBD, 0x00); + + int loop = 10; + while (loop--) { + if (loop != 9) { + // Silence the channel + writeOPL(0x40 + _regOffset[loop], 0x3F); + writeOPL(0x43 + _regOffset[loop], 0x3F); + } + initChannel(_channels[loop]); + } +} + +// Old calling style: output0x388(0xABCD) +// New calling style: writeOPL(0xAB, 0xCD) + +void AdLibDriver::writeOPL(byte reg, byte val) { + _adlib->writeReg(reg, val); +} + +void AdLibDriver::initChannel(Channel &channel) { + debugC(9, kDebugLevelSound, "initChannel(%lu)", (long)(&channel - _channels)); + memset(&channel.dataptr, 0, sizeof(Channel) - ((char *)&channel.dataptr - (char *)&channel)); + + channel.tempo = 0xFF; + channel.priority = 0; + // normally here are nullfuncs but we set 0 for now + channel.primaryEffect = 0; + channel.secondaryEffect = 0; + channel.spacing1 = 1; + channel.lock = false; +} + +void AdLibDriver::noteOff(Channel &channel) { + debugC(9, kDebugLevelSound, "noteOff(%lu)", (long)(&channel - _channels)); + + // The control channel has no corresponding AdLib channel + + if (_curChannel >= 9) + return; + + // When the rhythm section is enabled, channels 6, 7 and 8 are special. + + if (_rhythmSectionBits && _curChannel >= 6) + return; + + // This means the "Key On" bit will always be 0 + channel.regBx &= 0xDF; + + // Octave / F-Number / Key-On + writeOPL(0xB0 + _curChannel, channel.regBx); +} + +void AdLibDriver::unkOutput2(uint8 chan) { + debugC(9, kDebugLevelSound, "unkOutput2(%d)", chan); + + // The control channel has no corresponding AdLib channel + + if (chan >= 9) + return; + + // I believe this has to do with channels 6, 7, and 8 being special + // when AdLib's rhythm section is enabled. + + if (_rhythmSectionBits && chan >= 6) + return; + + uint8 offset = _regOffset[chan]; + + // The channel is cleared: First the attack/delay rate, then the + // sustain level/release rate, and finally the note is turned off. + + writeOPL(0x60 + offset, 0xFF); + writeOPL(0x63 + offset, 0xFF); + + writeOPL(0x80 + offset, 0xFF); + writeOPL(0x83 + offset, 0xFF); + + writeOPL(0xB0 + chan, 0x00); + + // ...and then the note is turned on again, with whatever value is + // still lurking in the A0 + chan register, but everything else - + // including the two most significant frequency bit, and the octave - + // set to zero. + // + // This is very strange behavior, and causes problems with the ancient + // FMOPL code we borrowed from AdPlug. I've added a workaround. See + // audio/softsynth/opl/mame.cpp for more details. + // + // Fortunately, the more modern DOSBox FMOPL code does not seem to have + // any trouble with this. + + writeOPL(0xB0 + chan, 0x20); +} + +// I believe this is a random number generator. It actually does seem to +// generate an even distribution of almost all numbers from 0 through 65535, +// though in my tests some numbers were never generated. + +uint16 AdLibDriver::getRandomNr() { + _rnd += 0x9248; + uint16 lowBits = _rnd & 7; + _rnd >>= 3; + _rnd |= (lowBits << 13); + return _rnd; +} + +void AdLibDriver::setupDuration(uint8 duration, Channel &channel) { + debugC(9, kDebugLevelSound, "setupDuration(%d, %lu)", duration, (long)(&channel - _channels)); + if (channel.durationRandomness) { + channel.duration = duration + (getRandomNr() & channel.durationRandomness); + return; + } + if (channel.fractionalSpacing) + channel.spacing2 = (duration >> 3) * channel.fractionalSpacing; + channel.duration = duration; +} + +// This function may or may not play the note. It's usually followed by a call +// to noteOn(), which will always play the current note. + +void AdLibDriver::setupNote(uint8 rawNote, Channel &channel, bool flag) { + debugC(9, kDebugLevelSound, "setupNote(%d, %lu)", rawNote, (long)(&channel - _channels)); + + if (_curChannel >= 9) + return; + + channel.rawNote = rawNote; + + int8 note = (rawNote & 0x0F) + channel.baseNote; + int8 octave = ((rawNote + channel.baseOctave) >> 4) & 0x0F; + + // There are only twelve notes. If we go outside that, we have to + // adjust the note and octave. + + if (note >= 12) { + note -= 12; + octave++; + } else if (note < 0) { + note += 12; + octave--; + } + + // The calculation of frequency looks quite different from the original + // disassembly at a first glance, but when you consider that the + // largest possible value would be 0x0246 + 0xFF + 0x47 (and that's if + // baseFreq is unsigned), freq is still a 10-bit value, just as it + // should be to fit in the Ax and Bx registers. + // + // If it were larger than that, it could have overflowed into the + // octave bits, and that could possibly have been used in some sound. + // But as it is now, I can't see any way it would happen. + + uint16 freq = _freqTable[note] + channel.baseFreq; + + // When called from callback 41, the behavior is slightly different: + // We adjust the frequency, even when channel.pitchBend is 0. + + if (channel.pitchBend || flag) { + const uint8 *table; + + if (channel.pitchBend >= 0) { + table = _pitchBendTables[(channel.rawNote & 0x0F) + 2]; + freq += table[channel.pitchBend]; + } else { + table = _pitchBendTables[channel.rawNote & 0x0F]; + freq -= table[-channel.pitchBend]; + } + } + + channel.regAx = freq & 0xFF; + channel.regBx = (channel.regBx & 0x20) | (octave << 2) | ((freq >> 8) & 0x03); + + // Keep the note on or off + writeOPL(0xA0 + _curChannel, channel.regAx); + writeOPL(0xB0 + _curChannel, channel.regBx); +} + +void AdLibDriver::setupInstrument(uint8 regOffset, const uint8 *dataptr, Channel &channel) { + debugC(9, kDebugLevelSound, "setupInstrument(%d, %p, %lu)", regOffset, (const void *)dataptr, (long)(&channel - _channels)); + + if (_curChannel >= 9) + return; + + // Amplitude Modulation / Vibrato / Envelope Generator Type / + // Keyboard Scaling Rate / Modulator Frequency Multiple + writeOPL(0x20 + regOffset, *dataptr++); + writeOPL(0x23 + regOffset, *dataptr++); + + uint8 temp = *dataptr++; + + // Feedback / Algorithm + + // It is very likely that _curChannel really does refer to the same + // channel as regOffset, but there's only one Cx register per channel. + + writeOPL(0xC0 + _curChannel, temp); + + // The algorithm bit. I don't pretend to understand this fully, but + // "If set to 0, operator 1 modulates operator 2. In this case, + // operator 2 is the only one producing sound. If set to 1, both + // operators produce sound directly. Complex sounds are more easily + // created if the algorithm is set to 0." + + channel.twoChan = temp & 1; + + // Waveform Select + writeOPL(0xE0 + regOffset, *dataptr++); + writeOPL(0xE3 + regOffset, *dataptr++); + + channel.opLevel1 = *dataptr++; + channel.opLevel2 = *dataptr++; + + // Level Key Scaling / Total Level + writeOPL(0x40 + regOffset, calculateOpLevel1(channel)); + writeOPL(0x43 + regOffset, calculateOpLevel2(channel)); + + // Attack Rate / Decay Rate + writeOPL(0x60 + regOffset, *dataptr++); + writeOPL(0x63 + regOffset, *dataptr++); + + // Sustain Level / Release Rate + writeOPL(0x80 + regOffset, *dataptr++); + writeOPL(0x83 + regOffset, *dataptr++); +} + +// Apart from playing the note, this function also updates the variables for +// primary effect 2. + +void AdLibDriver::noteOn(Channel &channel) { + debugC(9, kDebugLevelSound, "noteOn(%lu)", (long)(&channel - _channels)); + + // The "note on" bit is set, and the current note is played. + + if (_curChannel >= 9) + return; + + channel.regBx |= 0x20; + writeOPL(0xB0 + _curChannel, channel.regBx); + + int8 shift = 9 - channel.unk33; + uint16 temp = channel.regAx | (channel.regBx << 8); + channel.unk37 = ((temp & 0x3FF) >> shift) & 0xFF; + channel.unk38 = channel.unk36; +} + +void AdLibDriver::adjustVolume(Channel &channel) { + debugC(9, kDebugLevelSound, "adjustVolume(%lu)", (long)(&channel - _channels)); + + if (_curChannel >= 9) + return; + + // Level Key Scaling / Total Level + + writeOPL(0x43 + _regOffset[_curChannel], calculateOpLevel2(channel)); + if (channel.twoChan) + writeOPL(0x40 + _regOffset[_curChannel], calculateOpLevel1(channel)); +} + +// This is presumably only used for some sound effects, e.g. Malcolm blowing up +// the trees in the intro (but not the effect where he "booby-traps" the big +// tree) and turning Kallak to stone. Related functions and variables: +// +// update_setupPrimaryEffect1() +// - Initializes unk29, unk30 and unk31 +// - unk29 is not further modified +// - unk30 is not further modified, except by update_removePrimaryEffect1() +// +// update_removePrimaryEffect1() +// - Deinitializes unk30 +// +// unk29 - determines how often the notes are played +// unk30 - modifies the frequency +// unk31 - determines how often the notes are played + +void AdLibDriver::primaryEffect1(Channel &channel) { + debugC(9, kDebugLevelSound, "Calling primaryEffect1 (channel: %d)", _curChannel); + + if (_curChannel >= 9) + return; + + uint8 temp = channel.unk31; + channel.unk31 += channel.unk29; + if (channel.unk31 >= temp) + return; + + // Initialize unk1 to the current frequency + int16 unk1 = ((channel.regBx & 3) << 8) | channel.regAx; + + // This is presumably to shift the "note on" bit so far to the left + // that it won't be affected by any of the calculations below. + int16 unk2 = ((channel.regBx & 0x20) << 8) | (channel.regBx & 0x1C); + + int16 unk3 = (int16)channel.unk30; + + if (unk3 >= 0) { + unk1 += unk3; + if (unk1 >= 734) { + // The new frequency is too high. Shift it down and go + // up one octave. + unk1 >>= 1; + if (!(unk1 & 0x3FF)) + ++unk1; + unk2 = (unk2 & 0xFF00) | ((unk2 + 4) & 0xFF); + unk2 &= 0xFF1C; + } + } else { + unk1 += unk3; + if (unk1 < 388) { + // The new frequency is too low. Shift it up and go + // down one octave. + unk1 <<= 1; + if (!(unk1 & 0x3FF)) + --unk1; + unk2 = (unk2 & 0xFF00) | ((unk2 - 4) & 0xFF); + unk2 &= 0xFF1C; + } + } + + // Make sure that the new frequency is still a 10-bit value. + unk1 &= 0x3FF; + + writeOPL(0xA0 + _curChannel, unk1 & 0xFF); + channel.regAx = unk1 & 0xFF; + + // Shift down the "note on" bit again. + uint8 value = unk1 >> 8; + value |= (unk2 >> 8) & 0xFF; + value |= unk2 & 0xFF; + + writeOPL(0xB0 + _curChannel, value); + channel.regBx = value; +} + +// This is presumably only used for some sound effects, e.g. Malcolm entering +// and leaving Kallak's hut. Related functions and variables: +// +// update_setupPrimaryEffect2() +// - Initializes unk32, unk33, unk34, unk35 and unk36 +// - unk32 is not further modified +// - unk33 is not further modified +// - unk34 is a countdown that gets reinitialized to unk35 on zero +// - unk35 is based on unk34 and not further modified +// - unk36 is not further modified +// +// noteOn() +// - Plays the current note +// - Updates unk37 with a new (lower?) frequency +// - Copies unk36 to unk38. The unk38 variable is a countdown. +// +// unk32 - determines how often the notes are played +// unk33 - modifies the frequency +// unk34 - countdown, updates frequency on zero +// unk35 - initializer for unk34 countdown +// unk36 - initializer for unk38 countdown +// unk37 - frequency +// unk38 - countdown, begins playing on zero +// unk41 - determines how often the notes are played +// +// Note that unk41 is never initialized. Not that it should matter much, but it +// is a bit sloppy. + +void AdLibDriver::primaryEffect2(Channel &channel) { + debugC(9, kDebugLevelSound, "Calling primaryEffect2 (channel: %d)", _curChannel); + + if (_curChannel >= 9) + return; + + if (channel.unk38) { + --channel.unk38; + return; + } + + uint8 temp = channel.unk41; + channel.unk41 += channel.unk32; + if (channel.unk41 < temp) { + uint16 unk1 = channel.unk37; + if (!(--channel.unk34)) { + unk1 ^= 0xFFFF; + ++unk1; + channel.unk37 = unk1; + channel.unk34 = channel.unk35; + } + + uint16 unk2 = (channel.regAx | (channel.regBx << 8)) & 0x3FF; + unk2 += unk1; + + channel.regAx = unk2 & 0xFF; + channel.regBx = (channel.regBx & 0xFC) | (unk2 >> 8); + + // Octave / F-Number / Key-On + writeOPL(0xA0 + _curChannel, channel.regAx); + writeOPL(0xB0 + _curChannel, channel.regBx); + } +} + +// I don't know where this is used. The same operation is performed several +// times on the current channel, using a chunk of the _soundData[] buffer for +// parameters. The parameters are used starting at the end of the chunk. +// +// Since we use _curRegOffset to specify the final register, it's quite +// unlikely that this function is ever used to play notes. It's probably only +// used to modify the sound. Another thing that supports this idea is that it +// can be combined with any of the effects callbacks above. +// +// Related functions and variables: +// +// update_setupSecondaryEffect1() +// - Initialies unk18, unk19, unk20, unk21, unk22 and offset +// - unk19 is not further modified +// - unk20 is not further modified +// - unk22 is not further modified +// - offset is not further modified +// +// unk18 - determines how often the operation is performed +// unk19 - determines how often the operation is performed +// unk20 - the start index into the data chunk +// unk21 - the current index into the data chunk +// unk22 - the operation to perform +// offset - the offset to the data chunk + +void AdLibDriver::secondaryEffect1(Channel &channel) { + debugC(9, kDebugLevelSound, "Calling secondaryEffect1 (channel: %d)", _curChannel); + + if (_curChannel >= 9) + return; + + uint8 temp = channel.unk18; + channel.unk18 += channel.unk19; + if (channel.unk18 < temp) { + if (--channel.unk21 < 0) + channel.unk21 = channel.unk20; + writeOPL(channel.unk22 + _curRegOffset, _soundData[channel.offset + channel.unk21]); + } +} + +uint8 AdLibDriver::calculateOpLevel1(Channel &channel) { + int8 value = channel.opLevel1 & 0x3F; + + if (channel.twoChan) { + value += channel.opExtraLevel1; + value += channel.opExtraLevel2; + + uint16 level3 = (channel.opExtraLevel3 ^ 0x3F) * channel.volumeModifier; + if (level3) { + level3 += 0x3F; + level3 >>= 8; + } + + value += level3 ^ 0x3F; + } + + value = CLIP<int8>(value, 0, 0x3F); + + if (!channel.volumeModifier) + value = 0x3F; + + // Preserve the scaling level bits from opLevel1 + + return checkValue(value) | (channel.opLevel1 & 0xC0); +} + +uint8 AdLibDriver::calculateOpLevel2(Channel &channel) { + int8 value = channel.opLevel2 & 0x3F; + + value += channel.opExtraLevel1; + value += channel.opExtraLevel2; + + uint16 level3 = (channel.opExtraLevel3 ^ 0x3F) * channel.volumeModifier; + if (level3) { + level3 += 0x3F; + level3 >>= 8; + } + + value += level3 ^ 0x3F; + + value = CLIP<int8>(value, 0, 0x3F); + + if (!channel.volumeModifier) + value = 0x3F; + + // Preserve the scaling level bits from opLevel2 + + return checkValue(value) | (channel.opLevel2 & 0xC0); +} + +// parser opcodes + +int AdLibDriver::update_setRepeat(const uint8 *&dataptr, Channel &channel, uint8 value) { + channel.repeatCounter = value; + return 0; +} + +int AdLibDriver::update_checkRepeat(const uint8 *&dataptr, Channel &channel, uint8 value) { + ++dataptr; + if (--channel.repeatCounter) { + int16 add = READ_LE_UINT16(dataptr - 2); + dataptr += add; + } + return 0; +} + +int AdLibDriver::update_setupProgram(const uint8 *&dataptr, Channel &channel, uint8 value) { + if (value == 0xFF) + return 0; + + const uint8 *ptr = getProgram(value); + + // In case we encounter an invalid program we simply ignore it and do + // nothing instead. The original did not care about invalid programs and + // simply tried to play them anyway... But to avoid crashes due we ingore + // them. + // This, for example, happens in the Lands of Lore intro when Scotia gets + // the ring in the intro. + if (!ptr) { + debugC(3, kDebugLevelSound, "AdLibDriver::update_setupProgram: Invalid program %d specified", value); + return 0; + } + + uint8 chan = *ptr++; + uint8 priority = *ptr++; + + Channel &channel2 = _channels[chan]; + + if (priority >= channel2.priority) { + // We keep new tracks from being started for two further iterations of + // the callback. This assures the correct velocity is used for this + // program. + _programStartTimeout = 2; + initChannel(channel2); + channel2.priority = priority; + channel2.dataptr = ptr; + channel2.tempo = 0xFF; + channel2.position = 0xFF; + channel2.duration = 1; + + if (chan <= 5) + channel2.volumeModifier = _musicVolume; + else + channel2.volumeModifier = _sfxVolume; + + unkOutput2(chan); + } + + return 0; +} + +int AdLibDriver::update_setNoteSpacing(const uint8 *&dataptr, Channel &channel, uint8 value) { + channel.spacing1 = value; + return 0; +} + +int AdLibDriver::update_jump(const uint8 *&dataptr, Channel &channel, uint8 value) { + --dataptr; + int16 add = READ_LE_UINT16(dataptr); dataptr += 2; + if (_version == 1) + dataptr = _soundData + add - 191; + else + dataptr += add; + if (_syncJumpMask & (1 << (&channel - _channels))) + channel.lock = true; + return 0; +} + +int AdLibDriver::update_jumpToSubroutine(const uint8 *&dataptr, Channel &channel, uint8 value) { + --dataptr; + int16 add = READ_LE_UINT16(dataptr); dataptr += 2; + channel.dataptrStack[channel.dataptrStackPos++] = dataptr; + if (_version < 3) + dataptr = _soundData + add - 191; + else + dataptr += add; + return 0; +} + +int AdLibDriver::update_returnFromSubroutine(const uint8 *&dataptr, Channel &channel, uint8 value) { + dataptr = channel.dataptrStack[--channel.dataptrStackPos]; + return 0; +} + +int AdLibDriver::update_setBaseOctave(const uint8 *&dataptr, Channel &channel, uint8 value) { + channel.baseOctave = value; + return 0; +} + +int AdLibDriver::update_stopChannel(const uint8 *&dataptr, Channel &channel, uint8 value) { + channel.priority = 0; + if (_curChannel != 9) + noteOff(channel); + dataptr = 0; + return 2; +} + +int AdLibDriver::update_playRest(const uint8 *&dataptr, Channel &channel, uint8 value) { + setupDuration(value, channel); + noteOff(channel); + return (value != 0); +} + +int AdLibDriver::update_writeAdLib(const uint8 *&dataptr, Channel &channel, uint8 value) { + writeOPL(value, *dataptr++); + return 0; +} + +int AdLibDriver::update_setupNoteAndDuration(const uint8 *&dataptr, Channel &channel, uint8 value) { + setupNote(value, channel); + value = *dataptr++; + setupDuration(value, channel); + return (value != 0); +} + +int AdLibDriver::update_setBaseNote(const uint8 *&dataptr, Channel &channel, uint8 value) { + channel.baseNote = value; + return 0; +} + +int AdLibDriver::update_setupSecondaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value) { + channel.unk18 = value; + channel.unk19 = value; + channel.unk20 = channel.unk21 = *dataptr++; + channel.unk22 = *dataptr++; + // WORKAROUND: The original code reads a true offset which later gets translated via xlat (in + // the current segment). This means that the outcome depends on the sound data offset. + // Unfortunately this offset is different in most implementations of the audio driver and + // probably also different from the offset assumed by the sequencer. + // It seems that the driver assumes an offset of 191 which is wrong for all the game driver + // implementations. + // This bug has probably not been noticed, since the effect is hardly used and the sounds are + // not necessarily worse. I noticed the difference between ScummVM and DOSBox for the EOB II + // teleporter sound. I also found the location of the table which is supposed to be used here + // (simple enough: it is located at the end of the track after the 0x88 ending opcode). + // Teleporters in EOB I and II now sound exactly the same which I am sure was the intended way, + // since the sound data is exactly the same. + // In DOSBox the teleporters will sound different in EOB I and II, due to different sound + // data offsets. + channel.offset = READ_LE_UINT16(dataptr) - 191; dataptr += 2; + channel.secondaryEffect = &AdLibDriver::secondaryEffect1; + return 0; +} + +int AdLibDriver::update_stopOtherChannel(const uint8 *&dataptr, Channel &channel, uint8 value) { + Channel &channel2 = _channels[value]; + channel2.duration = 0; + channel2.priority = 0; + channel2.dataptr = 0; + return 0; +} + +int AdLibDriver::update_waitForEndOfProgram(const uint8 *&dataptr, Channel &channel, uint8 value) { + const uint8 *ptr = getProgram(value); + + // Safety check in case an invalid program is specified. This would make + // getProgram return a nullptr and thus cause invalid memory reads. + if (!ptr) { + debugC(3, kDebugLevelSound, "AdLibDriver::update_waitForEndOfProgram: Invalid program %d specified", value); + return 0; + } + + uint8 chan = *ptr; + + if (!_channels[chan].dataptr) + return 0; + + dataptr -= 2; + return 2; +} + +int AdLibDriver::update_setupInstrument(const uint8 *&dataptr, Channel &channel, uint8 value) { + const uint8 *instrument = getInstrument(value); + + // We add a safety check to avoid setting up invalid instruments. This is + // not done in the original. However, to avoid crashes due to invalid + // memory reads we simply ignore the request. + // This happens, for example, in Hand of Fate when using the swampsnake + // potion on Zanthia to scare off the rat in the cave in the first chapter + // of the game. + if (!instrument) { + debugC(3, kDebugLevelSound, "AdLibDriver::update_setupInstrument: Invalid instrument %d specified", value); + return 0; + } + + setupInstrument(_curRegOffset, instrument, channel); + return 0; +} + +int AdLibDriver::update_setupPrimaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value) { + channel.unk29 = value; + channel.unk30 = READ_BE_UINT16(dataptr); + dataptr += 2; + channel.primaryEffect = &AdLibDriver::primaryEffect1; + channel.unk31 = 0xFF; + return 0; +} + +int AdLibDriver::update_removePrimaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value) { + --dataptr; + channel.primaryEffect = 0; + channel.unk30 = 0; + return 0; +} + +int AdLibDriver::update_setBaseFreq(const uint8 *&dataptr, Channel &channel, uint8 value) { + channel.baseFreq = value; + return 0; +} + +int AdLibDriver::update_setupPrimaryEffect2(const uint8 *&dataptr, Channel &channel, uint8 value) { + channel.unk32 = value; + channel.unk33 = *dataptr++; + uint8 temp = *dataptr++; + channel.unk34 = temp + 1; + channel.unk35 = temp << 1; + channel.unk36 = *dataptr++; + channel.primaryEffect = &AdLibDriver::primaryEffect2; + return 0; +} + +int AdLibDriver::update_setPriority(const uint8 *&dataptr, Channel &channel, uint8 value) { + channel.priority = value; + return 0; +} + +int AdLibDriver::updateCallback23(const uint8 *&dataptr, Channel &channel, uint8 value) { + value >>= 1; + _unkValue1 = _unkValue2 = value; + _callbackTimer = 0xFF; + _unkValue4 = _unkValue5 = 0; + return 0; +} + +int AdLibDriver::updateCallback24(const uint8 *&dataptr, Channel &channel, uint8 value) { + if (_unkValue5) { + if (_unkValue4 & value) { + _unkValue5 = 0; + return 0; + } + } + + if (!(value & _unkValue4)) + ++_unkValue5; + + dataptr -= 2; + channel.duration = 1; + return 2; +} + +int AdLibDriver::update_setExtraLevel1(const uint8 *&dataptr, Channel &channel, uint8 value) { + channel.opExtraLevel1 = value; + adjustVolume(channel); + return 0; +} + +int AdLibDriver::update_setupDuration(const uint8 *&dataptr, Channel &channel, uint8 value) { + setupDuration(value, channel); + return (value != 0); +} + +int AdLibDriver::update_playNote(const uint8 *&dataptr, Channel &channel, uint8 value) { + setupDuration(value, channel); + noteOn(channel); + return (value != 0); +} + +int AdLibDriver::update_setFractionalNoteSpacing(const uint8 *&dataptr, Channel &channel, uint8 value) { + channel.fractionalSpacing = value & 7; + return 0; +} + +int AdLibDriver::update_setTempo(const uint8 *&dataptr, Channel &channel, uint8 value) { + _tempo = value; + return 0; +} + +int AdLibDriver::update_removeSecondaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value) { + --dataptr; + channel.secondaryEffect = 0; + return 0; +} + +int AdLibDriver::update_setChannelTempo(const uint8 *&dataptr, Channel &channel, uint8 value) { + channel.tempo = value; + return 0; +} + +int AdLibDriver::update_setExtraLevel3(const uint8 *&dataptr, Channel &channel, uint8 value) { + channel.opExtraLevel3 = value; + return 0; +} + +int AdLibDriver::update_setExtraLevel2(const uint8 *&dataptr, Channel &channel, uint8 value) { + int channelBackUp = _curChannel; + + _curChannel = value; + Channel &channel2 = _channels[value]; + channel2.opExtraLevel2 = *dataptr++; + adjustVolume(channel2); + + _curChannel = channelBackUp; + return 0; +} + +int AdLibDriver::update_changeExtraLevel2(const uint8 *&dataptr, Channel &channel, uint8 value) { + int channelBackUp = _curChannel; + + _curChannel = value; + Channel &channel2 = _channels[value]; + channel2.opExtraLevel2 += *dataptr++; + adjustVolume(channel2); + + _curChannel = channelBackUp; + return 0; +} + +// Apart from initializing to zero, these two functions are the only ones that +// modify _vibratoAndAMDepthBits. + +int AdLibDriver::update_setAMDepth(const uint8 *&dataptr, Channel &channel, uint8 value) { + if (value & 1) + _vibratoAndAMDepthBits |= 0x80; + else + _vibratoAndAMDepthBits &= 0x7F; + + writeOPL(0xBD, _vibratoAndAMDepthBits); + return 0; +} + +int AdLibDriver::update_setVibratoDepth(const uint8 *&dataptr, Channel &channel, uint8 value) { + if (value & 1) + _vibratoAndAMDepthBits |= 0x40; + else + _vibratoAndAMDepthBits &= 0xBF; + + writeOPL(0xBD, _vibratoAndAMDepthBits); + return 0; +} + +int AdLibDriver::update_changeExtraLevel1(const uint8 *&dataptr, Channel &channel, uint8 value) { + channel.opExtraLevel1 += value; + adjustVolume(channel); + return 0; +} + +int AdLibDriver::updateCallback38(const uint8 *&dataptr, Channel &channel, uint8 value) { + int channelBackUp = _curChannel; + + _curChannel = value; + Channel &channel2 = _channels[value]; + channel2.duration = channel2.priority = 0; + channel2.dataptr = 0; + channel2.opExtraLevel2 = 0; + + if (value != 9) { + uint8 outValue = _regOffset[value]; + + // Feedback strength / Connection type + writeOPL(0xC0 + _curChannel, 0x00); + + // Key scaling level / Operator output level + writeOPL(0x43 + outValue, 0x3F); + + // Sustain Level / Release Rate + writeOPL(0x83 + outValue, 0xFF); + + // Key On / Octave / Frequency + writeOPL(0xB0 + _curChannel, 0x00); + } + + _curChannel = channelBackUp; + return 0; +} + +int AdLibDriver::updateCallback39(const uint8 *&dataptr, Channel &channel, uint8 value) { + if (_curChannel >= 9) + return 0; + + uint16 unk = *dataptr++; + unk |= value << 8; + unk &= getRandomNr(); + + uint16 unk2 = ((channel.regBx & 0x1F) << 8) | channel.regAx; + unk2 += unk; + unk2 |= ((channel.regBx & 0x20) << 8); + + // Frequency + writeOPL(0xA0 + _curChannel, unk2 & 0xFF); + + // Key On / Octave / Frequency + writeOPL(0xB0 + _curChannel, (unk2 & 0xFF00) >> 8); + + return 0; +} + +int AdLibDriver::update_removePrimaryEffect2(const uint8 *&dataptr, Channel &channel, uint8 value) { + --dataptr; + channel.primaryEffect = 0; + return 0; +} + +int AdLibDriver::update_pitchBend(const uint8 *&dataptr, Channel &channel, uint8 value) { + channel.pitchBend = value; + setupNote(channel.rawNote, channel, true); + return 0; +} + +int AdLibDriver::update_resetToGlobalTempo(const uint8 *&dataptr, Channel &channel, uint8 value) { + --dataptr; + channel.tempo = _tempo; + return 0; +} + +int AdLibDriver::update_nop(const uint8 *&dataptr, Channel &channel, uint8 value) { + --dataptr; + return 0; +} + +int AdLibDriver::update_setDurationRandomness(const uint8 *&dataptr, Channel &channel, uint8 value) { + channel.durationRandomness = value; + return 0; +} + +int AdLibDriver::update_changeChannelTempo(const uint8 *&dataptr, Channel &channel, uint8 value) { + int tempo = channel.tempo + (int8)value; + + if (tempo <= 0) + tempo = 1; + else if (tempo > 255) + tempo = 255; + + channel.tempo = tempo; + return 0; +} + +int AdLibDriver::updateCallback46(const uint8 *&dataptr, Channel &channel, uint8 value) { + uint8 entry = *dataptr++; + _tablePtr1 = _unkTable2[entry++]; + _tablePtr2 = _unkTable2[entry]; + if (value == 2) { + // Frequency + writeOPL(0xA0, _tablePtr2[0]); + } + return 0; +} + +int AdLibDriver::update_setupRhythmSection(const uint8 *&dataptr, Channel &channel, uint8 value) { + int channelBackUp = _curChannel; + int regOffsetBackUp = _curRegOffset; + + _curChannel = 6; + _curRegOffset = _regOffset[6]; + + const uint8 *instrument; + instrument = getInstrument(value); + if (instrument) { + setupInstrument(_curRegOffset, instrument, channel); + } else { + debugC(3, kDebugLevelSound, "AdLibDriver::update_setupRhythmSection: Invalid instrument %d for channel 6 specified", value); + } + _unkValue6 = channel.opLevel2; + + _curChannel = 7; + _curRegOffset = _regOffset[7]; + + instrument = getInstrument(*dataptr++); + if (instrument) { + setupInstrument(_curRegOffset, instrument, channel); + } else { + debugC(3, kDebugLevelSound, "AdLibDriver::update_setupRhythmSection: Invalid instrument %d for channel 7 specified", value); + } + _unkValue7 = channel.opLevel1; + _unkValue8 = channel.opLevel2; + + _curChannel = 8; + _curRegOffset = _regOffset[8]; + + instrument = getInstrument(*dataptr++); + if (instrument) { + setupInstrument(_curRegOffset, instrument, channel); + } else { + debugC(3, kDebugLevelSound, "AdLibDriver::update_setupRhythmSection: Invalid instrument %d for channel 8 specified", value); + } + _unkValue9 = channel.opLevel1; + _unkValue10 = channel.opLevel2; + + // Octave / F-Number / Key-On for channels 6, 7 and 8 + + _channels[6].regBx = *dataptr++ & 0x2F; + writeOPL(0xB6, _channels[6].regBx); + writeOPL(0xA6, *dataptr++); + + _channels[7].regBx = *dataptr++ & 0x2F; + writeOPL(0xB7, _channels[7].regBx); + writeOPL(0xA7, *dataptr++); + + _channels[8].regBx = *dataptr++ & 0x2F; + writeOPL(0xB8, _channels[8].regBx); + writeOPL(0xA8, *dataptr++); + + _rhythmSectionBits = 0x20; + + _curRegOffset = regOffsetBackUp; + _curChannel = channelBackUp; + return 0; +} + +int AdLibDriver::update_playRhythmSection(const uint8 *&dataptr, Channel &channel, uint8 value) { + // Any instrument that we want to play, and which was already playing, + // is temporarily keyed off. Instruments that were off already, or + // which we don't want to play, retain their old on/off status. This is + // probably so that the instrument's envelope is played from its + // beginning again... + + writeOPL(0xBD, (_rhythmSectionBits & ~(value & 0x1F)) | 0x20); + + // ...but since we only set the rhythm instrument bits, and never clear + // them (until the entire rhythm section is disabled), I'm not sure how + // useful the cleverness above is. We could perhaps simply turn off all + // the rhythm instruments instead. + + _rhythmSectionBits |= value; + + writeOPL(0xBD, _vibratoAndAMDepthBits | 0x20 | _rhythmSectionBits); + return 0; +} + +int AdLibDriver::update_removeRhythmSection(const uint8 *&dataptr, Channel &channel, uint8 value) { + --dataptr; + _rhythmSectionBits = 0; + + // All the rhythm bits are cleared. The AM and Vibrato depth bits + // remain unchanged. + + writeOPL(0xBD, _vibratoAndAMDepthBits); + return 0; +} + +int AdLibDriver::updateCallback51(const uint8 *&dataptr, Channel &channel, uint8 value) { + uint8 value2 = *dataptr++; + + if (value & 1) { + _unkValue12 = value2; + + // Channel 7, op1: Level Key Scaling / Total Level + writeOPL(0x51, checkValue(value2 + _unkValue7 + _unkValue11 + _unkValue12)); + } + + if (value & 2) { + _unkValue14 = value2; + + // Channel 8, op2: Level Key Scaling / Total Level + writeOPL(0x55, checkValue(value2 + _unkValue10 + _unkValue13 + _unkValue14)); + } + + if (value & 4) { + _unkValue15 = value2; + + // Channel 8, op1: Level Key Scaling / Total Level + writeOPL(0x52, checkValue(value2 + _unkValue9 + _unkValue16 + _unkValue15)); + } + + if (value & 8) { + _unkValue18 = value2; + + // Channel 7, op2: Level Key Scaling / Total Level + writeOPL(0x54, checkValue(value2 + _unkValue8 + _unkValue17 + _unkValue18)); + } + + if (value & 16) { + _unkValue20 = value2; + + // Channel 6, op2: Level Key Scaling / Total Level + writeOPL(0x53, checkValue(value2 + _unkValue6 + _unkValue19 + _unkValue20)); + } + + return 0; +} + +int AdLibDriver::updateCallback52(const uint8 *&dataptr, Channel &channel, uint8 value) { + uint8 value2 = *dataptr++; + + if (value & 1) { + _unkValue11 = checkValue(value2 + _unkValue7 + _unkValue11 + _unkValue12); + + // Channel 7, op1: Level Key Scaling / Total Level + writeOPL(0x51, _unkValue11); + } + + if (value & 2) { + _unkValue13 = checkValue(value2 + _unkValue10 + _unkValue13 + _unkValue14); + + // Channel 8, op2: Level Key Scaling / Total Level + writeOPL(0x55, _unkValue13); + } + + if (value & 4) { + _unkValue16 = checkValue(value2 + _unkValue9 + _unkValue16 + _unkValue15); + + // Channel 8, op1: Level Key Scaling / Total Level + writeOPL(0x52, _unkValue16); + } + + if (value & 8) { + _unkValue17 = checkValue(value2 + _unkValue8 + _unkValue17 + _unkValue18); + + // Channel 7, op2: Level Key Scaling / Total Level + writeOPL(0x54, _unkValue17); + } + + if (value & 16) { + _unkValue19 = checkValue(value2 + _unkValue6 + _unkValue19 + _unkValue20); + + // Channel 6, op2: Level Key Scaling / Total Level + writeOPL(0x53, _unkValue19); + } + + return 0; +} + +int AdLibDriver::updateCallback53(const uint8 *&dataptr, Channel &channel, uint8 value) { + uint8 value2 = *dataptr++; + + if (value & 1) { + _unkValue11 = value2; + + // Channel 7, op1: Level Key Scaling / Total Level + writeOPL(0x51, checkValue(value2 + _unkValue7 + _unkValue12)); + } + + if (value & 2) { + _unkValue13 = value2; + + // Channel 8, op2: Level Key Scaling / Total Level + writeOPL(0x55, checkValue(value2 + _unkValue10 + _unkValue14)); + } + + if (value & 4) { + _unkValue16 = value2; + + // Channel 8, op1: Level Key Scaling / Total Level + writeOPL(0x52, checkValue(value2 + _unkValue9 + _unkValue15)); + } + + if (value & 8) { + _unkValue17 = value2; + + // Channel 7, op2: Level Key Scaling / Total Level + writeOPL(0x54, checkValue(value2 + _unkValue8 + _unkValue18)); + } + + if (value & 16) { + _unkValue19 = value2; + + // Channel 6, op2: Level Key Scaling / Total Level + writeOPL(0x53, checkValue(value2 + _unkValue6 + _unkValue20)); + } + + return 0; +} + +int AdLibDriver::update_setSoundTrigger(const uint8 *&dataptr, Channel &channel, uint8 value) { + _soundTrigger = value; + return 0; +} + +int AdLibDriver::update_setTempoReset(const uint8 *&dataptr, Channel &channel, uint8 value) { + channel.tempoReset = value; + return 0; +} + +int AdLibDriver::updateCallback56(const uint8 *&dataptr, Channel &channel, uint8 value) { + channel.unk39 = value; + channel.unk40 = *dataptr++; + return 0; +} + +// static res + +#define COMMAND(x) { &AdLibDriver::x, #x } + +void AdLibDriver::setupParserOpcodeTable() { + static const ParserOpcode parserOpcodeTable[] = { + // 0 + COMMAND(update_setRepeat), + COMMAND(update_checkRepeat), + COMMAND(update_setupProgram), + COMMAND(update_setNoteSpacing), + + // 4 + COMMAND(update_jump), + COMMAND(update_jumpToSubroutine), + COMMAND(update_returnFromSubroutine), + COMMAND(update_setBaseOctave), + + // 8 + COMMAND(update_stopChannel), + COMMAND(update_playRest), + COMMAND(update_writeAdLib), + COMMAND(update_setupNoteAndDuration), + + // 12 + COMMAND(update_setBaseNote), + COMMAND(update_setupSecondaryEffect1), + COMMAND(update_stopOtherChannel), + COMMAND(update_waitForEndOfProgram), + + // 16 + COMMAND(update_setupInstrument), + COMMAND(update_setupPrimaryEffect1), + COMMAND(update_removePrimaryEffect1), + COMMAND(update_setBaseFreq), + + // 20 + COMMAND(update_stopChannel), + COMMAND(update_setupPrimaryEffect2), + COMMAND(update_stopChannel), + COMMAND(update_stopChannel), + + // 24 + COMMAND(update_stopChannel), + COMMAND(update_stopChannel), + COMMAND(update_setPriority), + COMMAND(update_stopChannel), + + // 28 + COMMAND(updateCallback23), + COMMAND(updateCallback24), + COMMAND(update_setExtraLevel1), + COMMAND(update_stopChannel), + + // 32 + COMMAND(update_setupDuration), + COMMAND(update_playNote), + COMMAND(update_stopChannel), + COMMAND(update_stopChannel), + + // 36 + COMMAND(update_setFractionalNoteSpacing), + COMMAND(update_stopChannel), + COMMAND(update_setTempo), + COMMAND(update_removeSecondaryEffect1), + + // 40 + COMMAND(update_stopChannel), + COMMAND(update_setChannelTempo), + COMMAND(update_stopChannel), + COMMAND(update_setExtraLevel3), + + // 44 + COMMAND(update_setExtraLevel2), + COMMAND(update_changeExtraLevel2), + COMMAND(update_setAMDepth), + COMMAND(update_setVibratoDepth), + + // 48 + COMMAND(update_changeExtraLevel1), + COMMAND(update_stopChannel), + COMMAND(update_stopChannel), + COMMAND(updateCallback38), + + // 52 + COMMAND(update_stopChannel), + COMMAND(updateCallback39), + COMMAND(update_removePrimaryEffect2), + COMMAND(update_stopChannel), + + // 56 + COMMAND(update_stopChannel), + COMMAND(update_pitchBend), + COMMAND(update_resetToGlobalTempo), + COMMAND(update_nop), + + // 60 + COMMAND(update_setDurationRandomness), + COMMAND(update_changeChannelTempo), + COMMAND(update_stopChannel), + COMMAND(updateCallback46), + + // 64 + COMMAND(update_nop), + COMMAND(update_setupRhythmSection), + COMMAND(update_playRhythmSection), + COMMAND(update_removeRhythmSection), + + // 68 + COMMAND(updateCallback51), + COMMAND(updateCallback52), + COMMAND(updateCallback53), + COMMAND(update_setSoundTrigger), + + // 72 + COMMAND(update_setTempoReset), + COMMAND(updateCallback56), + COMMAND(update_stopChannel) + }; + + _parserOpcodeTable = parserOpcodeTable; + _parserOpcodeTableSize = ARRAYSIZE(parserOpcodeTable); +} +#undef COMMAND + +// This table holds the register offset for operator 1 for each of the nine +// channels. To get the register offset for operator 2, simply add 3. + +const uint8 AdLibDriver::_regOffset[] = { + 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, + 0x12 +}; + +//These are the F-Numbers (10 bits) for the notes of the 12-tone scale. +// However, it does not match the table in the AdLib documentation I've seen. + +const uint16 AdLibDriver::_freqTable[] = { + 0x0134, 0x0147, 0x015A, 0x016F, 0x0184, 0x019C, 0x01B4, 0x01CE, 0x01E9, + 0x0207, 0x0225, 0x0246 +}; + +// These tables are currently only used by updateCallback46(), which only ever +// uses the first element of one of the sub-tables. + +const uint8 *const AdLibDriver::_unkTable2[] = { + AdLibDriver::_unkTable2_1, + AdLibDriver::_unkTable2_2, + AdLibDriver::_unkTable2_1, + AdLibDriver::_unkTable2_2, + AdLibDriver::_unkTable2_3, + AdLibDriver::_unkTable2_2 +}; + +const uint8 AdLibDriver::_unkTable2_1[] = { + 0x50, 0x50, 0x4F, 0x4F, 0x4E, 0x4E, 0x4D, 0x4D, + 0x4C, 0x4C, 0x4B, 0x4B, 0x4A, 0x4A, 0x49, 0x49, + 0x48, 0x48, 0x47, 0x47, 0x46, 0x46, 0x45, 0x45, + 0x44, 0x44, 0x43, 0x43, 0x42, 0x42, 0x41, 0x41, + 0x40, 0x40, 0x3F, 0x3F, 0x3E, 0x3E, 0x3D, 0x3D, + 0x3C, 0x3C, 0x3B, 0x3B, 0x3A, 0x3A, 0x39, 0x39, + 0x38, 0x38, 0x37, 0x37, 0x36, 0x36, 0x35, 0x35, + 0x34, 0x34, 0x33, 0x33, 0x32, 0x32, 0x31, 0x31, + 0x30, 0x30, 0x2F, 0x2F, 0x2E, 0x2E, 0x2D, 0x2D, + 0x2C, 0x2C, 0x2B, 0x2B, 0x2A, 0x2A, 0x29, 0x29, + 0x28, 0x28, 0x27, 0x27, 0x26, 0x26, 0x25, 0x25, + 0x24, 0x24, 0x23, 0x23, 0x22, 0x22, 0x21, 0x21, + 0x20, 0x20, 0x1F, 0x1F, 0x1E, 0x1E, 0x1D, 0x1D, + 0x1C, 0x1C, 0x1B, 0x1B, 0x1A, 0x1A, 0x19, 0x19, + 0x18, 0x18, 0x17, 0x17, 0x16, 0x16, 0x15, 0x15, + 0x14, 0x14, 0x13, 0x13, 0x12, 0x12, 0x11, 0x11, + 0x10, 0x10 +}; + +// no don't ask me WHY this table exsits! +const uint8 AdLibDriver::_unkTable2_2[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x6F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F +}; + +const uint8 AdLibDriver::_unkTable2_3[] = { + 0x40, 0x40, 0x40, 0x3F, 0x3F, 0x3F, 0x3E, 0x3E, + 0x3E, 0x3D, 0x3D, 0x3D, 0x3C, 0x3C, 0x3C, 0x3B, + 0x3B, 0x3B, 0x3A, 0x3A, 0x3A, 0x39, 0x39, 0x39, + 0x38, 0x38, 0x38, 0x37, 0x37, 0x37, 0x36, 0x36, + 0x36, 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x33, + 0x33, 0x33, 0x32, 0x32, 0x32, 0x31, 0x31, 0x31, + 0x30, 0x30, 0x30, 0x2F, 0x2F, 0x2F, 0x2E, 0x2E, + 0x2E, 0x2D, 0x2D, 0x2D, 0x2C, 0x2C, 0x2C, 0x2B, + 0x2B, 0x2B, 0x2A, 0x2A, 0x2A, 0x29, 0x29, 0x29, + 0x28, 0x28, 0x28, 0x27, 0x27, 0x27, 0x26, 0x26, + 0x26, 0x25, 0x25, 0x25, 0x24, 0x24, 0x24, 0x23, + 0x23, 0x23, 0x22, 0x22, 0x22, 0x21, 0x21, 0x21, + 0x20, 0x20, 0x20, 0x1F, 0x1F, 0x1F, 0x1E, 0x1E, + 0x1E, 0x1D, 0x1D, 0x1D, 0x1C, 0x1C, 0x1C, 0x1B, + 0x1B, 0x1B, 0x1A, 0x1A, 0x1A, 0x19, 0x19, 0x19, + 0x18, 0x18, 0x18, 0x17, 0x17, 0x17, 0x16, 0x16, + 0x16, 0x15 +}; + +// This table is used to modify the frequency of the notes, depending on the +// note value and the pitch bend value. In theory, we could very well try to +// access memory outside this table, but in reality that probably won't happen. +// + +const uint8 AdLibDriver::_pitchBendTables[][32] = { + // 0 + { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, + 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x19, + 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21 }, + // 1 + { 0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x07, 0x09, + 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x1A, + 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x22, 0x24 }, + // 2 + { 0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x09, + 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0x11, 0x12, 0x13, + 0x14, 0x15, 0x16, 0x17, 0x19, 0x1A, 0x1C, 0x1D, + 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x24, 0x25, 0x26 }, + // 3 + { 0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x0A, + 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x11, 0x12, 0x13, + 0x14, 0x15, 0x16, 0x17, 0x18, 0x1A, 0x1C, 0x1D, + 0x1E, 0x1F, 0x20, 0x21, 0x23, 0x25, 0x27, 0x28 }, + // 4 + { 0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x0A, + 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x11, 0x13, 0x15, + 0x16, 0x17, 0x18, 0x19, 0x1B, 0x1D, 0x1F, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x28, 0x2A }, + // 5 + { 0x00, 0x01, 0x02, 0x03, 0x05, 0x07, 0x09, 0x0B, + 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x13, 0x15, + 0x16, 0x17, 0x18, 0x19, 0x1B, 0x1D, 0x1F, 0x20, + 0x21, 0x22, 0x23, 0x25, 0x27, 0x29, 0x2B, 0x2D }, + // 6 + { 0x00, 0x01, 0x02, 0x03, 0x05, 0x07, 0x09, 0x0B, + 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x13, 0x15, + 0x16, 0x17, 0x18, 0x1A, 0x1C, 0x1E, 0x21, 0x24, + 0x25, 0x26, 0x27, 0x29, 0x2B, 0x2D, 0x2F, 0x30 }, + // 7 + { 0x00, 0x01, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, + 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x13, 0x15, 0x18, + 0x19, 0x1A, 0x1C, 0x1D, 0x1F, 0x21, 0x23, 0x25, + 0x26, 0x27, 0x29, 0x2B, 0x2D, 0x2F, 0x30, 0x32 }, + // 8 + { 0x00, 0x01, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x14, 0x17, 0x1A, + 0x19, 0x1A, 0x1C, 0x1E, 0x20, 0x22, 0x25, 0x28, + 0x29, 0x2A, 0x2B, 0x2D, 0x2F, 0x31, 0x33, 0x35 }, + // 9 + { 0x00, 0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0E, + 0x0F, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1B, + 0x1C, 0x1D, 0x1E, 0x20, 0x22, 0x24, 0x26, 0x29, + 0x2A, 0x2C, 0x2E, 0x30, 0x32, 0x34, 0x36, 0x39 }, + // 10 + { 0x00, 0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0E, + 0x0F, 0x10, 0x12, 0x14, 0x16, 0x19, 0x1B, 0x1E, + 0x1F, 0x21, 0x23, 0x25, 0x27, 0x29, 0x2B, 0x2D, + 0x2E, 0x2F, 0x31, 0x32, 0x34, 0x36, 0x39, 0x3C }, + // 11 + { 0x00, 0x01, 0x03, 0x05, 0x07, 0x0A, 0x0C, 0x0F, + 0x10, 0x11, 0x13, 0x15, 0x17, 0x19, 0x1B, 0x1E, + 0x1F, 0x20, 0x22, 0x24, 0x26, 0x28, 0x2B, 0x2E, + 0x2F, 0x30, 0x32, 0x34, 0x36, 0x39, 0x3C, 0x3F }, + // 12 + { 0x00, 0x02, 0x04, 0x06, 0x08, 0x0B, 0x0D, 0x10, + 0x11, 0x12, 0x14, 0x16, 0x18, 0x1B, 0x1E, 0x21, + 0x22, 0x23, 0x25, 0x27, 0x29, 0x2C, 0x2F, 0x32, + 0x33, 0x34, 0x36, 0x38, 0x3B, 0x34, 0x41, 0x44 }, + // 13 + { 0x00, 0x02, 0x04, 0x06, 0x08, 0x0B, 0x0D, 0x11, + 0x12, 0x13, 0x15, 0x17, 0x1A, 0x1D, 0x20, 0x23, + 0x24, 0x25, 0x27, 0x29, 0x2C, 0x2F, 0x32, 0x35, + 0x36, 0x37, 0x39, 0x3B, 0x3E, 0x41, 0x44, 0x47 } +}; + +} // End of namespace Kyra + +#undef CALLBACKS_PER_SECOND diff --git a/engines/kyra/sound/drivers/adlib.h b/engines/kyra/sound/drivers/adlib.h new file mode 100644 index 0000000000..71896fff0a --- /dev/null +++ b/engines/kyra/sound/drivers/adlib.h @@ -0,0 +1,393 @@ +/* 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. + * + * LGPL License + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + + +#ifndef KYRA_SOUND_ADLIBDRIVER_H +#define KYRA_SOUND_ADLIBDRIVER_H + +#include "kyra/resource/resource.h" +#include "common/mutex.h" + + +// Basic AdLib Programming: +// http://www.gamedev.net/reference/articles/article446.asp + + +namespace Audio { +class Mixer; +} + +namespace OPL { +class OPL; +} + +namespace Kyra { + +class AdLibDriver { +public: + AdLibDriver(Audio::Mixer *mixer, int version); + ~AdLibDriver(); + + void initDriver(); + void setSoundData(uint8 *data, uint32 size); + void queueTrack(int track, int volume); + bool isChannelPlaying(int channel) const; + void stopAllChannels(); + int getSoundTrigger() const { return _soundTrigger; } + void resetSoundTrigger() { _soundTrigger = 0; } + + void callback(); + + void setSyncJumpMask(uint16 mask) { _syncJumpMask = mask; } + + void setMusicVolume(uint8 volume); + void setSfxVolume(uint8 volume); + +private: + // These variables have not yet been named, but some of them are partly + // known nevertheless: + // + // pitchBend - Sound-related. Possibly some sort of pitch bend. + // unk18 - Sound-effect. Used for secondaryEffect1() + // unk19 - Sound-effect. Used for secondaryEffect1() + // unk20 - Sound-effect. Used for secondaryEffect1() + // unk21 - Sound-effect. Used for secondaryEffect1() + // unk22 - Sound-effect. Used for secondaryEffect1() + // unk29 - Sound-effect. Used for primaryEffect1() + // unk30 - Sound-effect. Used for primaryEffect1() + // unk31 - Sound-effect. Used for primaryEffect1() + // unk32 - Sound-effect. Used for primaryEffect2() + // unk33 - Sound-effect. Used for primaryEffect2() + // unk34 - Sound-effect. Used for primaryEffect2() + // unk35 - Sound-effect. Used for primaryEffect2() + // unk36 - Sound-effect. Used for primaryEffect2() + // unk37 - Sound-effect. Used for primaryEffect2() + // unk38 - Sound-effect. Used for primaryEffect2() + // unk39 - Currently unused, except for updateCallback56() + // unk40 - Currently unused, except for updateCallback56() + // unk41 - Sound-effect. Used for primaryEffect2() + + struct Channel { + bool lock; // New to ScummVM + uint8 opExtraLevel2; + const uint8 *dataptr; + uint8 duration; + uint8 repeatCounter; + int8 baseOctave; + uint8 priority; + uint8 dataptrStackPos; + const uint8 *dataptrStack[4]; + int8 baseNote; + uint8 unk29; + uint8 unk31; + uint16 unk30; + uint16 unk37; + uint8 unk33; + uint8 unk34; + uint8 unk35; + uint8 unk36; + uint8 unk32; + uint8 unk41; + uint8 unk38; + uint8 opExtraLevel1; + uint8 spacing2; + uint8 baseFreq; + uint8 tempo; + uint8 position; + uint8 regAx; + uint8 regBx; + typedef void (AdLibDriver::*Callback)(Channel&); + Callback primaryEffect; + Callback secondaryEffect; + uint8 fractionalSpacing; + uint8 opLevel1; + uint8 opLevel2; + uint8 opExtraLevel3; + uint8 twoChan; + uint8 unk39; + uint8 unk40; + uint8 spacing1; + uint8 durationRandomness; + uint8 unk19; + uint8 unk18; + int8 unk20; + int8 unk21; + uint8 unk22; + uint16 offset; + uint8 tempoReset; + uint8 rawNote; + int8 pitchBend; + uint8 volumeModifier; + }; + + void primaryEffect1(Channel &channel); + void primaryEffect2(Channel &channel); + void secondaryEffect1(Channel &channel); + + void resetAdLibState(); + void writeOPL(byte reg, byte val); + void initChannel(Channel &channel); + void noteOff(Channel &channel); + void unkOutput2(uint8 num); + + uint16 getRandomNr(); + void setupDuration(uint8 duration, Channel &channel); + + void setupNote(uint8 rawNote, Channel &channel, bool flag = false); + void setupInstrument(uint8 regOffset, const uint8 *dataptr, Channel &channel); + void noteOn(Channel &channel); + + void adjustVolume(Channel &channel); + + uint8 calculateOpLevel1(Channel &channel); + uint8 calculateOpLevel2(Channel &channel); + + uint16 checkValue(int16 val) { + if (val < 0) + val = 0; + else if (val > 0x3F) + val = 0x3F; + return val; + } + + // The sound data has at least two lookup tables: + // + // * One for programs, starting at offset 0. + // * One for instruments, starting at offset 500. + + uint8 *getProgram(int progId) { + const uint16 offset = READ_LE_UINT16(_soundData + 2 * progId); + + // In case an invalid offset is specified we return nullptr to + // indicate an error. 0xFFFF seems to indicate "this is not a valid + // program/instrument". However, 0 is also invalid because it points + // inside the offset table itself. We also ignore any offsets outside + // of the actual data size. + // The original does not contain any safety checks and will simply + // read outside of the valid sound data in case an invalid offset is + // encountered. + if (offset == 0 || offset >= _soundDataSize) { + return nullptr; + } else { + return _soundData + offset; + } + } + + const uint8 *getInstrument(int instrumentId) { + return getProgram(_numPrograms + instrumentId); + } + + void setupPrograms(); + void executePrograms(); + + struct ParserOpcode { + typedef int (AdLibDriver::*POpcode)(const uint8 *&dataptr, Channel &channel, uint8 value); + POpcode function; + const char *name; + }; + + void setupParserOpcodeTable(); + const ParserOpcode *_parserOpcodeTable; + int _parserOpcodeTableSize; + + int update_setRepeat(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_checkRepeat(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setupProgram(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setNoteSpacing(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_jump(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_jumpToSubroutine(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_returnFromSubroutine(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setBaseOctave(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_stopChannel(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_playRest(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_writeAdLib(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setupNoteAndDuration(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setBaseNote(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setupSecondaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_stopOtherChannel(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_waitForEndOfProgram(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setupInstrument(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setupPrimaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_removePrimaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setBaseFreq(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setupPrimaryEffect2(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setPriority(const uint8 *&dataptr, Channel &channel, uint8 value); + int updateCallback23(const uint8 *&dataptr, Channel &channel, uint8 value); + int updateCallback24(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setExtraLevel1(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setupDuration(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_playNote(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setFractionalNoteSpacing(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setTempo(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_removeSecondaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setChannelTempo(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setExtraLevel3(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setExtraLevel2(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_changeExtraLevel2(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setAMDepth(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setVibratoDepth(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_changeExtraLevel1(const uint8 *&dataptr, Channel &channel, uint8 value); + int updateCallback38(const uint8 *&dataptr, Channel &channel, uint8 value); + int updateCallback39(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_removePrimaryEffect2(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_pitchBend(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_resetToGlobalTempo(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_nop(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setDurationRandomness(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_changeChannelTempo(const uint8 *&dataptr, Channel &channel, uint8 value); + int updateCallback46(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setupRhythmSection(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_playRhythmSection(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_removeRhythmSection(const uint8 *&dataptr, Channel &channel, uint8 value); + int updateCallback51(const uint8 *&dataptr, Channel &channel, uint8 value); + int updateCallback52(const uint8 *&dataptr, Channel &channel, uint8 value); + int updateCallback53(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setSoundTrigger(const uint8 *&dataptr, Channel &channel, uint8 value); + int update_setTempoReset(const uint8 *&dataptr, Channel &channel, uint8 value); + int updateCallback56(const uint8 *&dataptr, Channel &channel, uint8 value); +private: + // These variables have not yet been named, but some of them are partly + // known nevertheless: + // + // _unkValue1 - Unknown. Used for updating _unkValue2 + // _unkValue2 - Unknown. Used for updating _unkValue4 + // _unkValue4 - Unknown. Used for updating _unkValue5 + // _unkValue5 - Unknown. Used for controlling updateCallback24(). + // _unkValue6 - Unknown. Rhythm section volume? + // _unkValue7 - Unknown. Rhythm section volume? + // _unkValue8 - Unknown. Rhythm section volume? + // _unkValue9 - Unknown. Rhythm section volume? + // _unkValue10 - Unknown. Rhythm section volume? + // _unkValue11 - Unknown. Rhythm section volume? + // _unkValue12 - Unknown. Rhythm section volume? + // _unkValue13 - Unknown. Rhythm section volume? + // _unkValue14 - Unknown. Rhythm section volume? + // _unkValue15 - Unknown. Rhythm section volume? + // _unkValue16 - Unknown. Rhythm section volume? + // _unkValue17 - Unknown. Rhythm section volume? + // _unkValue18 - Unknown. Rhythm section volume? + // _unkValue19 - Unknown. Rhythm section volume? + // _unkValue20 - Unknown. Rhythm section volume? + // _freqTable[] - Probably frequences for the 12-tone scale. + // _unkTable2[] - Unknown. Currently only used by updateCallback46() + // _unkTable2_1[] - One of the tables in _unkTable2[] + // _unkTable2_2[] - One of the tables in _unkTable2[] + // _unkTable2_3[] - One of the tables in _unkTable2[] + + int _curChannel; + uint8 _soundTrigger; + + uint16 _rnd; + + uint8 _unkValue1; + uint8 _unkValue2; + uint8 _callbackTimer; + uint8 _unkValue4; + uint8 _unkValue5; + uint8 _unkValue6; + uint8 _unkValue7; + uint8 _unkValue8; + uint8 _unkValue9; + uint8 _unkValue10; + uint8 _unkValue11; + uint8 _unkValue12; + uint8 _unkValue13; + uint8 _unkValue14; + uint8 _unkValue15; + uint8 _unkValue16; + uint8 _unkValue17; + uint8 _unkValue18; + uint8 _unkValue19; + uint8 _unkValue20; + + OPL::OPL *_adlib; + + uint8 *_soundData; + uint32 _soundDataSize; + + struct QueueEntry { + QueueEntry() : data(0), id(0), volume(0) {} + QueueEntry(uint8 *ptr, uint8 track, uint8 vol) : data(ptr), id(track), volume(vol) {} + uint8 *data; + uint8 id; + uint8 volume; + }; + + QueueEntry _programQueue[16]; + int _programStartTimeout; + int _programQueueStart, _programQueueEnd; + bool _retrySounds; + + void adjustSfxData(uint8 *data, int volume); + uint8 *_sfxPointer; + int _sfxPriority; + int _sfxVelocity; + + Channel _channels[10]; + + uint8 _vibratoAndAMDepthBits; + uint8 _rhythmSectionBits; + + uint8 _curRegOffset; + uint8 _tempo; + + const uint8 *_tablePtr1; + const uint8 *_tablePtr2; + + static const uint8 _regOffset[]; + static const uint16 _freqTable[]; + static const uint8 *const _unkTable2[]; + static const uint8 _unkTable2_1[]; + static const uint8 _unkTable2_2[]; + static const uint8 _unkTable2_3[]; + static const uint8 _pitchBendTables[][32]; + + uint16 _syncJumpMask; + + Common::Mutex _mutex; + Audio::Mixer *_mixer; + + uint8 _musicVolume, _sfxVolume; + + int _numPrograms; + int _version; +}; + +} // End of namespace Kyra + +#endif
\ No newline at end of file diff --git a/engines/kyra/sound/drivers/audstream.cpp b/engines/kyra/sound/drivers/audstream.cpp new file mode 100644 index 0000000000..24522ee751 --- /dev/null +++ b/engines/kyra/sound/drivers/audstream.cpp @@ -0,0 +1,320 @@ +/* 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. + * + */ + +#include "kyra/resource/resource.h" +#include "audio/audiostream.h" + +#include "common/util.h" + +namespace Kyra { + +// Thanks to Torbjorn Andersson (eriktorbjorn) for his aud player on which +// this code is based on + +// TODO: cleanup of whole AUDStream + +class AUDStream : public Audio::SeekableAudioStream { +public: + AUDStream(Common::SeekableReadStream* stream); + ~AUDStream(); + + int readBuffer(int16* buffer, const int numSamples); + + bool isStereo() const { return false; } + bool endOfData() const { return _endOfData; } + + int getRate() const { return _rate; } + + bool seek(const Audio::Timestamp& where); + Audio::Timestamp getLength() const { return _length; } +private: + Common::SeekableReadStream* _stream; + uint32 _streamStart; + bool _endOfData; + int _rate; + uint _processedSize; + uint _totalSize; + Audio::Timestamp _length; + + int _bytesLeft; + + byte* _outBuffer; + int _outBufferOffset; + uint _outBufferSize; + + byte* _inBuffer; + uint _inBufferSize; + + int readChunk(int16* buffer, const int maxSamples); + + static const int8 WSTable2Bit[]; + static const int8 WSTable4Bit[]; +}; + +const int8 AUDStream::WSTable2Bit[] = { -2, -1, 0, 1 }; +const int8 AUDStream::WSTable4Bit[] = { + -9, -8, -6, -5, -4, -3, -2, -1, + 0, 1, 2, 3, 4, 5, 6, 8 +}; + +AUDStream::AUDStream(Common::SeekableReadStream *stream) : _stream(stream), _endOfData(true), _rate(0), + _processedSize(0), _totalSize(0), _length(0, 1), _bytesLeft(0), _outBuffer(0), + _outBufferOffset(0), _outBufferSize(0), _inBuffer(0), _inBufferSize(0) { + + _rate = _stream->readUint16LE(); + _totalSize = _stream->readUint32LE(); + + // TODO?: add checks + int flags = _stream->readByte(); // flags + int type = _stream->readByte(); // type + + _streamStart = stream->pos(); + + debugC(5, kDebugLevelSound, "AUD Info: rate: %d, totalSize: %d, flags: %d, type: %d, streamStart: %d", _rate, _totalSize, flags, type, _streamStart); + + _length = Audio::Timestamp(0, _rate); + for (uint32 i = 0; i < _totalSize;) { + uint16 size = _stream->readUint16LE(); + uint16 outSize = _stream->readUint16LE(); + + _length = _length.addFrames(outSize); + stream->seek(size + 4, SEEK_CUR); + i += size + 8; + } + + stream->seek(_streamStart, SEEK_SET); + + if (type == 1 && !flags) + _endOfData = false; + else + warning("No AUD file (rate: %d, size: %d, flags: 0x%X, type: %d)", _rate, _totalSize, flags, type); +} + +AUDStream::~AUDStream() { + delete[] _outBuffer; + delete[] _inBuffer; + delete _stream; +} + +int AUDStream::readBuffer(int16 *buffer, const int numSamples) { + int samplesRead = 0, samplesLeft = numSamples; + + while (samplesLeft > 0 && !_endOfData) { + int samples = readChunk(buffer, samplesLeft); + samplesRead += samples; + samplesLeft -= samples; + buffer += samples; + } + + return samplesRead; +} + +inline int16 clip8BitSample(int16 sample) { + return CLIP<int16>(sample, 0, 255); +} + +int AUDStream::readChunk(int16 *buffer, const int maxSamples) { + int samplesProcessed = 0; + + // if no bytes of the old chunk are left, read the next one + if (_bytesLeft <= 0) { + if (_processedSize >= _totalSize) { + _endOfData = true; + return 0; + } + + uint16 size = _stream->readUint16LE(); + uint16 outSize = _stream->readUint16LE(); + uint32 id = _stream->readUint32LE(); + + assert(id == 0x0000DEAF); + + _processedSize += 8 + size; + + _outBufferOffset = 0; + if (size == outSize) { + if (outSize > _outBufferSize) { + _outBufferSize = outSize; + delete[] _outBuffer; + _outBuffer = new uint8[_outBufferSize]; + assert(_outBuffer); + } + + _bytesLeft = size; + + _stream->read(_outBuffer, _bytesLeft); + } else { + _bytesLeft = outSize; + + if (outSize > _outBufferSize) { + _outBufferSize = outSize; + delete[] _outBuffer; + _outBuffer = new uint8[_outBufferSize]; + assert(_outBuffer); + } + + if (size > _inBufferSize) { + _inBufferSize = size; + delete[] _inBuffer; + _inBuffer = new uint8[_inBufferSize]; + assert(_inBuffer); + } + + if (_stream->read(_inBuffer, size) != size) { + _endOfData = true; + return 0; + } + + int16 curSample = 0x80; + byte code = 0; + int8 count = 0; + uint16 input = 0; + int j = 0; + int i = 0; + + while (outSize > 0) { + input = _inBuffer[i++] << 2; + code = (input >> 8) & 0xFF; + count = (input & 0xFF) >> 2; + + switch (code) { + case 2: + if (count & 0x20) { + /* NOTE: count is signed! */ + count <<= 3; + curSample += (count >> 3); + _outBuffer[j++] = curSample & 0xFF; + outSize--; + } else { + for (; count >= 0; count--) { + _outBuffer[j++] = _inBuffer[i++]; + outSize--; + } + curSample = _inBuffer[i - 1]; + } + break; + case 1: + for (; count >= 0; count--) { + code = _inBuffer[i++]; + + curSample += WSTable4Bit[code & 0x0F]; + curSample = clip8BitSample(curSample); + _outBuffer[j++] = curSample; + + curSample += WSTable4Bit[code >> 4]; + curSample = clip8BitSample(curSample); + _outBuffer[j++] = curSample; + + outSize -= 2; + } + break; + case 0: + for (; count >= 0; count--) { + code = (uint8)_inBuffer[i++]; + + curSample += WSTable2Bit[code & 0x03]; + curSample = clip8BitSample(curSample); + _outBuffer[j++] = curSample & 0xFF; + + curSample += WSTable2Bit[(code >> 2) & 0x03]; + curSample = clip8BitSample(curSample); + _outBuffer[j++] = curSample & 0xFF; + + curSample += WSTable2Bit[(code >> 4) & 0x03]; + curSample = clip8BitSample(curSample); + _outBuffer[j++] = curSample & 0xFF; + + curSample += WSTable2Bit[(code >> 6) & 0x03]; + curSample = clip8BitSample(curSample); + _outBuffer[j++] = curSample & 0xFF; + + outSize -= 4; + } + break; + default: + for (; count >= 0; count--) { + _outBuffer[j++] = curSample & 0xFF; + outSize--; + } + } + } + } + } + + // copies the chunk data to the output buffer + if (_bytesLeft > 0) { + int samples = MIN(_bytesLeft, maxSamples); + samplesProcessed += samples; + _bytesLeft -= samples; + + while (samples--) { + int16 sample = (_outBuffer[_outBufferOffset++] << 8) ^ 0x8000; + + *buffer++ = sample; + } + } + + return samplesProcessed; +} + +bool AUDStream::seek(const Audio::Timestamp &where) { + const uint32 seekSample = Audio::convertTimeToStreamPos(where, getRate(), isStereo()).totalNumberOfFrames(); + + _stream->seek(_streamStart); + _processedSize = 0; + _bytesLeft = 0; + _endOfData = false; + + uint32 curSample = 0; + + while (!endOfData()) { + uint16 size = _stream->readUint16LE(); + uint16 outSize = _stream->readUint16LE(); + + if (curSample + outSize > seekSample) { + _stream->seek(-4, SEEK_CUR); + + uint32 samples = seekSample - curSample; + int16 *temp = new int16[samples]; + assert(temp); + + readChunk(temp, samples); + delete[] temp; + curSample += samples; + break; + } else { + curSample += outSize; + _processedSize += 8 + size; + _stream->seek(size + 4, SEEK_CUR); + } + } + + _endOfData = (_processedSize >= _totalSize); + + return (curSample == seekSample); +} + +Audio::SeekableAudioStream* makeAUDStream(Common::SeekableReadStream* stream, DisposeAfterUse::Flag disposeAfterUse) { + return new AUDStream(stream); +} + +} // End of namespace Kyra diff --git a/engines/kyra/sound/drivers/midi.cpp b/engines/kyra/sound/drivers/midi.cpp new file mode 100644 index 0000000000..5e8460aa97 --- /dev/null +++ b/engines/kyra/sound/drivers/midi.cpp @@ -0,0 +1,355 @@ +/* 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. + * + */ + +#include "kyra/sound/drivers/midi.h" + +namespace Kyra { + +MidiOutput::MidiOutput(OSystem *system, MidiDriver *output, bool isMT32, bool defaultMT32) : _system(system), _output(output) { + _isMT32 = isMT32; + _defaultMT32 = defaultMT32; + + int ret = _output->open(); + if (ret != MidiDriver::MERR_ALREADY_OPEN && ret != 0) + error("Couldn't open midi driver"); + + static const Controller defaultControllers[] = { + { 0x07, 0x7F }, { 0x01, 0x00 }, { 0x0A, 0x40 }, + { 0x0B, 0x7F }, { 0x40, 0x00 }, { 0x72, 0x00 }, + { 0x6E, 0x00 }, { 0x6F, 0x00 }, { 0x70, 0x00 } + }; + + static const byte defaultPrograms[] = { + 0x44, 0x30, 0x5F, 0x4E, 0x29, 0x03, 0x6E, 0x7A, 0xFF + }; + + static const byte sysEx1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + static const byte sysEx2[] = { 3, 4, 3, 4, 3, 4, 3, 4, 4 }; + static const byte sysEx3[] = { 0, 3, 2 }; + + if (_isMT32) { + sendSysEx(0x7F, 0x00, 0x00, sysEx1, 1); + sendSysEx(0x10, 0x00, 0x0D, sysEx1, 9); + sendSysEx(0x10, 0x00, 0x04, sysEx2, 9); + sendSysEx(0x10, 0x00, 0x01, sysEx3, 3); + } else { + _output->sendGMReset(); + } + + memset(_channels, 0, sizeof(_channels)); + for (int i = 0; i < 16; ++i) { + for (int j = 0; j < 9; ++j) + _channels[i].controllers[j] = defaultControllers[j]; + _channels[i].pitchWheel = -1; + _channels[i].program = 0xFF; + } + + for (int i = 0; i < 9; ++i) { + for (int j = 1; j <= 9; ++j) + sendIntern(0xB0, j, defaultControllers[i].controller, defaultControllers[i].value); + } + + for (int i = 1; i <= 9; ++i) { + sendIntern(0xE0, i, 0x00, 0x40); + if (defaultPrograms[i - 1] != 0xFF) + sendIntern(0xC0, i, defaultPrograms[i - 1], 0x00); + } + + for (int i = 0; i < 4; ++i) { + _sources[i].volume = 256; + initSource(i); + } +} + + +MidiOutput::~MidiOutput() { + _output->close(); + delete _output; +} + +void MidiOutput::send(uint32 b) { + const byte event = b & 0xF0; + const byte channel = b & 0x0F; + byte param1 = (b >> 8) & 0xFF; + byte param2 = (b >> 16) & 0xFF; + + if (event == 0xE0) { // Pitch-Wheel + _channels[channel].pitchWheel = + _sources[_curSource].channelPW[channel] = (param2 << 8) | param1; + } else if (event == 0xC0) { // Program change + _channels[channel].program = + _sources[_curSource].channelProgram[channel] = param1; + } else if (event == 0xB0) { // Controller change + for (int i = 0; i < 9; ++i) { + Controller &cont = _sources[_curSource].controllers[channel][i]; + if (cont.controller == param1) { + cont.value = param2; + break; + } + } + + if (param1 == 0x07) { + param2 = (param2 * _sources[_curSource].volume) >> 8; + } else if (param1 == 0x6E) { // Lock Channel + if (param2 >= 0x40) { // Lock Channel + int chan = lockChannel(); + if (chan < 0) + chan = channel; + _sources[_curSource].channelMap[channel] = chan; + } else { // Unlock Channel + stopNotesOnChannel(channel); + unlockChannel(_sources[_curSource].channelMap[channel]); + _sources[_curSource].channelMap[channel] = channel; + } + } else if (param1 == 0x6F) { // Protect Channel + if (param2 >= 0x40) { // Protect Channel + _channels[channel].flags |= kChannelProtected; + } else { // Unprotect Channel + _channels[channel].flags &= ~kChannelProtected; + } + } else if (param1 == 0x7B) { // All notes off + // FIXME: Since the XMIDI parsers sends this + // on track change, we simply ignore it. + return; + } + } else if (event == 0x90 || event == 0x80) { // Note On/Off + if (!(_channels[channel].flags & kChannelLocked)) { + const bool remove = (event == 0x80) || (param2 == 0x00); + int note = -1; + + for (int i = 0; i < 32; ++i) { + if (remove) { + if (_sources[_curSource].notes[i].channel == channel && + _sources[_curSource].notes[i].note == param1) { + note = i; + break; + } + } else { + if (_sources[_curSource].notes[i].channel == 0xFF) { + note = i; + break; + } + } + } + + if (note != -1) { + if (remove) { + _sources[_curSource].notes[note].channel = 0xFF; + + --_channels[_sources[_curSource].channelMap[channel]].noteCount; + } else { + _sources[_curSource].notes[note].channel = channel; + _sources[_curSource].notes[note].note = param1; + + ++_channels[_sources[_curSource].channelMap[channel]].noteCount; + } + + sendIntern(event, _sources[_curSource].channelMap[channel], param1, param2); + } + } + return; + } + + if (!(_channels[channel].flags & kChannelLocked)) + sendIntern(event, _sources[_curSource].channelMap[channel], param1, param2); +} + +void MidiOutput::sendIntern(const byte event, const byte channel, byte param1, const byte param2) { + if (event == 0xC0) { + // MT32 -> GM conversion + if (!_isMT32 && _defaultMT32) + param1 = MidiDriver::_mt32ToGm[param1]; + } + + _output->send(event | channel, param1, param2); +} + +void MidiOutput::sysEx(const byte *msg, uint16 length) { + // Wait the time it takes to send the SysEx data + uint32 delay = (length + 2) * 1000 / 3125; + + // Plus an additional delay for the MT-32 rev00 + if (_isMT32) + delay += 40; + + _output->sysEx(msg, length); + _system->delayMillis(delay); +} + +void MidiOutput::sendSysEx(const byte p1, const byte p2, const byte p3, const byte *buffer, const int size) { + int bufferSize = 8 + size; + byte *outBuffer = new byte[bufferSize]; + assert(outBuffer); + + outBuffer[0] = 0x41; + outBuffer[1] = 0x10; + outBuffer[2] = 0x16; + outBuffer[3] = 0x12; + + outBuffer[4] = p1; + outBuffer[5] = p2; + outBuffer[6] = p3; + + memcpy(outBuffer + 7, buffer, size); + + uint16 checkSum = p1 + p2 + p3; + for (int i = 0; i < size; ++i) + checkSum += buffer[i]; + checkSum &= 0x7F; + checkSum -= 0x80; + checkSum = -checkSum; + checkSum &= 0x7F; + + outBuffer[7+size] = checkSum; + + sysEx(outBuffer, bufferSize); + + delete[] outBuffer; +} + +void MidiOutput::metaEvent(byte type, byte *data, uint16 length) { + if (type == 0x2F) // End of Track + deinitSource(_curSource); + + _output->metaEvent(type, data, length); +} + +void MidiOutput::setSourceVolume(int source, int volume, bool apply) { + _sources[source].volume = volume; + + if (apply) { + for (int i = 0; i < 16; ++i) { + // Controller 0 in the state table should always be '7' aka + // volume control + byte realVol = (_sources[source].controllers[i][0].value * volume) >> 8; + sendIntern(0xB0, i, 0x07, realVol); + } + } +} + +void MidiOutput::initSource(int source) { + memset(_sources[source].notes, -1, sizeof(_sources[source].notes)); + + for (int i = 0; i < 16; ++i) { + _sources[source].channelMap[i] = i; + _sources[source].channelProgram[i] = 0xFF; + _sources[source].channelPW[i] = -1; + + for (int j = 0; j < 9; ++j) + _sources[source].controllers[i][j] = _channels[i].controllers[j]; + } +} + +void MidiOutput::deinitSource(int source) { + for (int i = 0; i < 16; ++i) { + for (int j = 0; j < 9; ++j) { + const Controller &cont = _sources[source].controllers[i][j]; + + if (cont.controller == 0x40) { + if (cont.value >= 0x40) + sendIntern(0xB0, i, 0x40, 0); + } else if (cont.controller == 0x6E) { + if (cont.value >= 0x40) { + stopNotesOnChannel(i); + unlockChannel(_sources[source].channelMap[i]); + _sources[source].channelMap[i] = i; + } + } else if (cont.controller == 0x6F) { + if (cont.value >= 0x40) + _channels[i].flags &= ~kChannelProtected; + } else if (cont.controller == 0x70) { + if (cont.value >= 0x40) + sendIntern(0xB0, i, 0x70, 0); + } + } + } +} + +int MidiOutput::lockChannel() { + int channel = -1; + int notes = 0xFF; + byte flags = kChannelLocked | kChannelProtected; + + while (channel == -1) { + for (int i = _isMT32 ? 8 : 15; i >= 1; --i) { + if (_channels[i].flags & flags) + continue; + if (_channels[i].noteCount < notes) { + channel = i; + notes = _channels[i].noteCount; + } + } + + if (channel == -1) { + if (flags & kChannelProtected) + flags &= ~kChannelProtected; + else + break; + } + } + + if (channel == -1) + return -1; + + sendIntern(0xB0, channel, 0x40, 0); + stopNotesOnChannel(channel); + _channels[channel].noteCount = 0; + _channels[channel].flags |= kChannelLocked; + + return channel; +} + +void MidiOutput::unlockChannel(int channel) { + if (!(_channels[channel].flags & kChannelLocked)) + return; + + _channels[channel].flags &= ~kChannelLocked; + _channels[channel].noteCount = 0; + sendIntern(0xB0, channel, 0x40, 0); + sendIntern(0xB0, channel, 0x7B, 0); + + for (int i = 0; i < 9; ++i) { + if (_channels[channel].controllers[i].value != 0xFF) + sendIntern(0xB0, channel, _channels[channel].controllers[i].controller, _channels[channel].controllers[i].value); + } + + if (_channels[channel].program != 0xFF) + sendIntern(0xC0, channel, _channels[channel].program, 0); + + if (_channels[channel].pitchWheel != -1) + sendIntern(0xE0, channel, _channels[channel].pitchWheel & 0xFF, (_channels[channel].pitchWheel >> 8) & 0xFF); +} + +void MidiOutput::stopNotesOnChannel(int channel) { + for (int i = 0; i < 4; ++i) { + SoundSource &sound = _sources[i]; + for (int j = 0; j < 32; ++j) { + if (sound.notes[j].channel == channel) { + sound.notes[j].channel = 0xFF; + sendIntern(0x80, sound.channelMap[channel], sound.notes[j].note, 0); + --_channels[sound.channelMap[channel]].noteCount; + } + } + } +} + +} // End of namespace Kyra diff --git a/engines/kyra/sound/drivers/midi.h b/engines/kyra/sound/drivers/midi.h new file mode 100644 index 0000000000..386852e800 --- /dev/null +++ b/engines/kyra/sound/drivers/midi.h @@ -0,0 +1,108 @@ +/* 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. + * + */ + +#ifndef KYRA_SOUND_MIDIDRIVER_H +#define KYRA_SOUND_MIDIDRIVER_H + +#include "kyra/sound/sound_intern.h" + +namespace Kyra { + +class MidiOutput : public MidiDriver_BASE { +public: + MidiOutput(OSystem *system, MidiDriver *output, bool isMT32, bool defaultMT32); + ~MidiOutput(); + + void setSourceVolume(int source, int volume, bool apply=false); + + void initSource(int source); + void deinitSource(int source); + void stopNotesOnChannel(int channel); + + void setSoundSource(int source) { _curSource = source; } + + // MidiDriver_BASE interface + virtual void send(uint32 b); + virtual void sysEx(const byte *msg, uint16 length); + virtual void metaEvent(byte type, byte *data, uint16 length); + + // TODO: Get rid of the following two methods + void setTimerCallback(void *timerParam, void (*timerProc)(void *)) { _output->setTimerCallback(timerParam, timerProc); } + uint32 getBaseTempo() { return _output->getBaseTempo(); } + + +private: + void sendIntern(const byte event, const byte channel, byte param1, const byte param2); + void sendSysEx(const byte p1, const byte p2, const byte p3, const byte *buffer, const int size); + + OSystem *_system; + MidiDriver *_output; + + bool _isMT32; + bool _defaultMT32; + + struct Controller { + byte controller; + byte value; + }; + + enum { + kChannelLocked = 0x80, + kChannelProtected = 0x40 + }; + + struct Channel { + byte flags; + + byte program; + int16 pitchWheel; + + byte noteCount; + + Controller controllers[9]; + } _channels[16]; + + int lockChannel(); + void unlockChannel(int channel); + + int _curSource; + + struct SoundSource { + int volume; + + int8 channelMap[16]; + byte channelProgram[16]; + int16 channelPW[16]; + Controller controllers[16][9]; + + struct Note { + byte channel; + byte note; + }; + + Note notes[32]; + } _sources[4]; +}; + +} // End of namespace Kyra + +#endif
\ No newline at end of file diff --git a/engines/kyra/sound/drivers/pcspeaker.cpp b/engines/kyra/sound/drivers/pcspeaker.cpp new file mode 100644 index 0000000000..110addefd8 --- /dev/null +++ b/engines/kyra/sound/drivers/pcspeaker.cpp @@ -0,0 +1,366 @@ +/* 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. + * + */ + +#include "kyra/sound/sound_intern.h" + +#include "audio/mixer.h" +#include "audio/softsynth/pcspk.h" + +namespace Kyra { + +MidiDriver_PCSpeaker::MidiDriver_PCSpeaker(Audio::Mixer *mixer) + : MidiDriver_Emulated(mixer), _rate(mixer->getOutputRate()) { + _timerValue = 0; + memset(_channel, 0, sizeof(_channel)); + memset(_note, 0, sizeof(_note)); + + for (int i = 0; i < 2; ++i) + _note[i].hardwareChannel = 0xFF; + + _speaker = new Audio::PCSpeaker(_rate); + assert(_speaker); + _mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + + _countdown = 0xFFFF; + _hardwareChannel[0] = 0xFF; + _modulationFlag = false; +} + +MidiDriver_PCSpeaker::~MidiDriver_PCSpeaker() { + _mixer->stopHandle(_mixerSoundHandle); + delete _speaker; + _speaker = 0; +} + +void MidiDriver_PCSpeaker::send(uint32 data) { + Common::StackLock lock(_mutex); + + uint8 channel = data & 0x0F; + uint8 param1 = (data >> 8) & 0xFF; + uint8 param2 = (data >> 16) & 0xFF; + + uint8 flags = 0x00; + + if (channel > 1) + return; + + switch (data & 0xF0) { + case 0x80: // note off + noteOff(channel, param1); + return; + + case 0x90: // note on + if (channel > 1) + return; + + if (param2) + noteOn(channel, param1); + else + noteOff(channel, param1); + return; + + case 0xB0: // controller + switch (param1) { + case 0x01: // modulation + _channel[channel].modulation = param2; + break; + + case 0x40: // hold + _channel[channel].hold = param2; + if (param2 < 0x40) + resetController(channel); + return; + + case 0x70: // voice protect + _channel[channel].voiceProtect = param2; + return; + + case 0x79: // all notes off + _channel[channel].hold = 0; + resetController(channel); + _channel[channel].modulation = 0; + _channel[channel].pitchBendLow = 0; + _channel[channel].pitchBendHigh = 0x40; + flags = 0x01; + break; + + default: + return; + } + break; + + case 0xE0: // pitch bend + flags = 0x01; + _channel[channel].pitchBendLow = param1; + _channel[channel].pitchBendHigh = param2; + break; + + default: + return; + } + + for (int i = 0; i < 2; ++i) { + if (_note[i].enabled && _note[i].midiChannel == channel) { + _note[i].flags |= flags; + setupTone(i); + } + } +} + +void MidiDriver_PCSpeaker::resetController(int channel) { + for (int i = 0; i < 2; ++i) { + if (_note[i].enabled && _note[i].midiChannel == channel && _note[i].processHold) + noteOff(channel, _note[i].note); + } +} + +void MidiDriver_PCSpeaker::noteOn(int channel, int note) { + int n = 0; + + while (n < 2 && _note[n].enabled) + ++n; + + if (n >= 2) + return; + + _note[n].midiChannel = channel; + _note[n].note = note; + _note[n].enabled = true; + _note[n].processHold = false; + _note[n].hardwareFlags = 0x20; + _note[n].priority = 0x7FFF; + _note[n].flags = 0x01; + + turnNoteOn(n); +} + +void MidiDriver_PCSpeaker::turnNoteOn(int note) { + if (_hardwareChannel[0] == 0xFF) { + _note[note].hardwareChannel = 0; + ++_channel[_note[note].midiChannel].noteCount; + _hardwareChannel[0] = _note[note].midiChannel; + _note[note].flags = 0x01; + + setupTone(note); + } else { + overwriteNote(note); + } +} + +void MidiDriver_PCSpeaker::overwriteNote(int note) { + int totalNotes = 0; + for (int i = 0; i < 2; ++i) { + if (_note[i].enabled) { + ++totalNotes; + const int channel = _note[i].midiChannel; + + uint16 priority = 0xFFFF; + if (_channel[channel].voiceProtect < 0x40) + priority = _note[i].priority; + + if (_channel[channel].noteCount > priority) + priority = 0; + else + priority -= _channel[channel].noteCount; + + _note[i].precedence = priority; + } + } + + if (totalNotes <= 1) + return; + + do { + uint16 maxValue = 0; + uint16 minValue = 0xFFFF; + int newNote = 0; + + for (int i = 0; i < 2; ++i) { + if (_note[i].enabled) { + if (_note[i].hardwareChannel == 0xFF) { + if (_note[i].precedence >= maxValue) { + maxValue = _note[i].precedence; + newNote = i; + } + } else { + if (_note[i].precedence <= minValue) { + minValue = _note[i].precedence; + note = i; + } + } + } + } + + if (maxValue < minValue) + return; + + turnNoteOff(_note[note].hardwareChannel); + _note[note].enabled = false; + + _note[newNote].hardwareChannel = _note[note].hardwareChannel; + ++_channel[_note[newNote].midiChannel].noteCount; + _hardwareChannel[_note[note].hardwareChannel] = _note[newNote].midiChannel; + _note[newNote].flags = 0x01; + + setupTone(newNote); + } while (--totalNotes); +} + +void MidiDriver_PCSpeaker::noteOff(int channel, int note) { + for (int i = 0; i < 2; ++i) { + if (_note[i].enabled && _note[i].note == note && _note[i].midiChannel == channel) { + if (_channel[i].hold < 0x40) { + turnNoteOff(i); + _note[i].enabled = false; + } else { + _note[i].processHold = true; + } + } + } +} + +void MidiDriver_PCSpeaker::turnNoteOff(int note) { + if (_note[note].hardwareChannel != 0xFF) { + _note[note].hardwareFlags &= 0xDF; + _note[note].flags |= 1; + + setupTone(note); + + --_channel[_note[note].midiChannel].noteCount; + + _hardwareChannel[_note[note].hardwareChannel] = 0xFF; + _note[note].hardwareChannel = 0xFF; + } +} + +void MidiDriver_PCSpeaker::setupTone(int note) { + if (_note[note].hardwareChannel == 0xFF) + return; + + if (!(_note[note].flags & 0x01)) + return; + + if (!(_note[note].hardwareFlags & 0x20)) { + _speaker->stop(); + } else { + const int midiChannel = _note[note].midiChannel; + uint16 pitchBend = (_channel[midiChannel].pitchBendHigh << 7) | _channel[midiChannel].pitchBendLow; + + int noteValue = _note[note].note; + + noteValue -= 24; + do { + noteValue += 12; + } while (noteValue < 0); + + noteValue += 12; + do { + noteValue -= 12; + } while (noteValue > 95); + + int16 modulation = _note[note].modulation; + + int tableIndex = MAX(noteValue - 12, 0); + uint16 note1 = (_noteTable2[tableIndex] << 8) | _noteTable1[tableIndex]; + tableIndex = MIN(noteValue + 12, 95); + uint16 note2 = (_noteTable2[tableIndex] << 8) | _noteTable1[tableIndex]; + uint16 note3 = (_noteTable2[noteValue] << 8) | _noteTable1[noteValue]; + + int32 countdown = pitchBend - 0x2000; + countdown += modulation; + + if (countdown >= 0) + countdown *= (note2 - note3); + else + countdown *= (note3 - note1); + + countdown /= 0x2000; + countdown += note3; + + countdown = uint16(countdown & 0xFFFF); + if (countdown != _countdown) + _countdown = countdown; + + _speaker->play(Audio::PCSpeaker::kWaveFormSquare, 1193180 / _countdown, -1); + } + + _note[note].flags &= 0xFE; +} + +void MidiDriver_PCSpeaker::generateSamples(int16 *buffer, int numSamples) { + Common::StackLock lock(_mutex); + _speaker->readBuffer(buffer, numSamples); +} + +void MidiDriver_PCSpeaker::onTimer() { + /*Common::StackLock lock(_mutex); + + _timerValue += 20; + if (_timerValue < 120) + return; + _timerValue -= 120; + + _modulationFlag = !_modulationFlag; + for (int i = 0; i < 2; ++i) { + if (_note[i].enabled) { + uint16 modValue = 5 * _channel[_note[i].midiChannel].modulation; + if (_modulationFlag) + modValue = -modValue; + _note[i].modulation = modValue; + _note[i].flags |= 1; + + setupTone(i); + } + }*/ +} + +const uint8 MidiDriver_PCSpeaker::_noteTable1[] = { + 0x88, 0xB5, 0x4E, 0x40, 0x41, 0xCD, 0xC4, 0x3D, + 0x43, 0x7C, 0x2A, 0xD6, 0x88, 0xB5, 0xFF, 0xD1, + 0x20, 0xA7, 0xE2, 0x1E, 0xCE, 0xBE, 0xF2, 0x8A, + 0x44, 0x41, 0x7F, 0xE8, 0x90, 0x63, 0x63, 0x8F, + 0xE7, 0x5F, 0x01, 0xBD, 0xA2, 0xA0, 0xBF, 0xF4, + 0x48, 0xB1, 0x31, 0xC7, 0x70, 0x2F, 0xFE, 0xE0, + 0xD1, 0xD0, 0xDE, 0xFB, 0x24, 0x58, 0x98, 0xE3, + 0x39, 0x97, 0xFF, 0x6F, 0xE8, 0x68, 0xEF, 0x7D, + 0x11, 0xAC, 0x4C, 0xF1, 0x9C, 0x4B, 0xFF, 0xB7, + 0x74, 0x34, 0xF7, 0xBE, 0x88, 0x56, 0x26, 0xF8, + 0xCE, 0xA5, 0x7F, 0x5B, 0x3A, 0x1A, 0xFB, 0xDF, + 0xC4, 0xAB, 0x93, 0x7C, 0x67, 0x52, 0x3F, 0x2D +}; + +const uint8 MidiDriver_PCSpeaker::_noteTable2[] = { + 0x8E, 0x86, 0xFD, 0xF0, 0xE2, 0xD5, 0xC9, 0xBE, + 0xB3, 0xA9, 0xA0, 0x96, 0x8E, 0x86, 0x7E, 0x77, + 0x71, 0x6A, 0x64, 0x5F, 0x59, 0x54, 0x4F, 0x4B, + 0x47, 0x43, 0x3F, 0x3B, 0x38, 0x35, 0x32, 0x2F, + 0x2C, 0x2A, 0x28, 0x25, 0x23, 0x21, 0x1F, 0x1D, + 0x1C, 0x1A, 0x19, 0x17, 0x16, 0x15, 0x13, 0x12, + 0x11, 0x10, 0x0F, 0x0E, 0x0E, 0x0D, 0x0C, 0x0B, + 0x0B, 0x0A, 0x09, 0x09, 0x08, 0x08, 0x07, 0x07, + 0x07, 0x06, 0x06, 0x05, 0x05, 0x05, 0x04, 0x04, + 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 +}; + +} // End of namespace Kyra |