#include "amuse/Emitter.hpp" #include "amuse/Engine.hpp" #include "amuse/Listener.hpp" #include "amuse/Voice.hpp" namespace amuse { static constexpr Vector3f Delta(const Vector3f& a, const Vector3f& b) { return {a[0] - b[0], a[1] - b[1], a[2] - b[2]}; } Emitter::~Emitter() = default; Emitter::Emitter(Engine& engine, const AudioGroup& group, ObjToken vox, float maxDist, float minVol, float falloff, bool doppler) : Entity(engine, group, vox->getGroupId(), vox->getObjectId()) , m_vox(vox) , m_maxDist(maxDist) , m_minVol(std::clamp(minVol, 0.f, 1.f)) , m_falloff(std::clamp(falloff, -1.f, 1.f)) , m_doppler(doppler) {} void Emitter::_destroy() { Entity::_destroy(); m_vox->kill(); } float Emitter::_attenuationCurve(float dist) const { if (dist > m_maxDist) return 0.f; float t = dist / m_maxDist; if (m_falloff >= 0.f) { return 1.f - (m_falloff * t * t + (1.f - m_falloff) * t); } else { float omt = 1.f - t; return 1.f - ((1.f + m_falloff) * t - (1.f - omt * omt) * m_falloff); } } void Emitter::_update() { if (!m_dirty) { /* Ensure that all listeners are also not dirty */ bool dirty = false; for (auto& listener : m_engine.m_activeListeners) { if (listener->m_dirty) { dirty = true; break; } } if (!dirty) return; } std::array coefs{}; double avgDopplerRatio = 0.0; for (auto& listener : m_engine.m_activeListeners) { const Vector3f listenerToEmitter = Delta(m_pos, listener->m_pos); const float dist = Length(listenerToEmitter); const float panDist = Dot(listenerToEmitter, listener->m_right); const float frontPan = std::clamp(panDist / listener->m_frontDiff, -1.f, 1.f); const float backPan = std::clamp(panDist / listener->m_backDiff, -1.f, 1.f); const float spanDist = -Dot(listenerToEmitter, listener->m_heading); const float span = std::clamp(spanDist > 0.f ? spanDist / listener->m_backDiff : spanDist / listener->m_frontDiff, -1.f, 1.f); /* Calculate attenuation */ float att = _attenuationCurve(dist); att = (m_maxVol - m_minVol) * att + m_minVol; att = m_attCache.getVolume(att, false); if (att > FLT_EPSILON) { /* Apply pan law */ const std::array thisCoefs = m_vox->_panLaw(frontPan, backPan, span); /* Take maximum coefficient across listeners */ for (size_t i = 0; i < coefs.size(); ++i) { coefs[i] = std::max(coefs[i], thisCoefs[i] * att * listener->m_volume); } } /* Calculate doppler */ if (m_doppler) { /* Positive values indicate emitter and listener closing in */ const Vector3f dirDelta = Delta(m_dir, listener->m_dir); const Vector3f posDelta = Normalize(Delta(listener->m_pos, m_pos)); const float deltaSpeed = Dot(dirDelta, posDelta); if (listener->m_soundSpeed != 0.f) { avgDopplerRatio += 1.0 + deltaSpeed / listener->m_soundSpeed; } else { avgDopplerRatio += 1.0; } } } if (m_engine.m_activeListeners.size() != 0) { m_vox->setChannelCoefs(coefs); if (m_doppler) { m_vox->m_dopplerRatio = avgDopplerRatio / float(m_engine.m_activeListeners.size()); m_vox->m_pitchDirty = true; } } m_dirty = false; } void Emitter::setVectors(const float* pos, const float* dir) { for (size_t i = 0; i < m_pos.size(); ++i) { if (!std::isnan(pos[i])) { m_pos[i] = pos[i]; } else { m_pos[i] = 0.f; } if (!std::isnan(dir[i])) { m_dir[i] = dir[i]; } else { m_dir[i] = 0.f; } } m_dirty = true; } } // namespace amuse