/* 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 "titanic/sound/sound_manager.h" #include "titanic/titanic.h" namespace Titanic { const uint SAMPLING_RATE = 22050; const uint LATENCY = 100; const uint CHANNELS_COUNT = 16; CSoundManager::CSoundManager() : _musicPercent(75.0), _speechPercent(75.0), _masterPercent(75.0), _parrotPercent(75.0), _handleCtr(1) { } uint CSoundManager::getModeVolume(int mode) { switch (mode) { case -1: return (uint)_masterPercent; case -2: return (uint)(_masterPercent * 30 / 100); case -3: return (uint)(_masterPercent * 15 / 100); default: return 0; } } /*------------------------------------------------------------------------*/ void QSoundManagerSounds::add(CWaveFile *waveFile, int iChannel, CEndTalkerFn endFn, TTtalker *talker) { push_back(new QSoundManagerSound(waveFile, iChannel, endFn, talker)); } void QSoundManagerSounds::flushChannel(int iChannel) { for (iterator i = begin(); i != end(); ++i) { QSoundManagerSound *item = *i; if (item->_iChannel == iChannel) { if (item->_endFn) item->_endFn(item->_talker); remove(item); delete item; break; } } } void QSoundManagerSounds::flushChannel(CWaveFile *waveFile, int iChannel) { for (iterator i = begin(); i != end(); ++i) { QSoundManagerSound *item = *i; if (item->_waveFile->isLoaded() && item->_iChannel == iChannel) { if (item->_endFn) item->_endFn(item->_talker); remove(item); delete item; break; } } } bool QSoundManagerSounds::contains(const CWaveFile *waveFile) const { for (const_iterator i = begin(); i != end(); ++i) { const QSoundManagerSound *item = *i; if (item->_waveFile == waveFile) return true; } return false; } /*------------------------------------------------------------------------*/ void QSoundManager::Slot::clear() { _waveFile = nullptr; _isTimed = false; _ticks = 0; _channel = -1; _handle = 0; _positioningMode = POSMODE_NONE; } /*------------------------------------------------------------------------*/ QSoundManager::QSoundManager(Audio::Mixer *mixer) : CSoundManager(), QMixer(mixer), _field18(0), _field1C(0) { _slots.resize(48); Common::fill(&_channelsVolume[0], &_channelsVolume[16], 0); Common::fill(&_channelsMode[0], &_channelsMode[16], 0); qsWaveMixInitEx(QMIXCONFIG(SAMPLING_RATE, CHANNELS_COUNT, LATENCY)); qsWaveMixActivate(true); qsWaveMixOpenChannel(0, QMIX_OPENALL); } QSoundManager::~QSoundManager() { // Close down the mixer qsWaveMixCloseSession(); } CWaveFile *QSoundManager::loadSound(const CString &name) { CWaveFile *waveFile = new CWaveFile(); // Try to load the specified sound if (!waveFile->loadSound(name)) { delete waveFile; return nullptr; } return waveFile; } CWaveFile *QSoundManager::loadSpeech(CDialogueFile *dialogueFile, int speechId) { CWaveFile *waveFile = new CWaveFile(); // Try to load the specified sound if (!waveFile->loadSpeech(dialogueFile, speechId)) { delete waveFile; return nullptr; } return waveFile; } CWaveFile *QSoundManager::loadMusic(const CString &name) { CWaveFile *waveFile = new CWaveFile(); // Try to load the specified sound if (!waveFile->loadMusic(name)) { delete waveFile; return nullptr; } return waveFile; } int QSoundManager::playSound(CWaveFile &waveFile, CProximity &prox) { int channel = -1; uint flags = QMIX_CLEARQUEUE; if (prox._priorSoundHandle >= 1) { // This sound should only be started after a prior one finishes, // so scan the slots for the specified sound for (uint idx = 0; idx < _slots.size(); ++idx) { if (_slots[idx]._handle == prox._priorSoundHandle) { channel = _slots[idx]._channel; flags = QMIX_QUEUEWAVE; break; } } } if (channel >= 0 || (channel = resetChannel(prox._channel)) != -1) { return playWave(&waveFile, channel, flags, prox); } return 0; } void QSoundManager::stopSound(int handle) { resetChannel(10); for (uint idx = 0; idx < _slots.size(); ++idx) { Slot &slot = _slots[idx]; if (slot._handle == handle) { qsWaveMixFlushChannel(slot._channel); _sounds.flushChannel(slot._channel); resetChannel(10); } } } void QSoundManager::stopChannel(int channel) { int endChannel; switch (channel) { case 0: case 3: endChannel = channel + 3; break; case 6: endChannel = 10; break; case 10: endChannel = 48; break; default: return; } for (; channel < endChannel; ++channel) { qsWaveMixFlushChannel(channel); _sounds.flushChannel(channel); } } void QSoundManager::setCanFree(int handle) { for (uint idx = 0; idx < _slots.size(); ++idx) { if (_slots[idx]._handle == handle) _slots[idx]._isTimed = true; } } void QSoundManager::stopAllChannels() { qsWaveMixFlushChannel(0, QMIX_OPENALL); for (int idx = 0; idx < 16; ++idx) _sounds.flushChannel(idx); resetChannel(10); } int QSoundManager::resetChannel(int iChannel) { int newChannel = -1; int channelStart = 10; int channelEnd = 16; if (iChannel != 10) { qsWaveMixFlushChannel(iChannel); _sounds.flushChannel(iChannel); channelStart = iChannel; channelEnd = iChannel + 1; } else { uint ticks = g_vm->_events->getTicksCount(); for (uint idx = 0; idx < _slots.size(); ++idx) { Slot &slot = _slots[idx]; if (slot._isTimed && slot._ticks && ticks > slot._ticks) { qsWaveMixFlushChannel(slot._channel); _sounds.flushChannel(slot._channel); } } } for (iChannel = channelStart; iChannel < channelEnd; ++iChannel) { if (qsWaveMixIsChannelDone(iChannel)) { // Scan through the slots, and reset any slot using the channel for (uint idx = 0; idx < _slots.size(); ++idx) { Slot &slot = _slots[idx]; if (slot._channel == iChannel) slot.clear(); } // Use the empty channel newChannel = iChannel; } } return newChannel; } void QSoundManager::setVolume(int handle, uint volume, uint seconds) { for (uint idx = 0; idx < _slots.size(); ++idx) { Slot &slot = _slots[idx]; if (slot._handle == handle) { _channelsVolume[slot._channel] = volume; updateVolume(slot._channel, seconds * 1000); if (volume) { uint ticks = g_vm->_events->getTicksCount() + seconds * 1000; if (!slot._ticks || ticks >= slot._ticks) slot._ticks = ticks; } else { slot._ticks = 0; } break; } } } void QSoundManager::setVectorPosition(int handle, double x, double y, double z, uint panRate) { for (uint idx = 0; idx < _slots.size(); ++idx) { Slot &slot = _slots[idx]; if (slot._handle == handle) { qsWaveMixSetPanRate(slot._channel, QMIX_USEONCE, panRate); qsWaveMixSetSourcePosition(slot._channel, QMIX_USEONCE, QSVECTOR(x, y, z)); break; } } } void QSoundManager::setPolarPosition(int handle, double range, double azimuth, double elevation, uint panRate) { for (uint idx = 0; idx < _slots.size(); ++idx) { Slot &slot = _slots[idx]; if (slot._handle == handle) { qsWaveMixSetPanRate(slot._channel, QMIX_USEONCE, panRate); qsWaveMixSetPolarPosition(slot._channel, QMIX_USEONCE, QSPOLAR(azimuth, range, elevation)); break; } } } bool QSoundManager::isActive(int handle) const { for (uint idx = 0; idx < _slots.size(); ++idx) { if (_slots[idx]._handle == handle) return true; } return false; } bool QSoundManager::isActive(const CWaveFile *waveFile) const { return _sounds.contains(waveFile); } void QSoundManager::waveMixPump() { qsWaveMixPump(); } uint QSoundManager::getLatency() const { return LATENCY; } void QSoundManager::setMusicPercent(double percent) { _musicPercent = percent; updateVolumes(); } void QSoundManager::setSpeechPercent(double percent) { _speechPercent = percent; updateVolumes(); } void QSoundManager::setMasterPercent(double percent) { _masterPercent = percent; updateVolumes(); } void QSoundManager::setParrotPercent(double percent) { _parrotPercent = percent; } void QSoundManager::setListenerPosition(double posX, double posY, double posZ, double directionX, double directionY, double directionZ, bool stopSounds) { if (stopSounds) { // Stop any running sounds for (uint idx = 0; idx < _slots.size(); ++idx) { if (_slots[idx]._positioningMode != 0) stopSound(_slots[idx]._handle); } } qsWaveMixSetListenerPosition(QSVECTOR(posX, posY, posZ)); qsWaveMixSetListenerOrientation(QSVECTOR(directionX, directionY, directionZ), QSVECTOR(0.0, 0.0, -1.0)); } int QSoundManager::playWave(CWaveFile *waveFile, int iChannel, uint flags, CProximity &prox) { if (!waveFile || !waveFile->isLoaded()) return 0; prox._channelVolume = CLIP(prox._channelVolume, 0, 100); prox._fieldC = CLIP(prox._fieldC, -100, 100); int slotIndex = findFreeSlot(); if (slotIndex == -1) return -1; switch (prox._positioningMode) { case POSMODE_POLAR: qsWaveMixSetPolarPosition(iChannel, 8, QSPOLAR(prox._azimuth, prox._range, prox._elevation)); qsWaveMixEnableChannel(iChannel, QMIX_CHANNEL_ELEVATION, true); qsWaveMixSetDistanceMapping(iChannel, 8, QMIX_DISTANCES(5.0, 3.0, 1.0)); break; case POSMODE_VECTOR: qsWaveMixSetSourcePosition(iChannel, 8, QSVECTOR(prox._posX, prox._posY, prox._posZ)); qsWaveMixEnableChannel(iChannel, QMIX_CHANNEL_ELEVATION, true); qsWaveMixSetDistanceMapping(iChannel, 8, QMIX_DISTANCES(5.0, 3.0, 1.0)); break; default: qsWaveMixEnableChannel(iChannel, QMIX_CHANNEL_ELEVATION, true); qsWaveMixSetPolarPosition(iChannel, 8, QSPOLAR(0.0, 1.0, 0.0)); break; } if (prox._frequencyMultiplier || prox._field1C != 1.875) { uint freq = (uint)(waveFile->getFrequency() * prox._frequencyMultiplier); qsWaveMixSetFrequency(iChannel, 8, freq); } _sounds.add(waveFile, iChannel, prox._endTalkerFn, prox._talker); QMIXPLAYPARAMS playParams; playParams.callback = soundFinished; playParams.dwUser = this; if (!qsWaveMixPlayEx(iChannel, flags, waveFile, prox._repeated ? -1 : 0, playParams)) { Slot &slot = _slots[slotIndex]; slot._handle = _handleCtr++; slot._channel = iChannel; slot._waveFile = waveFile; slot._positioningMode = prox._positioningMode; return slot._handle; } else { _sounds.flushChannel(waveFile, iChannel); if (prox._freeSoundFlag) delete waveFile; return 0; } } void QSoundManager::soundFreed(Audio::SoundHandle &handle) { qsWaveMixFreeWave(handle); } void QSoundManager::updateVolume(int channel, uint panRate) { uint volume = _channelsVolume[channel] * 327; switch (_channelsMode[channel]) { case 0: case 1: case 2: volume = (_speechPercent * volume) / 100; break; case 3: case 4: case 5: volume = (24525 * volume) / 100; break; case 6: case 7: case 8: case 9: volume = (_masterPercent * volume) / 100; break; default: break; } volume = (_musicPercent * volume) / 100; qsWaveMixSetPanRate(channel, 0, panRate); qsWaveMixSetVolume(channel, 0, volume); } void QSoundManager::updateVolumes() { for (uint idx = 0; idx < CHANNELS_COUNT; ++idx) updateVolume(idx, 250); } void QSoundManager::soundFinished(int iChannel, CWaveFile *waveFile, void *soundManager) { static_cast(soundManager)->_sounds.flushChannel(waveFile, iChannel); } int QSoundManager::findFreeSlot() { for (uint idx = 0; idx < _slots.size(); ++idx) { if (!_slots[idx]._waveFile) return idx; } return -1; } void QSoundManager::setChannelVolume(int iChannel, uint volume, uint mode) { _channelsVolume[iChannel] = volume; _channelsMode[iChannel] = mode; updateVolume(iChannel, 250); } } // End of namespace Titanic z