diff options
Diffstat (limited to 'engines/kyra/sound_adlib.cpp')
-rw-r--r-- | engines/kyra/sound_adlib.cpp | 528 |
1 files changed, 245 insertions, 283 deletions
diff --git a/engines/kyra/sound_adlib.cpp b/engines/kyra/sound_adlib.cpp index 8976eba99c..b04abea080 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" @@ -59,7 +60,13 @@ public: AdLibDriver(Audio::Mixer *mixer, bool v2); ~AdLibDriver(); - int callback(int opcode, ...); + void initDriver(); + void setSoundData(uint8 *data); + void queueTrack(int track); + bool isChannelPlaying(int channel) const; + void stopAllChannels(); + int getSoundTrigger() const { return _soundTrigger; } + void callback(); // AudioStream API @@ -92,36 +99,10 @@ 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: // @@ -195,6 +176,7 @@ private: uint8 tempoReset; uint8 rawNote; int8 unk16; + uint8 volumeModifier; }; void primaryEffect1(Channel &channel); @@ -346,11 +328,8 @@ private: int32 _samplesTillCallback; int32 _samplesTillCallbackRemainder; - int _lastProcessed; - int8 _flagTrigger; int _curChannel; uint8 _soundTrigger; - int _soundsPlaying; uint16 _rnd; @@ -375,12 +354,19 @@ private: uint8 _unkValue19; uint8 _unkValue20; - int _flags; FM_OPL *_adlib; uint8 *_soundData; - uint8 _soundIdTable[0x10]; + int _programStartTimeout; + uint8 *_programQueue[16]; + int _programQueueStart, _programQueueEnd; + + void adjustSfxData(uint8 *data); + uint8 *_sfxPointer; + int _sfxPriority; + int _sfxVelocity; + Channel _channels[10]; uint8 _vibratoAndAMDepthBits; @@ -406,18 +392,18 @@ private: Audio::Mixer *_mixer; Audio::SoundHandle _soundHandle; + uint8 _musicVolume, _sfxVolume; + bool _v2; }; AdLibDriver::AdLibDriver(Audio::Mixer *mixer, bool v2) { - setupOpcodeList(); setupParserOpcodeTable(); _v2 = v2; _mixer = mixer; - _flags = 0; _adlib = makeAdLibOPL(getRate()); assert(_adlib); @@ -426,12 +412,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 +427,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 +435,13 @@ AdLibDriver::AdLibDriver(Audio::Mixer *mixer, bool v2) { _samplesTillCallbackRemainder = 0; _syncJumpMask = 0; + + _musicVolume = 0; + _sfxVolume = 0; + + _sfxPointer = 0; + + _programQueueStart = _programQueueEnd = 0; } AdLibDriver::~AdLibDriver() { @@ -464,100 +450,108 @@ 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. + if (!_v2) { + _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 v2 games. + if (!_v2) + 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 = 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) { + 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; - } + if (_programQueueEnd == _programQueueStart && _programQueue[_programQueueEnd] != 0) { + warning("AdLibDriver: Program queue full, dropping track %d", track); + return; } - _soundIdTable[_soundsPlaying++] = songId; - _soundsPlaying &= 0x0F; - - return 0; + _programQueue[_programQueueEnd++] = trackData; + _programQueueEnd &= 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 +561,16 @@ 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; } // 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,36 +584,86 @@ 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]; + // Clear the queue entry + _programQueue[_programQueueStart] = 0; + _programQueueStart = (_programQueueStart + 1) & 15; + + // Adjust data in case we hit a sound effect. + adjustSfxData(ptr); + + 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]; - // 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. + if (priority >= channel.priority) { + initChannel(channel); + channel.priority = priority; + channel.dataptr = ptr; + channel.tempo = 0xFF; + channel.position = 0xFF; + channel.duration = 1; - ++_lastProcessed; - _lastProcessed &= 0x0F; + 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; } } +void AdLibDriver::adjustSfxData(uint8 *ptr) { + // 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]; + + // 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. + + // Adjust the values. + int newVal = ((((ptr[3]) + 63) * 0xFF) >> 8) & 0xFF; + ptr[3] = -newVal + 63; + ptr[1] = ((ptr[1] * 0xFF) >> 8) & 0xFF; +} + // A few words on opcode parsing and timing: // // First of all, We simulate a timer callback 72 times per second. Each timeout @@ -1273,9 +1262,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 +1287,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 +1336,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); } @@ -1911,32 +1932,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 @@ -2266,13 +2261,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 +2279,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" @@ -2301,13 +2312,13 @@ void SoundAdLibPC::playTrack(uint8 track) { } void SoundAdLibPC::haltTrack() { - playSoundEffect(0); - playSoundEffect(0); + play(0); + play(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) { @@ -2326,60 +2337,11 @@ void SoundAdLibPC::play(uint8 track) { if ((soundId == 0xFFFF && _v2) || (soundId == 0xFF && !_v2) || !_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; - } - - 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); - } - - _driver->callback(6, soundId); + _driver->queueTrack(soundId); } void SoundAdLibPC::beginFadeOut() { - playSoundEffect(1); + play(1); } void SoundAdLibPC::loadSoundFile(uint file) { @@ -2409,7 +2371,7 @@ void SoundAdLibPC::internalLoadFile(Common::String file) { playSoundEffect(0); playSoundEffect(0); - _driver->callback(8, int(-1)); + _driver->stopAllChannels(); _soundDataPtr = 0; int soundDataSize = fileSize; @@ -2434,7 +2396,7 @@ void SoundAdLibPC::internalLoadFile(Common::String file) { fileData = p = 0; fileSize = 0; - _driver->callback(4, _soundDataPtr); + _driver->setSoundData(_soundDataPtr); _soundFileLoaded = file; } |