aboutsummaryrefslogtreecommitdiff
path: root/engines/kyra/sound_adlib.cpp
diff options
context:
space:
mode:
authorWillem Jan Palenstijn2013-04-18 23:35:23 +0200
committerWillem Jan Palenstijn2013-05-08 20:40:58 +0200
commit9c2341678ef4984bf92b3878295250faf980b066 (patch)
tree2fb4805e05e16b9924e80c9947e6bad723b28c4b /engines/kyra/sound_adlib.cpp
parent8172d679df5148a4a32f46074b20cb6caf91844f (diff)
parenta5f4ff36ffc386d48f2da49387a9655ce9295a4d (diff)
downloadscummvm-rg350-9c2341678ef4984bf92b3878295250faf980b066.tar.gz
scummvm-rg350-9c2341678ef4984bf92b3878295250faf980b066.tar.bz2
scummvm-rg350-9c2341678ef4984bf92b3878295250faf980b066.zip
Merge branch 'master'
Diffstat (limited to 'engines/kyra/sound_adlib.cpp')
-rw-r--r--engines/kyra/sound_adlib.cpp700
1 files changed, 367 insertions, 333 deletions
diff --git a/engines/kyra/sound_adlib.cpp b/engines/kyra/sound_adlib.cpp
index 8976eba99c..668e662413 100644
--- a/engines/kyra/sound_adlib.cpp
+++ b/engines/kyra/sound_adlib.cpp
@@ -42,6 +42,7 @@
#include "common/system.h"
#include "common/mutex.h"
+#include "common/config-manager.h"
#include "audio/mixer.h"
#include "audio/fmopl.h"
@@ -56,10 +57,17 @@ namespace Kyra {
class AdLibDriver : public Audio::AudioStream {
public:
- AdLibDriver(Audio::Mixer *mixer, bool v2);
+ AdLibDriver(Audio::Mixer *mixer, int version);
~AdLibDriver();
- int callback(int opcode, ...);
+ void initDriver();
+ void setSoundData(uint8 *data);
+ void queueTrack(int track, int volume);
+ bool isChannelPlaying(int channel) const;
+ void stopAllChannels();
+ int getSoundTrigger() const { return _soundTrigger; }
+ void resetSoundTrigger() { _soundTrigger = 0; }
+
void callback();
// AudioStream API
@@ -92,40 +100,14 @@ public:
void setSyncJumpMask(uint16 mask) { _syncJumpMask = mask; }
-private:
- struct OpcodeEntry {
- typedef int (AdLibDriver::*DriverOpcode)(va_list &list);
- DriverOpcode function;
- const char *name;
- };
-
- void setupOpcodeList();
- const OpcodeEntry *_opcodeList;
- int _opcodesEntries;
-
- int snd_ret0x100(va_list &list);
- int snd_ret0x1983(va_list &list);
- int snd_initDriver(va_list &list);
- int snd_deinitDriver(va_list &list);
- int snd_setSoundData(va_list &list);
- int snd_unkOpcode1(va_list &list);
- int snd_startSong(va_list &list);
- int snd_isChannelPlaying(va_list &list);
- int snd_stopChannel(va_list &list);
- int snd_readByte(va_list &list);
- int snd_writeByte(va_list &list);
- int snd_getSoundTrigger(va_list &list);
- int snd_unkOpcode4(va_list &list);
- int snd_dummy(va_list &list);
- int snd_getNullvar4(va_list &list);
- int snd_setNullvar3(va_list &list);
- int snd_setFlag(va_list &list);
- int snd_clearFlag(va_list &list);
+ void setMusicVolume(uint8 volume);
+ void setSfxVolume(uint8 volume);
+private:
// These variables have not yet been named, but some of them are partly
// known nevertheless:
//
- // unk16 - Sound-related. Possibly some sort of pitch bend.
+ // 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()
@@ -194,7 +176,8 @@ private:
uint16 offset;
uint8 tempoReset;
uint8 rawNote;
- int8 unk16;
+ int8 pitchBend;
+ uint8 volumeModifier;
};
void primaryEffect1(Channel &channel);
@@ -241,7 +224,7 @@ private:
}
uint8 *getInstrument(int instrumentId) {
- return _soundData + READ_LE_UINT16(_soundData + (_v2 ? 1000 : 500) + 2 * instrumentId);
+ return getProgram(_numPrograms + instrumentId);
}
void setupPrograms();
@@ -297,7 +280,7 @@ private:
int updateCallback38(uint8 *&dataptr, Channel &channel, uint8 value);
int updateCallback39(uint8 *&dataptr, Channel &channel, uint8 value);
int update_removePrimaryEffect2(uint8 *&dataptr, Channel &channel, uint8 value);
- int updateCallback41(uint8 *&dataptr, Channel &channel, uint8 value);
+ int update_pitchBend(uint8 *&dataptr, Channel &channel, uint8 value);
int update_resetToGlobalTempo(uint8 *&dataptr, Channel &channel, uint8 value);
int update_nop(uint8 *&dataptr, Channel &channel, uint8 value);
int update_setDurationRandomness(uint8 *&dataptr, Channel &channel, uint8 value);
@@ -335,7 +318,7 @@ private:
// _unkValue18 - Unknown. Rhythm section volume?
// _unkValue19 - Unknown. Rhythm section volume?
// _unkValue20 - Unknown. Rhythm section volume?
- // _unkTable[] - Probably frequences for the 12-tone scale.
+ // _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[]
@@ -346,11 +329,8 @@ private:
int32 _samplesTillCallback;
int32 _samplesTillCallbackRemainder;
- int _lastProcessed;
- int8 _flagTrigger;
int _curChannel;
uint8 _soundTrigger;
- int _soundsPlaying;
uint16 _rnd;
@@ -375,12 +355,28 @@ private:
uint8 _unkValue19;
uint8 _unkValue20;
- int _flags;
FM_OPL *_adlib;
uint8 *_soundData;
- uint8 _soundIdTable[0x10];
+ 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;
@@ -393,12 +389,12 @@ private:
const uint8 *_tablePtr2;
static const uint8 _regOffset[];
- static const uint16 _unkTable[];
+ 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 _unkTables[][32];
+ static const uint8 _pitchBendTables[][32];
uint16 _syncJumpMask;
@@ -406,18 +402,20 @@ private:
Audio::Mixer *_mixer;
Audio::SoundHandle _soundHandle;
- bool _v2;
+ uint8 _musicVolume, _sfxVolume;
+
+ int _numPrograms;
+ int _version;
};
-AdLibDriver::AdLibDriver(Audio::Mixer *mixer, bool v2) {
- setupOpcodeList();
+AdLibDriver::AdLibDriver(Audio::Mixer *mixer, int version) {
setupParserOpcodeTable();
- _v2 = v2;
+ _version = version;
+ _numPrograms = (_version == 1) ? 150 : ((_version == 4) ? 500 : 250);
_mixer = mixer;
- _flags = 0;
_adlib = makeAdLibOPL(getRate());
assert(_adlib);
@@ -426,12 +424,12 @@ AdLibDriver::AdLibDriver(Audio::Mixer *mixer, bool v2) {
_vibratoAndAMDepthBits = _curRegOffset = 0;
- _lastProcessed = _flagTrigger = _curChannel = _rhythmSectionBits = 0;
- _soundsPlaying = 0;
+ _curChannel = _rhythmSectionBits = 0;
_rnd = 0x1234;
_tempo = 0;
_soundTrigger = 0;
+ _programStartTimeout = 0;
_callbackTimer = 0xFF;
_unkValue1 = _unkValue2 = _unkValue4 = _unkValue5 = 0;
@@ -441,14 +439,7 @@ AdLibDriver::AdLibDriver(Audio::Mixer *mixer, bool v2) {
_tablePtr1 = _tablePtr2 = 0;
- // HACK: We use MusicSoundType here for now so we can adjust the volume in the launcher dialog.
- // This affects SFX too, but if we want to support different volumes for SFX and music we would
- // have to change our player implementation, currently we setup the volume for an AdLib channel
- // in AdLibDriver::adjustVolume, so if that would be called, we would have to know if the channel
- // is used by SFX or music, and then adjust the volume accordingly. Since Kyrandia 2 supports
- // different volumes for SFX and music, looking at the disasm and checking how the original does it
- // would be a good idea.
- _mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+ _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
_samplesPerCallback = getRate() / CALLBACKS_PER_SECOND;
_samplesPerCallbackRemainder = getRate() % CALLBACKS_PER_SECOND;
@@ -456,6 +447,14 @@ AdLibDriver::AdLibDriver(Audio::Mixer *mixer, bool v2) {
_samplesTillCallbackRemainder = 0;
_syncJumpMask = 0;
+
+ _musicVolume = 0;
+ _sfxVolume = 0;
+
+ _sfxPointer = 0;
+
+ _programQueueStart = _programQueueEnd = 0;
+ _retrySounds = false;
}
AdLibDriver::~AdLibDriver() {
@@ -464,100 +463,110 @@ AdLibDriver::~AdLibDriver() {
_adlib = 0;
}
-int AdLibDriver::callback(int opcode, ...) {
+void AdLibDriver::setMusicVolume(uint8 volume) {
Common::StackLock lock(_mutex);
- if (opcode >= _opcodesEntries || opcode < 0) {
- warning("AdLibDriver: calling unknown opcode '%d'", opcode);
- return 0;
+
+ _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));
}
- debugC(9, kDebugLevelSound, "Calling opcode '%s' (%d)", _opcodeList[opcode].name, opcode);
+ // For now we use the music volume for both sfx and music in Kyra1 and EoB
+ if (_version < 4) {
+ _sfxVolume = volume;
- va_list args;
- va_start(args, opcode);
- int returnValue = (this->*(_opcodeList[opcode].function))(args);
- va_end(args);
- return returnValue;
-}
+ for (uint i = 6; i < 9; ++i) {
+ Channel &chan = _channels[i];
+ chan.volumeModifier = volume;
-// Opcodes
+ const uint8 regOffset = _regOffset[i];
-int AdLibDriver::snd_ret0x100(va_list &list) {
- return 0x100;
+ // Level Key Scaling / Total Level
+ writeOPL(0x40 + regOffset, calculateOpLevel1(chan));
+ writeOPL(0x43 + regOffset, calculateOpLevel2(chan));
+ }
+ }
}
-int AdLibDriver::snd_ret0x1983(va_list &list) {
- return 0x1983;
-}
+void AdLibDriver::setSfxVolume(uint8 volume) {
+ // We only support sfx volume in version 4 games.
+ if (_version < 4)
+ return;
-int AdLibDriver::snd_initDriver(va_list &list) {
- _lastProcessed = _soundsPlaying = 0;
- resetAdLibState();
- return 0;
+ 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));
+ }
}
-int AdLibDriver::snd_deinitDriver(va_list &list) {
+void AdLibDriver::initDriver() {
+ Common::StackLock lock(_mutex);
resetAdLibState();
- return 0;
}
-int AdLibDriver::snd_setSoundData(va_list &list) {
+void AdLibDriver::setSoundData(uint8 *data) {
+ 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 = 0;
+ _soundData = _sfxPointer = 0;
}
- _soundData = va_arg(list, uint8 *);
- return 0;
-}
-int AdLibDriver::snd_unkOpcode1(va_list &list) {
- warning("unimplemented snd_unkOpcode1");
- return 0;
+ _soundData = data;
}
-int AdLibDriver::snd_startSong(va_list &list) {
- int songId = va_arg(list, int);
- _flags |= 8;
- _flagTrigger = 1;
+void AdLibDriver::queueTrack(int track, int volume) {
+ Common::StackLock lock(_mutex);
- uint8 *ptr = getProgram(songId);
- assert(ptr);
- uint8 chan = *ptr;
+ uint8 *trackData = getProgram(track);
+ if (!trackData)
+ return;
- if ((songId << 1) != 0) {
- if (chan == 9) {
- if (_flags & 2)
- return 0;
- } else {
- if (_flags & 1)
- return 0;
- }
+ // 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;
}
- _soundIdTable[_soundsPlaying++] = songId;
- _soundsPlaying &= 0x0F;
-
- return 0;
+ _programQueue[_programQueueEnd] = QueueEntry(trackData, track, volume);
+ _programQueueEnd = (_programQueueEnd + 1) & 15;
}
-int AdLibDriver::snd_isChannelPlaying(va_list &list) {
- int channel = va_arg(list, int);
+bool AdLibDriver::isChannelPlaying(int channel) const {
+ Common::StackLock lock(_mutex);
+
assert(channel >= 0 && channel <= 9);
- return (_channels[channel].dataptr != 0) ? 1 : 0;
+ return (_channels[channel].dataptr != 0);
}
-int AdLibDriver::snd_stopChannel(va_list &list) {
- int channel = va_arg(list, int);
-
- int maxChannel;
- if (channel < 0) {
- channel = 0;
- maxChannel = 9;
- } else {
- maxChannel = channel;
- }
+void AdLibDriver::stopAllChannels() {
+ Common::StackLock lock(_mutex);
- for (; channel <= maxChannel; ++channel) {
+ for (int channel = 0; channel <= 9; ++channel) {
_curChannel = channel;
Channel &chan = _channels[_curChannel];
@@ -567,71 +576,17 @@ int AdLibDriver::snd_stopChannel(va_list &list) {
if (channel != 9)
noteOff(chan);
}
-
- return 0;
-}
-
-int AdLibDriver::snd_readByte(va_list &list) {
- int a = va_arg(list, int);
- int b = va_arg(list, int);
- uint8 *ptr = getProgram(a) + b;
- assert(ptr);
- return *ptr;
-}
-
-int AdLibDriver::snd_writeByte(va_list &list) {
- int a = va_arg(list, int);
- int b = va_arg(list, int);
- uint8 value = va_arg(list, int);
- uint8 *ptr = getProgram(a) + b;
- assert(ptr);
- SWAP(*ptr, value);
- return value;
-}
-
-int AdLibDriver::snd_getSoundTrigger(va_list &list) {
- return _soundTrigger;
-}
-
-int AdLibDriver::snd_unkOpcode4(va_list &list) {
- warning("unimplemented snd_unkOpcode4");
- return 0;
-}
-
-int AdLibDriver::snd_dummy(va_list &list) {
- return 0;
-}
-
-int AdLibDriver::snd_getNullvar4(va_list &list) {
- warning("unimplemented snd_getNullvar4");
- return 0;
-}
-
-int AdLibDriver::snd_setNullvar3(va_list &list) {
- warning("unimplemented snd_setNullvar3");
- return 0;
-}
-
-int AdLibDriver::snd_setFlag(va_list &list) {
- int oldFlags = _flags;
- _flags |= va_arg(list, int);
- return oldFlags;
-}
-
-int AdLibDriver::snd_clearFlag(va_list &list) {
- int oldFlags = _flags;
- _flags &= ~(va_arg(list, int));
- return oldFlags;
+ _retrySounds = false;
}
// timer callback
void AdLibDriver::callback() {
Common::StackLock lock(_mutex);
- --_flagTrigger;
- if (_flagTrigger < 0)
- _flags &= ~8;
- setupPrograms();
+ if (_programStartTimeout)
+ --_programStartTimeout;
+ else
+ setupPrograms();
executePrograms();
uint8 temp = _callbackTimer;
@@ -645,33 +600,99 @@ void AdLibDriver::callback() {
}
void AdLibDriver::setupPrograms() {
- while (_lastProcessed != _soundsPlaying) {
- uint8 *ptr = getProgram(_soundIdTable[_lastProcessed]);
- uint8 chan = *ptr++;
- uint8 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;
- unkOutput2(chan);
- }
+ // 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;
- // What we have set up now is, probably, the controlling
- // channel for the sound. It is assumed that this program will
- // set up all the other channels it needs, clearing their locks
- // along the way.
+ retrySound = QueueEntry();
+ }
- ++_lastProcessed;
- _lastProcessed &= 0x0F;
+ 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;
+ }
}
}
@@ -984,20 +1005,20 @@ void AdLibDriver::setupNote(uint8 rawNote, Channel &channel, bool flag) {
// 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 = _unkTable[note] + channel.baseFreq;
+ uint16 freq = _freqTable[note] + channel.baseFreq;
// When called from callback 41, the behavior is slightly different:
- // We adjust the frequency, even when channel.unk16 is 0.
+ // We adjust the frequency, even when channel.pitchBend is 0.
- if (channel.unk16 || flag) {
+ if (channel.pitchBend || flag) {
const uint8 *table;
- if (channel.unk16 >= 0) {
- table = _unkTables[(channel.rawNote & 0x0F) + 2];
- freq += table[channel.unk16];
+ if (channel.pitchBend >= 0) {
+ table = _pitchBendTables[(channel.rawNote & 0x0F) + 2];
+ freq += table[channel.pitchBend];
} else {
- table = _unkTables[channel.rawNote & 0x0F];
- freq -= table[-channel.unk16];
+ table = _pitchBendTables[channel.rawNote & 0x0F];
+ freq -= table[-channel.pitchBend];
}
}
@@ -1273,9 +1294,21 @@ uint8 AdLibDriver::calculateOpLevel1(Channel &channel) {
if (channel.twoChan) {
value += channel.opExtraLevel1;
value += channel.opExtraLevel2;
- value += channel.opExtraLevel3;
+
+ 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);
@@ -1286,7 +1319,19 @@ uint8 AdLibDriver::calculateOpLevel2(Channel &channel) {
value += channel.opExtraLevel1;
value += channel.opExtraLevel2;
- value += channel.opExtraLevel3;
+
+ 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
@@ -1323,14 +1368,22 @@ int AdLibDriver::update_setupProgram(uint8 *&dataptr, Channel &channel, uint8 va
Channel &channel2 = _channels[chan];
if (priority >= channel2.priority) {
- _flagTrigger = 1;
- _flags |= 8;
+ // 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);
}
@@ -1345,7 +1398,10 @@ int AdLibDriver::update_setNoteSpacing(uint8 *&dataptr, Channel &channel, uint8
int AdLibDriver::update_jump(uint8 *&dataptr, Channel &channel, uint8 value) {
--dataptr;
int16 add = READ_LE_UINT16(dataptr); dataptr += 2;
- dataptr += add;
+ if (_version == 1)
+ dataptr = _soundData + add - 191;
+ else
+ dataptr += add;
if (_syncJumpMask & (1 << (&channel - _channels)))
channel.lock = true;
return 0;
@@ -1355,7 +1411,10 @@ int AdLibDriver::update_jumpToSubroutine(uint8 *&dataptr, Channel &channel, uint
--dataptr;
int16 add = READ_LE_UINT16(dataptr); dataptr += 2;
channel.dataptrStack[channel.dataptrStackPos++] = dataptr;
- dataptr += add;
+ if (_version < 3)
+ dataptr = _soundData + add - 191;
+ else
+ dataptr += add;
return 0;
}
@@ -1405,7 +1464,21 @@ int AdLibDriver::update_setupSecondaryEffect1(uint8 *&dataptr, Channel &channel,
channel.unk19 = value;
channel.unk20 = channel.unk21 = *dataptr++;
channel.unk22 = *dataptr++;
- channel.offset = READ_LE_UINT16(dataptr); dataptr += 2;
+ // 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;
}
@@ -1647,8 +1720,8 @@ int AdLibDriver::update_removePrimaryEffect2(uint8 *&dataptr, Channel &channel,
return 0;
}
-int AdLibDriver::updateCallback41(uint8 *&dataptr, Channel &channel, uint8 value) {
- channel.unk16 = value;
+int AdLibDriver::update_pitchBend(uint8 *&dataptr, Channel &channel, uint8 value) {
+ channel.pitchBend = value;
setupNote(channel.rawNote, channel, true);
return 0;
}
@@ -1911,32 +1984,6 @@ int AdLibDriver::updateCallback56(uint8 *&dataptr, Channel &channel, uint8 value
#define COMMAND(x) { &AdLibDriver::x, #x }
-void AdLibDriver::setupOpcodeList() {
- static const OpcodeEntry opcodeList[] = {
- COMMAND(snd_ret0x100),
- COMMAND(snd_ret0x1983),
- COMMAND(snd_initDriver),
- COMMAND(snd_deinitDriver),
- COMMAND(snd_setSoundData),
- COMMAND(snd_unkOpcode1),
- COMMAND(snd_startSong),
- COMMAND(snd_isChannelPlaying),
- COMMAND(snd_stopChannel),
- COMMAND(snd_readByte),
- COMMAND(snd_writeByte),
- COMMAND(snd_getSoundTrigger),
- COMMAND(snd_unkOpcode4),
- COMMAND(snd_dummy),
- COMMAND(snd_getNullvar4),
- COMMAND(snd_setNullvar3),
- COMMAND(snd_setFlag),
- COMMAND(snd_clearFlag)
- };
-
- _opcodeList = opcodeList;
- _opcodesEntries = ARRAYSIZE(opcodeList);
-}
-
void AdLibDriver::setupParserOpcodeTable() {
static const ParserOpcode parserOpcodeTable[] = {
// 0
@@ -2025,7 +2072,7 @@ void AdLibDriver::setupParserOpcodeTable() {
// 56
COMMAND(update_stopChannel),
- COMMAND(updateCallback41),
+ COMMAND(update_pitchBend),
COMMAND(update_resetToGlobalTempo),
COMMAND(update_nop),
@@ -2066,11 +2113,10 @@ const uint8 AdLibDriver::_regOffset[] = {
0x12
};
-// Given the size of this table, and the range of its values, it's probably 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.
+//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::_unkTable[] = {
+const uint16 AdLibDriver::_freqTable[] = {
0x0134, 0x0147, 0x015A, 0x016F, 0x0184, 0x019C, 0x01B4, 0x01CE, 0x01E9,
0x0207, 0x0225, 0x0246
};
@@ -2148,13 +2194,11 @@ const uint8 AdLibDriver::_unkTable2_3[] = {
};
// This table is used to modify the frequency of the notes, depending on the
-// note value and unk16. In theory, we could very well try to access memory
-// outside this table, but in reality that probably won't happen.
+// 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.
//
-// This could be some sort of pitch bend, but I have yet to see it used for
-// anything so it's hard to say.
-const uint8 AdLibDriver::_unkTables[][32] = {
+const uint8 AdLibDriver::_pitchBendTables[][32] = {
// 0
{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
@@ -2243,21 +2287,36 @@ const int SoundAdLibPC::_kyra1NumSoundTriggers = ARRAYSIZE(SoundAdLibPC::_kyra1S
SoundAdLibPC::SoundAdLibPC(KyraEngine_v1 *vm, Audio::Mixer *mixer)
: Sound(vm, mixer), _driver(0), _trackEntries(), _soundDataPtr(0) {
memset(_trackEntries, 0, sizeof(_trackEntries));
- _v2 = (_vm->game() == GI_KYRA2) || (_vm->game() == GI_LOL && !_vm->gameFlags().isDemo);
- _driver = new AdLibDriver(mixer, _v2);
- assert(_driver);
+ _soundTriggers = 0;
+ _numSoundTriggers = 0;
_sfxPlayingSound = -1;
_soundFileLoaded.clear();
- if (_v2) {
- // TODO: Figure out if Kyra 2 uses sound triggers at all.
- _soundTriggers = 0;
- _numSoundTriggers = 0;
- } else {
+ switch (vm->game()) {
+ case GI_LOL:
+ _version = _vm->gameFlags().isDemo ? 3 : 4;
+ break;
+ case GI_KYRA2:
+ _version = 4;
+ break;
+ case GI_KYRA1:
+ _version = 3;
_soundTriggers = _kyra1SoundTriggers;
_numSoundTriggers = _kyra1NumSoundTriggers;
+ break;
+ case GI_EOB2:
+ _version = 2;
+ break;
+ case GI_EOB1:
+ _version = 1;
+ break;
+ default:
+ break;
}
+
+ _driver = new AdLibDriver(mixer, _version);
+ assert(_driver);
}
SoundAdLibPC::~SoundAdLibPC() {
@@ -2266,13 +2325,12 @@ SoundAdLibPC::~SoundAdLibPC() {
}
bool SoundAdLibPC::init() {
- _driver->callback(2);
- _driver->callback(16, int(4));
+ _driver->initDriver();
return true;
}
void SoundAdLibPC::process() {
- uint8 trigger = _driver->callback(11);
+ int trigger = _driver->getSoundTrigger();
if (trigger < _numSoundTriggers) {
int soundId = _soundTriggers[trigger];
@@ -2285,6 +2343,23 @@ void SoundAdLibPC::process() {
}
}
+void SoundAdLibPC::updateVolumeSettings() {
+ bool mute = false;
+ if (ConfMan.hasKey("mute"))
+ mute = ConfMan.getBool("mute");
+
+ int newMusicVolume = mute ? 0 : ConfMan.getInt("music_volume");
+ //newMusicVolume = (newMusicVolume * 145) / Audio::Mixer::kMaxMixerVolume + 110;
+ newMusicVolume = CLIP(newMusicVolume, 0, 255);
+
+ int newSfxVolume = mute ? 0 : ConfMan.getInt("sfx_volume");
+ //newSfxVolume = (newSfxVolume * 200) / Audio::Mixer::kMaxMixerVolume + 55;
+ newSfxVolume = CLIP(newSfxVolume, 0, 255);
+
+ _driver->setMusicVolume(newMusicVolume);
+ _driver->setSfxVolume(newSfxVolume);
+}
+
void SoundAdLibPC::playTrack(uint8 track) {
if (_musicEnabled) {
// WORKAROUND: There is a bug in the Kyra 1 "Pool of Sorrow"
@@ -2296,90 +2371,49 @@ void SoundAdLibPC::playTrack(uint8 track) {
_driver->setSyncJumpMask(0x000F);
else
_driver->setSyncJumpMask(0);
- play(track);
+ play(track, 0xff);
}
}
void SoundAdLibPC::haltTrack() {
- playSoundEffect(0);
- playSoundEffect(0);
+ play(0, 0);
+ play(0, 0);
//_vm->_system->delayMillis(3 * 60);
}
bool SoundAdLibPC::isPlaying() const {
- return _driver->callback(7, int(0)) != 0;
+ return _driver->isChannelPlaying(0);
}
-void SoundAdLibPC::playSoundEffect(uint8 track) {
+void SoundAdLibPC::playSoundEffect(uint8 track, uint8 volume) {
if (_sfxEnabled)
- play(track);
+ play(track, volume);
}
-void SoundAdLibPC::play(uint8 track) {
+void SoundAdLibPC::play(uint8 track, uint8 volume) {
uint16 soundId = 0;
- if (_v2)
+ if (_version == 4)
soundId = READ_LE_UINT16(&_trackEntries[track<<1]);
else
soundId = _trackEntries[track];
- if ((soundId == 0xFFFF && _v2) || (soundId == 0xFF && !_v2) || !_soundDataPtr)
+ if ((soundId == 0xFFFF && _version == 4) || (soundId == 0xFF && _version < 4) || !_soundDataPtr)
return;
- // HACK: Since we might call this when the engines is paused (on game load via GMM)
- // we must unpause the engine here, so this will work properly
-
- int pauseCount = 0;
- while (_vm->isPaused()) {
- ++pauseCount;
- _vm->pauseEngine(false);
- }
-
- while ((_driver->callback(16, 0) & 8)) {
- // We call the system delay and not the game delay to avoid concurrency issues.
- _vm->_system->delayMillis(10);
- }
-
- while (pauseCount--)
- _vm->pauseEngine(true);
-
- if (_sfxPlayingSound != -1) {
- // Restore the sounds's normal values.
- _driver->callback(10, _sfxPlayingSound, int(1), int(_sfxPriority));
- _driver->callback(10, _sfxPlayingSound, int(3), int(_sfxFourthByteOfSong));
- _sfxPlayingSound = -1;
- }
+ _driver->queueTrack(soundId, volume);
+}
- int chan = _driver->callback(9, soundId, int(0));
-
- if (chan != 9) {
- _sfxPlayingSound = soundId;
- _sfxPriority = _driver->callback(9, soundId, int(1));
- _sfxFourthByteOfSong = _driver->callback(9, soundId, int(3));
-
- // In the cases I've seen, the mysterious fourth byte has been
- // the parameter for the update_setExtraLevel3() callback.
- //
- // The extra level is part of the channels "total level", which
- // is a six-bit value where larger values means softer volume.
- //
- // So what seems to be happening here is that sounds which are
- // started by this function are given a slightly lower priority
- // and a slightly higher (i.e. softer) extra level 3 than they
- // would have if they were started from anywhere else. Strange.
-
- int newVal = ((((-_sfxFourthByteOfSong) + 63) * 0xFF) >> 8) & 0xFF;
- newVal = -newVal + 63;
- _driver->callback(10, soundId, int(3), newVal);
- newVal = ((_sfxPriority * 0xFF) >> 8) & 0xFF;
- _driver->callback(10, soundId, int(1), newVal);
- }
+void SoundAdLibPC::beginFadeOut() {
+ play(_version > 2 ? 1 : 15, 0xff);
+}
- _driver->callback(6, soundId);
+int SoundAdLibPC::checkTrigger() {
+ return _driver->getSoundTrigger();
}
-void SoundAdLibPC::beginFadeOut() {
- playSoundEffect(1);
+void SoundAdLibPC::resetTrigger() {
+ _driver->resetSoundTrigger();
}
void SoundAdLibPC::loadSoundFile(uint file) {
@@ -2391,7 +2425,7 @@ void SoundAdLibPC::loadSoundFile(Common::String file) {
}
void SoundAdLibPC::internalLoadFile(Common::String file) {
- file += ".ADL";
+ file += ((_version == 1) ? ".DAT" : ".ADL");
if (_soundFileLoaded == file)
return;
@@ -2409,13 +2443,13 @@ void SoundAdLibPC::internalLoadFile(Common::String file) {
playSoundEffect(0);
playSoundEffect(0);
- _driver->callback(8, int(-1));
+ _driver->stopAllChannels();
_soundDataPtr = 0;
int soundDataSize = fileSize;
uint8 *p = fileData;
- if (_v2) {
+ if (_version == 4) {
memcpy(_trackEntries, p, 500);
p += 500;
soundDataSize -= 500;
@@ -2434,7 +2468,7 @@ void SoundAdLibPC::internalLoadFile(Common::String file) {
fileData = p = 0;
fileSize = 0;
- _driver->callback(4, _soundDataPtr);
+ _driver->setSoundData(_soundDataPtr);
_soundFileLoaded = file;
}