scummvm/engines/titanic/sound/qmixer.cpp

255 lines
8.3 KiB
C++

/* 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 "common/system.h"
#include "titanic/sound/qmixer.h"
namespace Titanic {
QMixer::QMixer(Audio::Mixer *mixer) : _mixer(mixer) {
}
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<SoundEntry>::iterator i;
Common::List<SoundEntry> &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 = newVolume;
channel._volumeEnd = volume * 255 / 100; // Convert from 0-100 (percent) to 0-255
channel._volumeChangeStart = g_system->getMillis();
channel._volumeChangeEnd = channel._volumeChangeStart + channel._panRate;
}
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 &params) {
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;
}
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)) {
if (sound._loops == -1 || sound._loops-- > 0) {
// Need to loop the sound again
sound._waveFile->_stream->rewind();
_mixer->playStream(sound._waveFile->_soundType,
&sound._soundHandle, sound._waveFile->_stream,
-1, channel.getRawVolume(), 0, DisposeAfterUse::NO);
} else {
// 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;
// Calculate an effective volume based on distance of source
_mixer->playStream(sound._waveFile->_soundType,
&sound._soundHandle, sound._waveFile->_stream,
-1, channel.getRawVolume(), 0, DisposeAfterUse::NO);
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