/* * ***** BEGIN GPL LICENSE BLOCK ***** * * Copyright 2009-2011 Jörg Hermann Müller * * This file is part of AudaSpace. * * Audaspace 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. * * AudaSpace 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 Audaspace; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * ***** END GPL LICENSE BLOCK ***** */ /** \file audaspace/intern/AUD_SoftwareDevice.cpp * \ingroup audaspaceintern */ #include "AUD_SoftwareDevice.h" #include "AUD_IReader.h" #include "AUD_Mixer.h" #include "AUD_IFactory.h" #include "AUD_JOSResampleReader.h" #include "AUD_LinearResampleReader.h" #include #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif typedef enum { AUD_RENDER_DISTANCE = 0x01, AUD_RENDER_DOPPLER = 0x02, AUD_RENDER_CONE = 0x04, AUD_RENDER_VOLUME = 0x08 } AUD_RenderFlags; #define AUD_PITCH_MAX 10 /******************************************************************************/ /********************** AUD_SoftwareHandle Handle Code ************************/ /******************************************************************************/ AUD_SoftwareDevice::AUD_SoftwareHandle::AUD_SoftwareHandle(AUD_SoftwareDevice* device, AUD_Reference reader, AUD_Reference pitch, AUD_Reference resampler, AUD_Reference 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_loopcount(0), m_relative(true), m_volume_max(1.0f), m_volume_min(0), m_distance_max(std::numeric_limits::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(AUD_RENDER_CONE), m_stop(NULL), m_stop_data(NULL), m_status(AUD_STATUS_PLAYING), m_device(device) { } void AUD_SoftwareDevice::AUD_SoftwareHandle::update() { int flags = 0; AUD_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 |= AUD_RENDER_DOPPLER | AUD_RENDER_DISTANCE; if(m_pitch->getSpecs().channels != AUD_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 & AUD_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(AUD_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 & AUD_RENDER_VOLUME) { // Distance if(flags & AUD_RENDER_DISTANCE) { if(m_device->m_distance_model == AUD_DISTANCE_MODEL_INVERSE_CLAMPED || m_device->m_distance_model == AUD_DISTANCE_MODEL_LINEAR_CLAMPED || m_device->m_distance_model == AUD_DISTANCE_MODEL_EXPONENT_CLAMPED) { distance = AUD_MAX(AUD_MIN(m_distance_max, distance), m_distance_reference); } switch(m_device->m_distance_model) { case AUD_DISTANCE_MODEL_INVERSE: case AUD_DISTANCE_MODEL_INVERSE_CLAMPED: m_volume = m_distance_reference / (m_distance_reference + m_attenuation * (distance - m_distance_reference)); break; case AUD_DISTANCE_MODEL_LINEAR: case AUD_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 AUD_DISTANCE_MODEL_EXPONENT: case AUD_DISTANCE_MODEL_EXPONENT_CLAMPED: if(m_distance_reference == 0) m_volume = 0; else m_volume = pow(distance / m_distance_reference, -m_attenuation); break; default: m_volume = 1.0f; } } else m_volume = 1.0f; // Cone if(flags & AUD_RENDER_CONE) { AUD_Vector3 SZ = m_orientation.getLookAt(); float phi = 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 AUD_Quaternion orientation; if(!m_relative) orientation = m_device->m_orientation; AUD_Vector3 Z = orientation.getLookAt(); AUD_Vector3 N = orientation.getUp(); AUD_Vector3 A = N * ((SL * N) / (N * N)) - SL; float Asquare = A * A; if(Asquare > 0) { float phi = acos(float(Z * A / (Z.length() * 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 AUD_SoftwareDevice::AUD_SoftwareHandle::setSpecs(AUD_Specs specs) { m_mapper->setChannels(specs.channels); m_resampler->setRate(specs.rate); } bool AUD_SoftwareDevice::AUD_SoftwareHandle::pause() { if(m_status) { m_device->lock(); if(m_status == AUD_STATUS_PLAYING) { m_device->m_playingSounds.remove(this); m_device->m_pausedSounds.push_back(this); if(m_device->m_playingSounds.empty()) m_device->playing(m_device->m_playback = false); m_status = AUD_STATUS_PAUSED; m_device->unlock(); return true; } m_device->unlock(); } return false; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::resume() { if(m_status) { m_device->lock(); if(m_status == AUD_STATUS_PAUSED) { m_device->m_pausedSounds.remove(this); m_device->m_playingSounds.push_back(this); if(!m_device->m_playback) m_device->playing(m_device->m_playback = true); m_status = AUD_STATUS_PLAYING; m_device->unlock(); return true; } m_device->unlock(); } return false; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::stop() { if(!m_status) return false; m_device->lock(); // AUD_XXX Create a reference of our own object so that it doesn't get // deleted before the end of this function AUD_Reference This = this; if(m_status == AUD_STATUS_PLAYING) { m_device->m_playingSounds.remove(This); if(m_device->m_playingSounds.empty()) m_device->playing(m_device->m_playback = false); } else m_device->m_pausedSounds.remove(This); m_device->unlock(); m_status = AUD_STATUS_INVALID; return true; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::getKeep() { if(m_status) return m_keep; return false; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::setKeep(bool keep) { if(!m_status) return false; m_device->lock(); m_keep = keep; m_device->unlock(); return true; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::seek(float position) { if(!m_status) return false; m_device->lock(); m_reader->seek((int)(position * m_reader->getSpecs().rate)); m_device->unlock(); return true; } float AUD_SoftwareDevice::AUD_SoftwareHandle::getPosition() { if(!m_status) return 0.0f; m_device->lock(); float position = m_reader->getPosition() / (float)m_device->m_specs.rate; m_device->unlock(); return position; } AUD_Status AUD_SoftwareDevice::AUD_SoftwareHandle::getStatus() { return m_status; } float AUD_SoftwareDevice::AUD_SoftwareHandle::getVolume() { return m_user_volume; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::setVolume(float volume) { if(!m_status) return false; m_user_volume = volume; if(volume == 0) { m_volume = volume; m_flags |= AUD_RENDER_VOLUME; } else m_flags &= ~AUD_RENDER_VOLUME; return true; } float AUD_SoftwareDevice::AUD_SoftwareHandle::getPitch() { return m_user_pitch; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::setPitch(float pitch) { if(!m_status) return false; m_user_pitch = pitch; return true; } int AUD_SoftwareDevice::AUD_SoftwareHandle::getLoopCount() { if(!m_status) return 0; return m_loopcount; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::setLoopCount(int count) { if(!m_status) return false; m_loopcount = count; return true; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::setStopCallback(stopCallback callback, void* data) { if(!m_status) return false; m_device->lock(); m_stop = callback; m_stop_data = data; m_device->unlock(); return true; } /******************************************************************************/ /******************** AUD_SoftwareHandle 3DHandle Code ************************/ /******************************************************************************/ AUD_Vector3 AUD_SoftwareDevice::AUD_SoftwareHandle::getSourceLocation() { if(!m_status) return AUD_Vector3(); return m_location; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::setSourceLocation(const AUD_Vector3& location) { if(!m_status) return false; m_location = location; return true; } AUD_Vector3 AUD_SoftwareDevice::AUD_SoftwareHandle::getSourceVelocity() { if(!m_status) return AUD_Vector3(); return m_velocity; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::setSourceVelocity(const AUD_Vector3& velocity) { if(!m_status) return false; m_velocity = velocity; return true; } AUD_Quaternion AUD_SoftwareDevice::AUD_SoftwareHandle::getSourceOrientation() { if(!m_status) return AUD_Quaternion(); return m_orientation; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::setSourceOrientation(const AUD_Quaternion& orientation) { if(!m_status) return false; m_orientation = orientation; return true; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::isRelative() { if(!m_status) return false; return m_relative; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::setRelative(bool relative) { if(!m_status) return false; m_relative = relative; return true; } float AUD_SoftwareDevice::AUD_SoftwareHandle::getVolumeMaximum() { if(!m_status) return std::numeric_limits::quiet_NaN(); return m_volume_max; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::setVolumeMaximum(float volume) { if(!m_status) return false; m_volume_max = volume; return true; } float AUD_SoftwareDevice::AUD_SoftwareHandle::getVolumeMinimum() { if(!m_status) return std::numeric_limits::quiet_NaN(); return m_volume_min; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::setVolumeMinimum(float volume) { if(!m_status) return false; m_volume_min = volume; return true; } float AUD_SoftwareDevice::AUD_SoftwareHandle::getDistanceMaximum() { if(!m_status) return std::numeric_limits::quiet_NaN(); return m_distance_max; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::setDistanceMaximum(float distance) { if(!m_status) return false; m_distance_max = distance; return true; } float AUD_SoftwareDevice::AUD_SoftwareHandle::getDistanceReference() { if(!m_status) return std::numeric_limits::quiet_NaN(); return m_distance_reference; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::setDistanceReference(float distance) { if(!m_status) return false; m_distance_reference = distance; return true; } float AUD_SoftwareDevice::AUD_SoftwareHandle::getAttenuation() { if(!m_status) return std::numeric_limits::quiet_NaN(); return m_attenuation; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::setAttenuation(float factor) { if(!m_status) return false; m_attenuation = factor; if(factor == 0) m_flags |= AUD_RENDER_DISTANCE; else m_flags &= ~AUD_RENDER_DISTANCE; return true; } float AUD_SoftwareDevice::AUD_SoftwareHandle::getConeAngleOuter() { if(!m_status) return std::numeric_limits::quiet_NaN(); return m_cone_angle_outer * 360.0f / M_PI; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::setConeAngleOuter(float angle) { if(!m_status) return false; m_cone_angle_outer = angle * M_PI / 360.0f; return true; } float AUD_SoftwareDevice::AUD_SoftwareHandle::getConeAngleInner() { if(!m_status) return std::numeric_limits::quiet_NaN(); return m_cone_angle_inner * 360.0f / M_PI; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::setConeAngleInner(float angle) { if(!m_status) return false; if(angle >= 360) m_flags |= AUD_RENDER_CONE; else m_flags &= ~AUD_RENDER_CONE; m_cone_angle_inner = angle * M_PI / 360.0f; return true; } float AUD_SoftwareDevice::AUD_SoftwareHandle::getConeVolumeOuter() { if(!m_status) return std::numeric_limits::quiet_NaN(); return m_cone_volume_outer; } bool AUD_SoftwareDevice::AUD_SoftwareHandle::setConeVolumeOuter(float volume) { if(!m_status) return false; m_cone_volume_outer = volume; return true; } /******************************************************************************/ /**************************** IDevice Code ************************************/ /******************************************************************************/ void AUD_SoftwareDevice::create() { m_playback = false; m_volume = 1.0f; m_mixer = new AUD_Mixer(m_specs); m_speed_of_sound = 343.0f; m_doppler_factor = 1.0f; m_distance_model = AUD_DISTANCE_MODEL_INVERSE_CLAMPED; m_flags = 0; m_quality = false; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&m_mutex, &attr); pthread_mutexattr_destroy(&attr); } void AUD_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(); pthread_mutex_destroy(&m_mutex); } void AUD_SoftwareDevice::mix(data_t* buffer, int length) { m_buffer.assureSize(length * AUD_SAMPLE_SIZE(m_specs)); lock(); { AUD_Reference sound; int len; int pos; bool eos; std::list > stopSounds; std::list > pauseSounds; sample_t* buf = m_buffer.getBuffer(); m_mixer->clear(length); // for all sounds AUD_HandleIterator it = m_playingSounds.begin(); while(it != m_playingSounds.end()) { sound = *it; // increment the iterator to make sure it's valid, // in case the sound gets deleted after stopping ++it; // get the buffer from the source pos = 0; len = length; // update 3D Info sound->update(); 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); 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; } m_mixer->mix(buf, pos, len, sound->m_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 while(!stopSounds.empty()) { sound = stopSounds.front(); stopSounds.pop_front(); sound->stop(); } while(!pauseSounds.empty()) { sound = pauseSounds.front(); pauseSounds.pop_front(); sound->pause(); } } unlock(); } void AUD_SoftwareDevice::setPanning(AUD_IHandle* handle, float pan) { AUD_SoftwareDevice::AUD_SoftwareHandle* h = dynamic_cast(handle); h->m_user_pan = pan; } void AUD_SoftwareDevice::setQuality(bool quality) { m_quality = quality; } void AUD_SoftwareDevice::setSpecs(AUD_Specs specs) { m_specs.specs = specs; m_mixer->setSpecs(specs); for(AUD_HandleIterator it = m_playingSounds.begin(); it != m_playingSounds.end(); it++) { (*it)->setSpecs(specs); } } AUD_DeviceSpecs AUD_SoftwareDevice::getSpecs() const { return m_specs; } AUD_Reference AUD_SoftwareDevice::play(AUD_Reference reader, bool keep) { // prepare the reader // pitch AUD_Reference pitch = new AUD_PitchReader(reader, 1); reader = AUD_Reference(pitch); AUD_Reference resampler; // resample if(m_quality) resampler = new AUD_JOSResampleReader(reader, m_specs.specs); else resampler = new AUD_LinearResampleReader(reader, m_specs.specs); reader = AUD_Reference(resampler); // rechannel AUD_Reference mapper = new AUD_ChannelMapperReader(reader, m_specs.channels); reader = AUD_Reference(mapper); if(reader.isNull()) return AUD_Reference(); // play sound AUD_Reference sound = new AUD_SoftwareDevice::AUD_SoftwareHandle(this, reader, pitch, resampler, mapper, keep); lock(); m_playingSounds.push_back(sound); if(!m_playback) playing(m_playback = true); unlock(); return AUD_Reference(sound); } AUD_Reference AUD_SoftwareDevice::play(AUD_Reference factory, bool keep) { return play(factory->createReader(), keep); } void AUD_SoftwareDevice::stopAll() { lock(); while(!m_playingSounds.empty()) m_playingSounds.front()->stop(); while(!m_pausedSounds.empty()) m_pausedSounds.front()->stop(); unlock(); } void AUD_SoftwareDevice::lock() { pthread_mutex_lock(&m_mutex); } void AUD_SoftwareDevice::unlock() { pthread_mutex_unlock(&m_mutex); } float AUD_SoftwareDevice::getVolume() const { return m_volume; } void AUD_SoftwareDevice::setVolume(float volume) { m_volume = volume; } /******************************************************************************/ /**************************** 3D Device Code **********************************/ /******************************************************************************/ AUD_Vector3 AUD_SoftwareDevice::getListenerLocation() const { return m_location; } void AUD_SoftwareDevice::setListenerLocation(const AUD_Vector3& location) { m_location = location; } AUD_Vector3 AUD_SoftwareDevice::getListenerVelocity() const { return m_velocity; } void AUD_SoftwareDevice::setListenerVelocity(const AUD_Vector3& velocity) { m_velocity = velocity; } AUD_Quaternion AUD_SoftwareDevice::getListenerOrientation() const { return m_orientation; } void AUD_SoftwareDevice::setListenerOrientation(const AUD_Quaternion& orientation) { m_orientation = orientation; } float AUD_SoftwareDevice::getSpeedOfSound() const { return m_speed_of_sound; } void AUD_SoftwareDevice::setSpeedOfSound(float speed) { m_speed_of_sound = speed; } float AUD_SoftwareDevice::getDopplerFactor() const { return m_doppler_factor; } void AUD_SoftwareDevice::setDopplerFactor(float factor) { m_doppler_factor = factor; if(factor == 0) m_flags |= AUD_RENDER_DOPPLER; else m_flags &= ~AUD_RENDER_DOPPLER; } AUD_DistanceModel AUD_SoftwareDevice::getDistanceModel() const { return m_distance_model; } void AUD_SoftwareDevice::setDistanceModel(AUD_DistanceModel model) { m_distance_model = model; if(model == AUD_DISTANCE_MODEL_INVALID) m_flags |= AUD_RENDER_DISTANCE; else m_flags &= ~AUD_RENDER_DISTANCE; }