/* 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/qmixer.h" #include "titanic/debugger.h" #include "common/system.h" namespace Titanic { QMixer::QMixer(Audio::Mixer *mixer) : _mixer(mixer) { } QMixer::~QMixer() { _channels.clear(); } bool QMixer::qsWaveMixInitEx(const QMIXCONFIG &config) { assert(_channels.empty()); assert(config.iChannels > 0 && config.iChannels < 256); _channels.resize(config.iChannels); return true; } void QMixer::qsWaveMixActivate(bool fActivate) { // Not currently implemented in ScummVM } int QMixer::qsWaveMixOpenChannel(int iChannel, QMixFlag mode) { // Not currently implemented in ScummVM return 0; } int QMixer::qsWaveMixEnableChannel(int iChannel, uint flags, bool enabled) { // Not currently implemented in ScummVM return 0; } void QMixer::qsWaveMixCloseSession() { _mixer->stopAll(); _channels.clear(); } void QMixer::qsWaveMixFreeWave(Audio::SoundHandle &handle) { _mixer->stopHandle(handle); } void QMixer::qsWaveMixFlushChannel(int iChannel, uint flags) { if (flags & QMIX_OPENALL) { // Ignore channel, and flush all the channels for (uint idx = 0; idx < _channels.size(); ++idx) qsWaveMixFlushChannel(idx, 0); } else { // Flush the specified channel Common::List::iterator i; Common::List &sounds = _channels[iChannel]._sounds; for (i = sounds.begin(); i != sounds.end(); ++i) _mixer->stopHandle((*i)._soundHandle); sounds.clear(); } } void QMixer::qsWaveMixSetPanRate(int iChannel, uint flags, uint rate) { ChannelEntry &channel = _channels[iChannel]; channel._panRate = rate; channel._volumeChangeStart = channel._volumeChangeEnd = 0; } void QMixer::qsWaveMixSetVolume(int iChannel, uint flags, uint volume) { ChannelEntry &channel = _channels[iChannel]; // QMixer volumes go from 0-32767, but we need to convert to 0-255 for ScummVM assert(volume <= 32767); byte newVolume = (volume >= 32700) ? 255 : volume * 255 / 32767; channel._volumeStart = channel._volume; channel._volumeEnd = newVolume; channel._volumeChangeStart = g_system->getMillis(); channel._volumeChangeEnd = channel._volumeChangeStart + channel._panRate; debugC(DEBUG_DETAILED, kDebugCore, "qsWaveMixSetPanRate vol=%d to %d, start=%u, end=%u", channel._volumeStart, channel._volumeEnd, channel._volumeChangeStart, channel._volumeChangeEnd); } void QMixer::qsWaveMixSetSourcePosition(int iChannel, uint flags, const QSVECTOR &position) { ChannelEntry &channel = _channels[iChannel]; // Flag whether distance should reset when a new sound is started channel._resetDistance = (flags & QMIX_USEONCE) != 0; // Currently, we only do a basic simulation of spatial positioning by // getting the distance, and proportionately reducing the volume the // further away the source is channel._distance = sqrt(position.x * position.x + position.y * position.y + position.z * position.z); } void QMixer::qsWaveMixSetPolarPosition(int iChannel, uint flags, const QSPOLAR &position) { ChannelEntry &channel = _channels[iChannel]; // Flag whether distance should reset when a new sound is started channel._resetDistance = (flags & QMIX_USEONCE) != 0; // Currently, we only do a basic simulation of spatial positioning by // getting the distance, and proportionately reducing the volume the // further away the source is channel._distance = position.range; } void QMixer::qsWaveMixSetListenerPosition(const QSVECTOR &position, uint flags) { // Not currently implemented in ScummVM } void QMixer::qsWaveMixSetListenerOrientation(const QSVECTOR &direction, const QSVECTOR &up, uint flags) { // Not currently implemented in ScummVM } void QMixer::qsWaveMixSetDistanceMapping(int iChannel, uint flags, const QMIX_DISTANCES &distances) { // Not currently implemented in ScummVM } void QMixer::qsWaveMixSetFrequency(int iChannel, uint flags, uint frequency) { // Not currently implemented in ScummVM } void QMixer::qsWaveMixSetSourceVelocity(int iChannel, uint flags, const QSVECTOR &velocity) { // Not currently implemented in ScummVM } int QMixer::qsWaveMixPlayEx(int iChannel, uint flags, CWaveFile *waveFile, int loops, const QMIXPLAYPARAMS ¶ms) { if (iChannel == -1) { // Find a free channel for (iChannel = 0; iChannel < (int)_channels.size(); ++iChannel) { if (_channels[iChannel]._sounds.empty()) break; } assert(iChannel != (int)_channels.size()); } // If the new sound replaces current ones, then clear the channel ChannelEntry &channel = _channels[iChannel]; if (flags & QMIX_CLEARQUEUE) { if (!channel._sounds.empty() && channel._sounds.front()._started) _mixer->stopHandle(channel._sounds.front()._soundHandle); channel._sounds.clear(); } // Add the sound to the channel channel._sounds.push_back(SoundEntry(waveFile, params.callback, loops, params.dwUser)); qsWaveMixPump(); return 0; } bool QMixer::qsWaveMixIsChannelDone(int iChannel) const { return _channels[iChannel]._sounds.empty(); } void QMixer::qsWaveMixPump() { // Iterate through each of the channels for (uint iChannel = 0; iChannel < _channels.size(); ++iChannel) { ChannelEntry &channel = _channels[iChannel]; // If there's a transition in sound volume in progress, handle it if (channel._volumeChangeEnd) { byte oldVolume = channel._volume; uint currentTicks = g_system->getMillis(); if (currentTicks >= channel._volumeChangeEnd) { // Reached end of transition period channel._volume = channel._volumeEnd; channel._volumeChangeStart = channel._volumeChangeEnd = 0; } else { // Transition in progress, so figure out new volume channel._volume = (int)channel._volumeStart + ((int)channel._volumeEnd - (int)channel._volumeStart) * (int)(currentTicks - channel._volumeChangeStart) / (int)channel._panRate; } debugC(DEBUG_DETAILED, kDebugCore, "qsWaveMixPump time=%u vol=%d", currentTicks, channel._volume); if (channel._volume != oldVolume && !channel._sounds.empty() && channel._sounds.front()._started) { _mixer->setChannelVolume(channel._sounds.front()._soundHandle, channel.getRawVolume()); } } // If the playing sound on the channel is finished, then call // the callback registered for it, and remove it from the list if (!channel._sounds.empty()) { SoundEntry &sound = channel._sounds.front(); if (sound._started && !_mixer->isSoundHandleActive(sound._soundHandle)) { // Sound is finished if (sound._callback) // Call the callback to signal end sound._callback(iChannel, sound._waveFile, sound._userData); // Remove sound record from channel channel._sounds.erase(channel._sounds.begin()); } } // If there's an unstarted sound at the front of a channel's // sound list, then start it playing if (!channel._sounds.empty()) { SoundEntry &sound = channel._sounds.front(); if (!sound._started) { if (channel._resetDistance) channel._distance = 0.0; // Play the wave sound._soundHandle = sound._waveFile->play( sound._loops, channel.getRawVolume()); sound._started = true; } } } } /*------------------------------------------------------------------------*/ byte QMixer::ChannelEntry::getRawVolume() const { // Emperically decided adjustment divisor for distances const double ADJUSTMENT_FACTOR = 5.0; double r = 1.0 + (_distance / ADJUSTMENT_FACTOR); double percent = 1.0 / (r * r); double newVolume = _volume * percent; return (byte)newVolume; } } // End of namespace Titanic