diff options
Diffstat (limited to 'extern/audaspace/src/devices/SoftwareDevice.cpp')
-rw-r--r-- | extern/audaspace/src/devices/SoftwareDevice.cpp | 989 |
1 files changed, 989 insertions, 0 deletions
diff --git a/extern/audaspace/src/devices/SoftwareDevice.cpp b/extern/audaspace/src/devices/SoftwareDevice.cpp new file mode 100644 index 00000000000..c944b9ed12d --- /dev/null +++ b/extern/audaspace/src/devices/SoftwareDevice.cpp @@ -0,0 +1,989 @@ +/******************************************************************************* + * Copyright 2009-2016 Jörg Müller + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +#include "devices/SoftwareDevice.h" +#include "fx/PitchReader.h" +#include "respec/ChannelMapperReader.h" +#include "respec/JOSResampleReader.h" +#include "respec/LinearResampleReader.h" +#include "respec/Mixer.h" +#include "Exception.h" +#include "ISound.h" + +#include <algorithm> +#include <cmath> +#include <cstring> +#include <iostream> +#include <limits> +#include <mutex> + +AUD_NAMESPACE_BEGIN + +enum RenderFlags +{ + RENDER_DISTANCE = 0x01, + RENDER_DOPPLER = 0x02, + RENDER_CONE = 0x04, + RENDER_VOLUME = 0x08 +}; + +#define PITCH_MAX 10 + +/******************************************************************************/ +/********************** SoftwareHandle Handle Code ************************/ +/******************************************************************************/ + +bool SoftwareDevice::SoftwareHandle::pause(bool keep) +{ + if(m_status) + { + std::lock_guard<ILockable> lock(*m_device); + + if(m_status == STATUS_PLAYING) + { + for(auto it = m_device->m_playingSounds.begin(); it != m_device->m_playingSounds.end(); it++) + { + if(it->get() == this) + { + std::shared_ptr<SoftwareHandle> This = *it; + + m_device->m_playingSounds.erase(it); + m_device->m_pausedSounds.push_back(This); + + if(m_device->m_playingSounds.empty()) + m_device->playing(m_device->m_playback = false); + + m_status = keep ? STATUS_STOPPED : STATUS_PAUSED; + + return true; + } + } + } + } + + return false; +} + +SoftwareDevice::SoftwareHandle::SoftwareHandle(SoftwareDevice* device, std::shared_ptr<IReader> reader, std::shared_ptr<PitchReader> pitch, std::shared_ptr<ResampleReader> resampler, std::shared_ptr<ChannelMapperReader> mapper, bool keep) : + m_reader(reader), m_pitch(pitch), m_resampler(resampler), m_mapper(mapper), m_keep(keep), m_user_pitch(1.0f), m_user_volume(1.0f), m_user_pan(0.0f), m_volume(1.0f), m_old_volume(0), m_loopcount(0), + m_relative(true), m_volume_max(1.0f), m_volume_min(0), m_distance_max(std::numeric_limits<float>::max()), + m_distance_reference(1.0f), m_attenuation(1.0f), m_cone_angle_outer(M_PI), m_cone_angle_inner(M_PI), m_cone_volume_outer(0), + m_flags(RENDER_CONE), m_stop(nullptr), m_stop_data(nullptr), m_status(STATUS_PLAYING), m_device(device) +{ +} + +void SoftwareDevice::SoftwareHandle::update() +{ + int flags = 0; + + m_old_volume = m_volume; + + Vector3 SL; + if(m_relative) + SL = -m_location; + else + SL = m_device->m_location - m_location; + float distance = SL * SL; + + if(distance > 0) + distance = sqrt(distance); + else + flags |= RENDER_DOPPLER | RENDER_DISTANCE; + + if(m_pitch->getSpecs().channels != CHANNELS_MONO) + { + m_volume = m_user_volume; + m_pitch->setPitch(m_user_pitch); + return; + } + + flags = ~(flags | m_flags | m_device->m_flags); + + // Doppler and Pitch + + if(flags & RENDER_DOPPLER) + { + float vls; + if(m_relative) + vls = 0; + else + vls = SL * m_device->m_velocity / distance; + float vss = SL * m_velocity / distance; + float max = m_device->m_speed_of_sound / m_device->m_doppler_factor; + if(vss >= max) + { + m_pitch->setPitch(PITCH_MAX); + } + else + { + if(vls > max) + vls = max; + + m_pitch->setPitch((m_device->m_speed_of_sound - m_device->m_doppler_factor * vls) / (m_device->m_speed_of_sound - m_device->m_doppler_factor * vss) * m_user_pitch); + } + } + else + m_pitch->setPitch(m_user_pitch); + + if(flags & RENDER_VOLUME) + { + // Distance + + if(flags & RENDER_DISTANCE) + { + if(m_device->m_distance_model == DISTANCE_MODEL_INVERSE_CLAMPED || + m_device->m_distance_model == DISTANCE_MODEL_LINEAR_CLAMPED || + m_device->m_distance_model == DISTANCE_MODEL_EXPONENT_CLAMPED) + { + distance = std::max(std::min(m_distance_max, distance), m_distance_reference); + } + + switch(m_device->m_distance_model) + { + case DISTANCE_MODEL_INVERSE: + case DISTANCE_MODEL_INVERSE_CLAMPED: + m_volume = m_distance_reference / (m_distance_reference + m_attenuation * (distance - m_distance_reference)); + break; + case DISTANCE_MODEL_LINEAR: + case DISTANCE_MODEL_LINEAR_CLAMPED: + { + float temp = m_distance_max - m_distance_reference; + if(temp == 0) + { + if(distance > m_distance_reference) + m_volume = 0.0f; + else + m_volume = 1.0f; + } + else + m_volume = 1.0f - m_attenuation * (distance - m_distance_reference) / (m_distance_max - m_distance_reference); + break; + } + case DISTANCE_MODEL_EXPONENT: + case DISTANCE_MODEL_EXPONENT_CLAMPED: + if(m_distance_reference == 0) + m_volume = 0; + else + m_volume = std::pow(distance / m_distance_reference, -m_attenuation); + break; + default: + m_volume = 1.0f; + } + } + else + m_volume = 1.0f; + + // Cone + + if(flags & RENDER_CONE) + { + Vector3 SZ = m_orientation.getLookAt(); + + float phi = std::acos(float(SZ * SL / (SZ.length() * SL.length()))); + float t = (phi - m_cone_angle_inner)/(m_cone_angle_outer - m_cone_angle_inner); + + if(t > 0) + { + if(t > 1) + m_volume *= m_cone_volume_outer; + else + m_volume *= 1 + t * (m_cone_volume_outer - 1); + } + } + + if(m_volume > m_volume_max) + m_volume = m_volume_max; + else if(m_volume < m_volume_min) + m_volume = m_volume_min; + + // Volume + + m_volume *= m_user_volume; + } + + // 3D Cue + + Quaternion orientation; + + if(!m_relative) + orientation = m_device->m_orientation; + + Vector3 Z = orientation.getLookAt(); + Vector3 N = orientation.getUp(); + Vector3 A = N * ((SL * N) / (N * N)) - SL; + + float Asquare = A * A; + + if(Asquare > 0) + { + float phi = std::acos(float(Z * A / (Z.length() * std::sqrt(Asquare)))); + if(N.cross(Z) * A > 0) + phi = -phi; + + m_mapper->setMonoAngle(phi); + } + else + m_mapper->setMonoAngle(m_relative ? m_user_pan * M_PI / 2.0 : 0); +} + +void SoftwareDevice::SoftwareHandle::setSpecs(Specs specs) +{ + m_mapper->setChannels(specs.channels); + m_resampler->setRate(specs.rate); +} + +bool SoftwareDevice::SoftwareHandle::pause() +{ + return pause(false); +} + +bool SoftwareDevice::SoftwareHandle::resume() +{ + if(m_status) + { + std::lock_guard<ILockable> lock(*m_device); + + if(m_status == STATUS_PAUSED) + { + for(auto it = m_device->m_pausedSounds.begin(); it != m_device->m_pausedSounds.end(); it++) + { + if(it->get() == this) + { + std::shared_ptr<SoftwareHandle> This = *it; + + m_device->m_pausedSounds.erase(it); + + m_device->m_playingSounds.push_back(This); + + if(!m_device->m_playback) + m_device->playing(m_device->m_playback = true); + m_status = STATUS_PLAYING; + + return true; + } + } + } + + } + + return false; +} + +bool SoftwareDevice::SoftwareHandle::stop() +{ + if(!m_status) + return false; + + std::lock_guard<ILockable> lock(*m_device); + + if(!m_status) + return false; + + m_status = STATUS_INVALID; + + for(auto it = m_device->m_playingSounds.begin(); it != m_device->m_playingSounds.end(); it++) + { + if(it->get() == this) + { + std::shared_ptr<SoftwareHandle> This = *it; + + m_device->m_playingSounds.erase(it); + + if(m_device->m_playingSounds.empty()) + m_device->playing(m_device->m_playback = false); + + return true; + } + } + + for(auto it = m_device->m_pausedSounds.begin(); it != m_device->m_pausedSounds.end(); it++) + { + if(it->get() == this) + { + std::shared_ptr<SoftwareHandle> This = *it; + + m_device->m_pausedSounds.erase(it); + + return true; + } + } + + return false; +} + +bool SoftwareDevice::SoftwareHandle::getKeep() +{ + if(m_status) + return m_keep; + + return false; +} + +bool SoftwareDevice::SoftwareHandle::setKeep(bool keep) +{ + if(!m_status) + return false; + + std::lock_guard<ILockable> lock(*m_device); + + if(!m_status) + return false; + + m_keep = keep; + + return true; +} + +bool SoftwareDevice::SoftwareHandle::seek(float position) +{ + if(!m_status) + return false; + + std::lock_guard<ILockable> lock(*m_device); + + if(!m_status) + return false; + + m_pitch->setPitch(m_user_pitch); + m_reader->seek((int)(position * m_reader->getSpecs().rate)); + + if(m_status == STATUS_STOPPED) + m_status = STATUS_PAUSED; + + return true; +} + +float SoftwareDevice::SoftwareHandle::getPosition() +{ + if(!m_status) + return false; + + std::lock_guard<ILockable> lock(*m_device); + + if(!m_status) + return 0.0f; + + float position = m_reader->getPosition() / (float)m_device->m_specs.rate; + + return position; +} + +Status SoftwareDevice::SoftwareHandle::getStatus() +{ + return m_status; +} + +float SoftwareDevice::SoftwareHandle::getVolume() +{ + return m_user_volume; +} + +bool SoftwareDevice::SoftwareHandle::setVolume(float volume) +{ + if(!m_status) + return false; + m_user_volume = volume; + + if(volume == 0) + { + m_old_volume = m_volume = volume; + m_flags |= RENDER_VOLUME; + } + else + m_flags &= ~RENDER_VOLUME; + + return true; +} + +float SoftwareDevice::SoftwareHandle::getPitch() +{ + return m_user_pitch; +} + +bool SoftwareDevice::SoftwareHandle::setPitch(float pitch) +{ + if(!m_status) + return false; + if(pitch > 0.0f) + m_user_pitch = pitch; + return true; +} + +int SoftwareDevice::SoftwareHandle::getLoopCount() +{ + if(!m_status) + return 0; + return m_loopcount; +} + +bool SoftwareDevice::SoftwareHandle::setLoopCount(int count) +{ + if(!m_status) + return false; + + if(m_status == STATUS_STOPPED && (count > m_loopcount || count < 0)) + m_status = STATUS_PAUSED; + + m_loopcount = count; + + return true; +} + +bool SoftwareDevice::SoftwareHandle::setStopCallback(stopCallback callback, void* data) +{ + if(!m_status) + return false; + + std::lock_guard<ILockable> lock(*m_device); + + if(!m_status) + return false; + + m_stop = callback; + m_stop_data = data; + + return true; +} + + + +/******************************************************************************/ +/******************** SoftwareHandle 3DHandle Code ************************/ +/******************************************************************************/ + +Vector3 SoftwareDevice::SoftwareHandle::getLocation() +{ + if(!m_status) + return Vector3(); + + return m_location; +} + +bool SoftwareDevice::SoftwareHandle::setLocation(const Vector3& location) +{ + if(!m_status) + return false; + + m_location = location; + + return true; +} + +Vector3 SoftwareDevice::SoftwareHandle::getVelocity() +{ + if(!m_status) + return Vector3(); + + return m_velocity; +} + +bool SoftwareDevice::SoftwareHandle::setVelocity(const Vector3& velocity) +{ + if(!m_status) + return false; + + m_velocity = velocity; + + return true; +} + +Quaternion SoftwareDevice::SoftwareHandle::getOrientation() +{ + if(!m_status) + return Quaternion(); + + return m_orientation; +} + +bool SoftwareDevice::SoftwareHandle::setOrientation(const Quaternion& orientation) +{ + if(!m_status) + return false; + + m_orientation = orientation; + + return true; +} + +bool SoftwareDevice::SoftwareHandle::isRelative() +{ + if(!m_status) + return false; + + return m_relative; +} + +bool SoftwareDevice::SoftwareHandle::setRelative(bool relative) +{ + if(!m_status) + return false; + + m_relative = relative; + + return true; +} + +float SoftwareDevice::SoftwareHandle::getVolumeMaximum() +{ + if(!m_status) + return std::numeric_limits<float>::quiet_NaN(); + + return m_volume_max; +} + +bool SoftwareDevice::SoftwareHandle::setVolumeMaximum(float volume) +{ + if(!m_status) + return false; + + m_volume_max = volume; + + return true; +} + +float SoftwareDevice::SoftwareHandle::getVolumeMinimum() +{ + if(!m_status) + return std::numeric_limits<float>::quiet_NaN(); + + return m_volume_min; +} + +bool SoftwareDevice::SoftwareHandle::setVolumeMinimum(float volume) +{ + if(!m_status) + return false; + + m_volume_min = volume; + + return true; +} + +float SoftwareDevice::SoftwareHandle::getDistanceMaximum() +{ + if(!m_status) + return std::numeric_limits<float>::quiet_NaN(); + + return m_distance_max; +} + +bool SoftwareDevice::SoftwareHandle::setDistanceMaximum(float distance) +{ + if(!m_status) + return false; + + m_distance_max = distance; + + return true; +} + +float SoftwareDevice::SoftwareHandle::getDistanceReference() +{ + if(!m_status) + return std::numeric_limits<float>::quiet_NaN(); + + return m_distance_reference; +} + +bool SoftwareDevice::SoftwareHandle::setDistanceReference(float distance) +{ + if(!m_status) + return false; + + m_distance_reference = distance; + + return true; +} + +float SoftwareDevice::SoftwareHandle::getAttenuation() +{ + if(!m_status) + return std::numeric_limits<float>::quiet_NaN(); + + return m_attenuation; +} + +bool SoftwareDevice::SoftwareHandle::setAttenuation(float factor) +{ + if(!m_status) + return false; + + m_attenuation = factor; + + if(factor == 0) + m_flags |= RENDER_DISTANCE; + else + m_flags &= ~RENDER_DISTANCE; + + return true; +} + +float SoftwareDevice::SoftwareHandle::getConeAngleOuter() +{ + if(!m_status) + return std::numeric_limits<float>::quiet_NaN(); + + return m_cone_angle_outer * 360.0f / M_PI; +} + +bool SoftwareDevice::SoftwareHandle::setConeAngleOuter(float angle) +{ + if(!m_status) + return false; + + m_cone_angle_outer = angle * M_PI / 360.0f; + + return true; +} + +float SoftwareDevice::SoftwareHandle::getConeAngleInner() +{ + if(!m_status) + return std::numeric_limits<float>::quiet_NaN(); + + return m_cone_angle_inner * 360.0f / M_PI; +} + +bool SoftwareDevice::SoftwareHandle::setConeAngleInner(float angle) +{ + if(!m_status) + return false; + + if(angle >= 360) + m_flags |= RENDER_CONE; + else + m_flags &= ~RENDER_CONE; + + m_cone_angle_inner = angle * M_PI / 360.0f; + + return true; +} + +float SoftwareDevice::SoftwareHandle::getConeVolumeOuter() +{ + if(!m_status) + return std::numeric_limits<float>::quiet_NaN(); + + return m_cone_volume_outer; +} + +bool SoftwareDevice::SoftwareHandle::setConeVolumeOuter(float volume) +{ + if(!m_status) + return false; + + m_cone_volume_outer = volume; + + return true; +} + +/******************************************************************************/ +/**************************** IDevice Code ************************************/ +/******************************************************************************/ + +void SoftwareDevice::create() +{ + m_playback = false; + m_volume = 1.0f; + m_mixer = std::shared_ptr<Mixer>(new Mixer(m_specs)); + m_speed_of_sound = 343.3f; + m_doppler_factor = 1.0f; + m_distance_model = DISTANCE_MODEL_INVERSE_CLAMPED; + m_flags = 0; + m_quality = false; +} + +void SoftwareDevice::destroy() +{ + if(m_playback) + playing(m_playback = false); + + while(!m_playingSounds.empty()) + m_playingSounds.front()->stop(); + + while(!m_pausedSounds.empty()) + m_pausedSounds.front()->stop(); +} + +void SoftwareDevice::mix(data_t* buffer, int length) +{ + m_buffer.assureSize(length * AUD_SAMPLE_SIZE(m_specs)); + + std::lock_guard<std::recursive_mutex> lock(m_mutex); + + { + std::shared_ptr<SoftwareDevice::SoftwareHandle> sound; + int len; + int pos; + bool eos; + std::list<std::shared_ptr<SoftwareDevice::SoftwareHandle> > stopSounds; + std::list<std::shared_ptr<SoftwareDevice::SoftwareHandle> > pauseSounds; + sample_t* buf = m_buffer.getBuffer(); + + m_mixer->clear(length); + + // for all sounds + for(auto& sound : m_playingSounds) + { + // get the buffer from the source + pos = 0; + len = length; + + // update 3D Info + sound->update(); + + try + { + sound->m_reader->read(len, eos, buf); + + // in case of looping + while(pos + len < length && sound->m_loopcount && eos) + { + m_mixer->mix(buf, pos, len, sound->m_volume, sound->m_old_volume); + + pos += len; + + if(sound->m_loopcount > 0) + sound->m_loopcount--; + + sound->m_reader->seek(0); + + len = length - pos; + sound->m_reader->read(len, eos, buf); + + // prevent endless loop + if(!len) + break; + } + } + catch(Exception& e) + { + len = 0; + std::cerr << "Caught exception while reading sound data during playback with software mixing: " << e.getMessage() << std::endl; + } + + m_mixer->mix(buf, pos, len, sound->m_volume, sound->m_old_volume); + + // in case the end of the sound is reached + if(eos && !sound->m_loopcount) + { + if(sound->m_stop) + sound->m_stop(sound->m_stop_data); + + if(sound->m_keep) + pauseSounds.push_back(sound); + else + stopSounds.push_back(sound); + } + } + + // superpose + m_mixer->read(buffer, m_volume); + + // cleanup + for(auto& sound : pauseSounds) + sound->pause(true); + + for(auto& sound : stopSounds) + sound->stop(); + + pauseSounds.clear(); + stopSounds.clear(); + } +} + +void SoftwareDevice::setPanning(IHandle* handle, float pan) +{ + SoftwareDevice::SoftwareHandle* h = dynamic_cast<SoftwareDevice::SoftwareHandle*>(handle); + h->m_user_pan = pan; +} + +void SoftwareDevice::setQuality(bool quality) +{ + m_quality = quality; +} + +void SoftwareDevice::setSpecs(Specs specs) +{ + m_specs.specs = specs; + m_mixer->setSpecs(specs); + + for(auto& sound : m_playingSounds) + { + sound->setSpecs(specs); + } +} + +SoftwareDevice::SoftwareDevice() +{ +} + +DeviceSpecs SoftwareDevice::getSpecs() const +{ + return m_specs; +} + +std::shared_ptr<IHandle> SoftwareDevice::play(std::shared_ptr<IReader> reader, bool keep) +{ + // prepare the reader + // pitch + + std::shared_ptr<PitchReader> pitch = std::shared_ptr<PitchReader>(new PitchReader(reader, 1)); + reader = std::shared_ptr<IReader>(pitch); + + std::shared_ptr<ResampleReader> resampler; + + // resample + if(m_quality) + resampler = std::shared_ptr<ResampleReader>(new JOSResampleReader(reader, m_specs.rate)); + else + resampler = std::shared_ptr<ResampleReader>(new LinearResampleReader(reader, m_specs.rate)); + reader = std::shared_ptr<IReader>(resampler); + + // rechannel + std::shared_ptr<ChannelMapperReader> mapper = std::shared_ptr<ChannelMapperReader>(new ChannelMapperReader(reader, m_specs.channels)); + reader = std::shared_ptr<IReader>(mapper); + + if(!reader.get()) + return std::shared_ptr<IHandle>(); + + // play sound + std::shared_ptr<SoftwareDevice::SoftwareHandle> sound = std::shared_ptr<SoftwareDevice::SoftwareHandle>(new SoftwareDevice::SoftwareHandle(this, reader, pitch, resampler, mapper, keep)); + + std::lock_guard<std::recursive_mutex> lock(m_mutex); + + m_playingSounds.push_back(sound); + + if(!m_playback) + playing(m_playback = true); + + return std::shared_ptr<IHandle>(sound); +} + +std::shared_ptr<IHandle> SoftwareDevice::play(std::shared_ptr<ISound> sound, bool keep) +{ + return play(sound->createReader(), keep); +} + +void SoftwareDevice::stopAll() +{ + std::lock_guard<std::recursive_mutex> lock(m_mutex); + + while(!m_playingSounds.empty()) + m_playingSounds.front()->stop(); + + while(!m_pausedSounds.empty()) + m_pausedSounds.front()->stop(); +} + +void SoftwareDevice::lock() +{ + m_mutex.lock(); +} + +void SoftwareDevice::unlock() +{ + m_mutex.unlock(); +} + +float SoftwareDevice::getVolume() const +{ + return m_volume; +} + +void SoftwareDevice::setVolume(float volume) +{ + m_volume = volume; +} + +ISynchronizer* SoftwareDevice::getSynchronizer() +{ + return &m_synchronizer; +} + +/******************************************************************************/ +/**************************** 3D Device Code **********************************/ +/******************************************************************************/ + +Vector3 SoftwareDevice::getListenerLocation() const +{ + return m_location; +} + +void SoftwareDevice::setListenerLocation(const Vector3& location) +{ + m_location = location; +} + +Vector3 SoftwareDevice::getListenerVelocity() const +{ + return m_velocity; +} + +void SoftwareDevice::setListenerVelocity(const Vector3& velocity) +{ + m_velocity = velocity; +} + +Quaternion SoftwareDevice::getListenerOrientation() const +{ + return m_orientation; +} + +void SoftwareDevice::setListenerOrientation(const Quaternion& orientation) +{ + m_orientation = orientation; +} + +float SoftwareDevice::getSpeedOfSound() const +{ + return m_speed_of_sound; +} + +void SoftwareDevice::setSpeedOfSound(float speed) +{ + m_speed_of_sound = speed; +} + +float SoftwareDevice::getDopplerFactor() const +{ + return m_doppler_factor; +} + +void SoftwareDevice::setDopplerFactor(float factor) +{ + m_doppler_factor = factor; + if(factor == 0) + m_flags |= RENDER_DOPPLER; + else + m_flags &= ~RENDER_DOPPLER; +} + +DistanceModel SoftwareDevice::getDistanceModel() const +{ + return m_distance_model; +} + +void SoftwareDevice::setDistanceModel(DistanceModel model) +{ + m_distance_model = model; + if(model == DISTANCE_MODEL_INVALID) + m_flags |= RENDER_DISTANCE; + else + m_flags &= ~RENDER_DISTANCE; +} + +AUD_NAMESPACE_END |