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 | |
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')
-rw-r--r-- | engines/kyra/module.mk | 5 | ||||
-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 (renamed from engines/kyra/sound/sound_pcspk.cpp) | 0 | ||||
-rw-r--r-- | engines/kyra/sound/sound_adlib.cpp | 2246 | ||||
-rw-r--r-- | engines/kyra/sound/sound_digital_mr.cpp | 298 | ||||
-rw-r--r-- | engines/kyra/sound/sound_digital_mr.h | 4 | ||||
-rw-r--r-- | engines/kyra/sound/sound_midi.cpp | 409 |
11 files changed, 3137 insertions, 2952 deletions
diff --git a/engines/kyra/module.mk b/engines/kyra/module.mk index 0f2634b703..a45c0ac2df 100644 --- a/engines/kyra/module.mk +++ b/engines/kyra/module.mk @@ -64,12 +64,15 @@ MODULE_OBJS := \ sound/sound_amiga_lok.o \ sound/sound_digital_mr.o \ sound/sound_midi.o \ - sound/sound_pcspk.o \ sound/sound_pc98_lok.o \ sound/sound_pc98_v2.o \ sound/sound_towns_lok.o \ sound/sound.o \ sound/sound_lok.o \ + sound/drivers/adlib.o \ + sound/drivers/audstream.o \ + sound/drivers/midi.o \ + sound/drivers/pcspeaker.o \ text/text.o \ text/text_lok.o \ text/text_hof.o \ 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/sound_pcspk.cpp b/engines/kyra/sound/drivers/pcspeaker.cpp index 110addefd8..110addefd8 100644 --- a/engines/kyra/sound/sound_pcspk.cpp +++ b/engines/kyra/sound/drivers/pcspeaker.cpp diff --git a/engines/kyra/sound/sound_adlib.cpp b/engines/kyra/sound/sound_adlib.cpp index 7a87e71982..599221c3d9 100644 --- a/engines/kyra/sound/sound_adlib.cpp +++ b/engines/kyra/sound/sound_adlib.cpp @@ -38,2258 +38,14 @@ #include "kyra/sound/sound_intern.h" -#include "kyra/resource/resource.h" +#include "kyra/sound/drivers/adlib.h" #include "common/system.h" -#include "common/mutex.h" #include "common/config-manager.h" -#include "audio/fmopl.h" - -// Basic AdLib Programming: -// http://www.gamedev.net/reference/articles/article446.asp - -#define CALLBACKS_PER_SECOND 72 - -namespace Audio { -class Mixer; -} 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; -}; - -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 } -}; - -#pragma mark - - // Kyra 1 sound triggers. Most noticeably, these are used towards the end of // the game, in the castle, to cycle between different songs. The same music is // used in other places throughout the game, but the player is less likely to diff --git a/engines/kyra/sound/sound_digital_mr.cpp b/engines/kyra/sound/sound_digital_mr.cpp index cf94eb0ff7..ffcfcd128d 100644 --- a/engines/kyra/sound/sound_digital_mr.cpp +++ b/engines/kyra/sound/sound_digital_mr.cpp @@ -25,7 +25,6 @@ #include "kyra/engine/kyra_mr.h" #include "audio/audiostream.h" - #include "audio/decoders/mp3.h" #include "audio/decoders/vorbis.h" #include "audio/decoders/flac.h" @@ -103,294 +102,6 @@ int KyraAudioStream::readBuffer(int16 *buffer, const int numSamples) { return samplesRead; } -// 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); -} - -#pragma mark - - SoundDigital::SoundDigital(KyraEngine_MR *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) { for (uint i = 0; i < ARRAYSIZE(_sounds); ++i) _sounds[i].stream = 0; @@ -518,13 +229,7 @@ void SoundDigital::beginFadeOut(int channel, int ticks) { // static res -namespace { - -Audio::SeekableAudioStream *makeAUDStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) { - return new AUDStream(stream); -} - -} // end of anonymous namespace +Audio::SeekableAudioStream *makeAUDStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse); const SoundDigital::AudioCodecs SoundDigital::_supportedCodecs[] = { #ifdef USE_FLAC @@ -540,5 +245,4 @@ const SoundDigital::AudioCodecs SoundDigital::_supportedCodecs[] = { { 0, 0 } }; - } // End of namespace Kyra diff --git a/engines/kyra/sound/sound_digital_mr.h b/engines/kyra/sound/sound_digital_mr.h index 271dde6a21..1aece2da07 100644 --- a/engines/kyra/sound/sound_digital_mr.h +++ b/engines/kyra/sound/sound_digital_mr.h @@ -20,8 +20,8 @@ * */ -#ifndef KYRA_SOUND_DIGITAL_H -#define KYRA_SOUND_DIGITAL_H +#ifndef KYRA_SOUND_DIGITAL_MR_H +#define KYRA_SOUND_DIGITAL_MR_H #include "audio/mixer.h" diff --git a/engines/kyra/sound/sound_midi.cpp b/engines/kyra/sound/sound_midi.cpp index c0cf6c1b16..2390b75983 100644 --- a/engines/kyra/sound/sound_midi.cpp +++ b/engines/kyra/sound/sound_midi.cpp @@ -20,7 +20,8 @@ * */ -#include "kyra/sound/sound_intern.h" +#include "kyra/sound/drivers/midi.h" + #include "kyra/resource/resource.h" #include "common/system.h" @@ -31,412 +32,6 @@ 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]; -}; - -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; - } - } - } -} - -#pragma mark - - SoundMidiPC::SoundMidiPC(KyraEngine_v1 *vm, Audio::Mixer *mixer, MidiDriver *driver, kType type) : Sound(vm, mixer) { _driver = driver; _output = 0; |