Implement listener, emitter, and -3dB pan law

This commit is contained in:
Jack Andersen 2017-09-18 17:59:20 -10:00
parent aef2b2a707
commit c6781df90a
11 changed files with 348 additions and 250 deletions

View File

@ -37,7 +37,6 @@ set(SOURCES
lib/EffectReverb.cpp
lib/EffectChorus.cpp
lib/EffectDelay.cpp
lib/SurroundProfiles.cpp
lib/ContainerRegistry.cpp
lib/DSPCodec.c
lib/N64MusyXCodec.c)
@ -68,7 +67,6 @@ set(HEADERS
include/amuse/EffectReverb.hpp
include/amuse/EffectChorus.hpp
include/amuse/EffectDelay.hpp
include/amuse/SurroundProfiles.hpp
include/amuse/ContainerRegistry.hpp
include/amuse/Common.hpp
include/amuse/amuse.hpp

View File

@ -2,32 +2,51 @@
#define __AMUSE_EMITTER_HPP__
#include "Entity.hpp"
#include "Common.hpp"
#include <memory>
#include <cmath>
#include <cfloat>
namespace amuse
{
class Voice;
class Listener;
using Vector3f = float[3];
static float Dot(const Vector3f& a, const Vector3f& b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; }
static float Length(const Vector3f& a)
{
if (std::fabs(a[0]) <= FLT_EPSILON && std::fabs(a[1]) <= FLT_EPSILON && std::fabs(a[2]) <= FLT_EPSILON)
return 0.f;
return std::sqrt(Dot(a, a));
}
/** Voice wrapper with positional-3D level control */
class Emitter : public Entity
{
std::shared_ptr<Voice> m_vox;
Vector3f m_pos = {};
Vector3f m_dir = {};
float m_maxDist;
float m_maxVol = 1.f;
float m_minVol;
float m_falloff;
bool m_doppler;
friend class Engine;
void _destroy();
float _attenuationCurve(float dist) const;
void _update();
public:
~Emitter();
Emitter(Engine& engine, const AudioGroup& group, std::shared_ptr<Voice>&& vox);
Emitter(Engine& engine, const AudioGroup& group, std::shared_ptr<Voice>&& vox,
float maxDist, float minVol, float falloff, bool doppler);
void setPos(const float* pos);
void setDir(const float* dir);
void setMaxDist(float maxDist);
void setMaxVol(float maxVol);
void setMinVol(float minVol);
void setFalloff(float falloff);
void setVectors(const float* pos, const float* dir);
void setMaxVol(float maxVol) { m_maxVol = clamp(0.f, maxVol, 1.f); }
std::shared_ptr<Voice>& getVoice() { return m_vox; }
};

View File

@ -7,9 +7,11 @@
#include <unordered_map>
#include <unordered_set>
#include "Emitter.hpp"
#include "Listener.hpp"
#include "AudioGroupSampleDirectory.hpp"
#include "Sequencer.hpp"
#include "Studio.hpp"
#include "IBackendVoiceAllocator.hpp"
namespace amuse
{
@ -42,6 +44,7 @@ class Engine
std::unordered_map<const AudioGroupData*, std::unique_ptr<AudioGroup>> m_audioGroups;
std::list<std::shared_ptr<Voice>> m_activeVoices;
std::list<std::shared_ptr<Emitter>> m_activeEmitters;
std::list<std::shared_ptr<Listener>> m_activeListeners;
std::list<std::shared_ptr<Sequencer>> m_activeSequencers;
std::list<std::weak_ptr<Studio>> m_activeStudios; /* lifetime dependent on contributing audio entities */
bool m_defaultStudioReady = false;
@ -50,6 +53,7 @@ class Engine
std::linear_congruential_engine<uint32_t, 0x41c64e6d, 0x3039, UINT32_MAX> m_random;
int m_nextVid = 0;
float m_masterVolume = 1.f;
AudioChannelSet m_channelSet = AudioChannelSet::Unknown;
AudioGroup* _addAudioGroup(const AudioGroupData& data, std::unique_ptr<AudioGroup>&& grp);
std::pair<AudioGroup*, const SongGroupIndex*> _findSongGroup(int groupId) const;
@ -94,13 +98,21 @@ public:
/** Start soundFX playing from loaded audio groups, attach to positional emitter */
std::shared_ptr<Emitter> addEmitter(const float* pos, const float* dir, float maxDist, float falloff,
int sfxId, float minVol, float maxVol, std::weak_ptr<Studio> smx);
int sfxId, float minVol, float maxVol, bool doppler,
std::weak_ptr<Studio> smx);
std::shared_ptr<Emitter> addEmitter(const float* pos, const float* dir, float maxDist, float falloff,
int sfxId, float minVol, float maxVol)
int sfxId, float minVol, float maxVol, bool doppler)
{
return addEmitter(pos, dir, maxDist, falloff, sfxId, minVol, maxVol, m_defaultStudio);
return addEmitter(pos, dir, maxDist, falloff, sfxId, minVol, maxVol, doppler, m_defaultStudio);
}
/** Build listener and add to engine's listener list */
std::shared_ptr<Listener> addListener(const float* pos, const float* dir, const float* heading, const float* up,
float frontDiff, float backDiff, float soundSpeed, float volume);
/** Remove listener from engine's listener list */
void removeListener(Listener* listener);
/** Start song playing from loaded audio groups */
std::shared_ptr<Sequencer> seqPlay(int groupId, int songId, const unsigned char* arrData,
std::weak_ptr<Studio> smx);

View File

@ -1,13 +1,27 @@
#ifndef __AMUSE_LISTENER_HPP__
#define __AMUSE_LISTENER_HPP__
#include "Entity.hpp"
#include "amuse/Emitter.hpp"
namespace amuse
{
class Listener : public Entity
class Listener
{
friend class Emitter;
Vector3f m_pos = {};
Vector3f m_dir = {};
Vector3f m_heading = {};
Vector3f m_up = {};
Vector3f m_right = {};
float m_volume;
float m_frontDiff;
float m_backDiff;
float m_soundSpeed;
public:
Listener(float volume, float frontDiff, float backDiff, float soundSpeed)
: m_volume(clamp(0.f, volume, 1.f)), m_frontDiff(frontDiff), m_backDiff(backDiff), m_soundSpeed(soundSpeed) {}
void setVectors(const float* pos, const float* dir, const float* heading, const float* up);
void setVolume(float vol) { m_volume = clamp(0.f, vol, 1.f); }
};
}

View File

@ -1,24 +0,0 @@
#ifndef __AMUSE_SURROUNDPROFILES_HPP__
#define __AMUSE_SURROUNDPROFILES_HPP__
#include "IBackendVoice.hpp"
#include "IBackendVoiceAllocator.hpp"
#include "Emitter.hpp"
namespace amuse
{
struct ReferenceVector;
/** Support class for attenuating channel audio based on speaker 'positions' */
class SurroundProfiles
{
static void SetupRefs(float matOut[8], const ChannelMap& map, const Vector3f& listenEmit,
const ReferenceVector refs[]);
public:
static void SetupMatrix(float matOut[8], const ChannelMap& map, AudioChannelSet set, const Vector3f& emitPos,
const Vector3f& listenPos, const Vector3f& listenDir, const Vector3f& listenUp);
};
}
#endif // __AMUSE_SURROUNDPROFILES_HPP__

View File

@ -33,6 +33,7 @@ class Voice : public Entity
friend class Sequencer;
friend class SoundMacroState;
friend class Envelope;
friend class Emitter;
int m_vid; /**< VoiceID of this voice instance */
bool m_emitter; /**< Voice is part of an Emitter */
std::shared_ptr<Studio> m_studio; /**< Studio this voice outputs to */
@ -62,6 +63,7 @@ class Voice : public Entity
uint32_t m_lastSamplePos = 0; /**< Last sample position (or last loop sample) */
int16_t m_prev1 = 0; /**< DSPADPCM prev sample */
int16_t m_prev2 = 0; /**< DSPADPCM prev-prev sample */
double m_dopplerRatio = 1.0; /**< Current ratio to mix with chromatic pitch for doppler effects */
double m_sampleRate = NativeSampleRate; /**< Current sample rate computed from relative sample key or SETPITCH */
double m_voiceTime = 0.0; /**< Current seconds of voice playback (per-sample resolution) */
uint64_t m_voiceSamples = 0; /**< Count of samples processed over voice's lifetime */
@ -166,8 +168,10 @@ class Voice : public Entity
std::shared_ptr<Voice> _startChildMacro(ObjectId macroId, int macroStep, double ticksPerSec, uint8_t midiKey,
uint8_t midiVel, uint8_t midiMod, bool pushPc = false);
void _panLaw(float coefsOut[8], float frontPan, float backPan, float totalSpan) const;
void _setPan(float pan);
void _setSurroundPan(float span);
void _setChannelCoefs(const float coefs[8]);
void _setPitchWheel(float pitchWheel);
void _notifyCtrlChange(uint8_t ctrl, int8_t val);
@ -231,6 +235,9 @@ public:
/** Set current voice surround-panning immediately */
void setSurroundPan(float span);
/** Set current voice channel coefficients immediately */
void setChannelCoefs(const float coefs[8]);
/** Start volume envelope to specified level */
void startEnvelope(double dur, float vol, const Curve* envCurve);

View File

@ -1,14 +1,24 @@
#include "amuse/Emitter.hpp"
#include "amuse/Listener.hpp"
#include "amuse/Voice.hpp"
#include "amuse/Engine.hpp"
namespace amuse
{
static void Delta(Vector3f& out, const Vector3f& a, const Vector3f& b)
{
out[0] = a[0] - b[0];
out[1] = a[1] - b[1];
out[2] = a[2] - b[2];
}
Emitter::~Emitter() {}
Emitter::Emitter(Engine& engine, const AudioGroup& group, std::shared_ptr<Voice>&& vox)
: Entity(engine, group, vox->getObjectId()), m_vox(std::move(vox))
Emitter::Emitter(Engine& engine, const AudioGroup& group, std::shared_ptr<Voice>&& vox,
float maxDist, float minVol, float falloff, bool doppler)
: Entity(engine, group, vox->getObjectId()), m_vox(std::move(vox)), m_maxDist(maxDist),
m_minVol(clamp(0.f, minVol, 1.f)), m_falloff(clamp(-1.f, falloff, 1.f)), m_doppler(doppler)
{
}
@ -18,15 +28,87 @@ void Emitter::_destroy()
m_vox->kill();
}
void Emitter::setPos(const float* pos) {}
void Emitter::setDir(const float* dir) {}
void Emitter::setMaxDist(float maxDist) {}
void Emitter::setMaxVol(float maxVol) {}
void Emitter::setMinVol(float minVol) {}
void Emitter::setFalloff(float falloff) {}
float Emitter::_attenuationCurve(float dist) const
{
if (dist > m_maxDist)
return 0.f;
float t = dist / m_maxDist;
if (m_falloff < 0.f)
{
float tmp = t * 10.f + 1.f;
tmp = 1.f / (tmp * tmp);
return (1.f + m_falloff) * (-t + 1.f) + -m_falloff * tmp;
}
else if (m_falloff > 0.f)
{
float tmp = (t - 1.f) * 10.f - 1.f;
tmp = -1.f / (tmp * tmp) + 1.f;
return (1.f - m_falloff) * (-t + 1.f) + m_falloff * tmp;
}
else
{
return -t + 1.f;
}
}
void Emitter::_update()
{
float coefs[8] = {};
double avgDopplerRatio = 0.0;
for (auto& listener : m_engine.m_activeListeners)
{
Vector3f listenerToEmitter;
Delta(listenerToEmitter, m_pos, listener->m_pos);
float dist = Length(listenerToEmitter);
float panDist = Dot(listenerToEmitter, listener->m_right);
float frontPan = clamp(-1.f, panDist / listener->m_frontDiff, 1.f);
float backPan = clamp(-1.f, panDist / listener->m_backDiff, 1.f);
float spanDist = -Dot(listenerToEmitter, listener->m_heading);
float span = clamp(-1.f, spanDist > 0.f ? spanDist / listener->m_backDiff :
spanDist / listener->m_frontDiff, 1.f);
/* Calculate attenuation */
float att = _attenuationCurve(dist);
att = (1.f - att) * m_minVol + att * m_maxVol;
/* Apply pan law */
float thisCoefs[8] = {};
m_vox->_panLaw(thisCoefs, frontPan, backPan, span);
/* Take maximum coefficient across listeners */
for (int i=0 ; i<8 ; ++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 */
Vector3f dirDelta;
Delta(dirDelta, listener->m_dir, m_dir);
float sign = -Dot(listener->m_dir, m_dir);
if (listener->m_soundSpeed != 0.f)
avgDopplerRatio += 1.0 + std::copysign(Length(dirDelta), sign) / 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());
}
}
void Emitter::setVectors(const float* pos, const float* dir)
{
for (int i=0 ; i<3 ; ++i)
{
m_pos[i] = pos[i];
m_dir[i] = dir[i];
}
}
}

View File

@ -174,10 +174,13 @@ void Engine::_bringOutYourDead()
void Engine::_on5MsInterval(IBackendVoiceAllocator& engine, double dt)
{
m_channelSet = engine.getAvailableSet();
if (m_midiReader)
m_midiReader->pumpReader(dt);
for (std::shared_ptr<Sequencer>& seq : m_activeSequencers)
seq->advance(dt);
for (std::shared_ptr<Emitter>& emitter : m_activeEmitters)
emitter->_update();
}
void Engine::_onPumpCycleComplete(IBackendVoiceAllocator& engine)
@ -320,7 +323,8 @@ std::shared_ptr<Voice> Engine::fxStart(int sfxId, float vol, float pan, std::wea
/** Start soundFX playing from loaded audio groups, attach to positional emitter */
std::shared_ptr<Emitter> Engine::addEmitter(const float* pos, const float* dir, float maxDist, float falloff,
int sfxId, float minVol, float maxVol, std::weak_ptr<Studio> smx)
int sfxId, float minVol, float maxVol, bool doppler,
std::weak_ptr<Studio> smx)
{
auto search = m_sfxLookup.find(sfxId);
if (search == m_sfxLookup.end())
@ -333,7 +337,8 @@ std::shared_ptr<Emitter> Engine::addEmitter(const float* pos, const float* dir,
std::list<std::shared_ptr<Voice>>::iterator vox =
_allocateVoice(*grp, std::get<1>(search->second), NativeSampleRate, true, true, smx);
auto emitIt = m_activeEmitters.emplace(m_activeEmitters.end(), new Emitter(*this, *grp, std::move(*vox)));
auto emitIt = m_activeEmitters.emplace(m_activeEmitters.end(),
new Emitter(*this, *grp, std::move(*vox), maxDist, minVol, falloff, doppler));
Emitter& ret = *(*emitIt);
ObjectId oid = (grp->getDataFormat() == DataFormat::PC) ? entry->objId : SBig(entry->objId);
@ -345,16 +350,36 @@ std::shared_ptr<Emitter> Engine::addEmitter(const float* pos, const float* dir,
}
(*vox)->setPan(entry->panning);
ret.setPos(pos);
ret.setDir(dir);
ret.setMaxDist(maxDist);
ret.setFalloff(falloff);
ret.setMinVol(minVol);
ret.setVectors(pos, dir);
ret.setMaxVol(maxVol);
return *emitIt;
}
/** Build listener and add to engine's listener list */
std::shared_ptr<Listener> Engine::addListener(const float* pos, const float* dir, const float* heading, const float* up,
float frontDiff, float backDiff, float soundSpeed, float volume)
{
auto listenerIt = m_activeListeners.emplace(m_activeListeners.end(),
new Listener(volume, frontDiff, backDiff, soundSpeed));
Listener& ret = *(*listenerIt);
ret.setVectors(pos, dir, heading, up);
return *listenerIt;
}
/** Remove listener from engine's listener list */
void Engine::removeListener(Listener* listener)
{
for (auto it = m_activeListeners.begin() ; it != m_activeListeners.end() ; ++it)
{
if (it->get() == listener)
{
m_activeListeners.erase(it);
return;
}
}
}
/** Start song playing from loaded audio groups */
std::shared_ptr<Sequencer> Engine::seqPlay(int groupId, int songId, const unsigned char* arrData,
std::weak_ptr<Studio> smx)

View File

@ -0,0 +1,39 @@
#include "amuse/Listener.hpp"
namespace amuse
{
static void Cross(Vector3f& out, const Vector3f& a, const Vector3f& b)
{
out[0] = a[1] * b[2] - a[2] * b[1];
out[1] = a[2] * b[0] - a[0] * b[2];
out[2] = a[0] * b[1] - a[1] * b[0];
}
static float Normalize(Vector3f& out)
{
float dist = Length(out);
if (dist == 0.f)
return 0.f;
out[0] /= dist;
out[1] /= dist;
out[2] /= dist;
return dist;
}
void Listener::setVectors(const float* pos, const float* dir, const float* heading, const float* up)
{
for (int i=0 ; i<3 ; ++i)
{
m_pos[i] = pos[i];
m_dir[i] = dir[i];
m_heading[i] = heading[i];
m_up[i] = up[i];
}
Normalize(m_heading);
Normalize(m_up);
Cross(m_right, m_heading, m_up);
Normalize(m_right);
}
}

View File

@ -1,155 +0,0 @@
#include "amuse/SurroundProfiles.hpp"
#include <cmath>
#include <algorithm>
#include <cfloat>
namespace amuse
{
static float Dot(const Vector3f& a, const Vector3f& b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; }
static float Length(const Vector3f& a)
{
if (std::fabs(a[0]) <= FLT_EPSILON && std::fabs(a[1]) <= FLT_EPSILON && std::fabs(a[2]) <= FLT_EPSILON)
return 0.f;
return std::sqrt(Dot(a, a));
}
static float Normalize(Vector3f& out, const Vector3f& in)
{
out[0] = in[0];
out[1] = in[1];
out[2] = in[2];
float dist = Length(out);
out[0] /= dist;
out[1] /= dist;
out[2] /= dist;
return dist;
}
static void Cross(Vector3f& out, const Vector3f& a, const Vector3f& b)
{
out[0] = a[1] * b[2] - a[2] * b[1];
out[1] = a[2] * b[0] - a[0] * b[2];
out[2] = a[0] * b[1] - a[1] * b[0];
}
class SimpleMatrix
{
Vector3f m_mat[3];
public:
SimpleMatrix(const Vector3f& dir, const Vector3f& up)
{
Vector3f temp;
Normalize(temp, dir);
m_mat[0][1] = temp[0];
m_mat[1][1] = temp[1];
m_mat[2][1] = temp[2];
Normalize(temp, up);
m_mat[0][2] = temp[0];
m_mat[1][2] = temp[1];
m_mat[2][2] = temp[2];
Cross(temp, dir, up);
m_mat[0][0] = temp[0];
m_mat[1][0] = temp[1];
m_mat[2][0] = temp[2];
}
void vecMult(Vector3f& out, const Vector3f& in)
{
out[0] = Dot(m_mat[0], in);
out[1] = Dot(m_mat[1], in);
out[2] = Dot(m_mat[2], in);
}
};
struct ReferenceVector
{
Vector3f vec;
float bias;
bool valid = false;
ReferenceVector() = default;
ReferenceVector(float x, float y, float z, float thres)
{
vec[0] = x;
vec[1] = y;
vec[2] = z;
bias = thres;
valid = true;
}
};
static const ReferenceVector StereoVectors[8] = {
{-0.80901f, 0.58778f, 0.f, 0.3f}, {0.80901f, 0.58778f, 0.f, 0.3f},
};
static const ReferenceVector QuadVectors[8] = {
{-0.70710f, 0.70710f, 0.f, 0.1f},
{0.70710f, 0.70710f, 0.f, 0.1f},
{-0.70710f, -0.70710f, 0.f, 0.1f},
{0.70710f, -0.70710f, 0.f, 0.1f},
};
static const ReferenceVector Sur51Vectors[8] = {
{-0.70710f, 0.70710f, 0.f, 0.1f}, {0.70710f, 0.70710f, 0.f, 0.1f}, {-0.70710f, -0.70710f, 0.f, 0.1f},
{0.70710f, -0.70710f, 0.f, 0.1f}, {0.0f, 1.0f, 0.f, 0.1f}, {0.0f, 1.0f, 0.f, 1.0f},
};
static const ReferenceVector Sur71Vectors[8] = {
{-0.70710f, 0.70710f, 0.f, 0.1f}, {0.70710f, 0.70710f, 0.f, 0.1f}, {-0.70710f, -0.70710f, 0.f, 0.1f},
{0.70710f, -0.70710f, 0.f, 0.1f}, {0.0f, 1.0f, 0.f, 0.1f}, {0.0f, 1.0f, 0.f, 1.0f},
{-1.f, 0.0f, 0.f, 0.1f}, {1.f, 0.0f, 0.f, 0.1f},
};
void SurroundProfiles::SetupRefs(float matOut[8], const ChannelMap& map, const Vector3f& listenEmit,
const ReferenceVector refs[])
{
for (unsigned i = 0; i < map.m_channelCount && i < 8; ++i)
{
matOut[i] = 0.f;
if (map.m_channels[i] == AudioChannel::Unknown)
continue;
const ReferenceVector& refVec = refs[int(map.m_channels[i])];
if (!refVec.valid)
continue;
matOut[i] = std::max(1.f, Dot(listenEmit, refVec.vec) + refVec.bias);
}
}
void SurroundProfiles::SetupMatrix(float matOut[8], const ChannelMap& map, AudioChannelSet set, const Vector3f& emitPos,
const Vector3f& listenPos, const Vector3f& listenHeading, const Vector3f& listenUp)
{
Vector3f listenDelta;
listenDelta[0] = emitPos[0] - listenPos[0];
listenDelta[1] = emitPos[1] - listenPos[1];
listenDelta[2] = emitPos[2] - listenPos[2];
Vector3f listenNorm;
float dist = Normalize(listenNorm, listenDelta);
SimpleMatrix listenerMat(listenHeading, listenUp);
Vector3f listenEmit;
listenerMat.vecMult(listenEmit, listenNorm);
/* Factor for each channel in set */
switch (set)
{
case AudioChannelSet::Stereo:
default:
SetupRefs(matOut, map, listenEmit, StereoVectors);
break;
case AudioChannelSet::Quad:
SetupRefs(matOut, map, listenEmit, QuadVectors);
break;
case AudioChannelSet::Surround51:
SetupRefs(matOut, map, listenEmit, Sur51Vectors);
break;
case AudioChannelSet::Surround71:
SetupRefs(matOut, map, listenEmit, Sur71Vectors);
break;
}
}
}

View File

@ -117,7 +117,7 @@ void Voice::_setTotalPitch(int32_t cents, bool slew)
{
// fprintf(stderr, "PITCH %d %d \n", cents, slew);
int32_t interval = cents - m_curSample->first.m_pitch * 100;
double ratio = std::exp2(interval / 1200.0);
double ratio = std::exp2(interval / 1200.0) * m_dopplerRatio;
m_sampleRate = m_curSample->first.m_sampleRate * ratio;
m_backendVoice->setPitchRatio(ratio, slew);
}
@ -947,46 +947,113 @@ void Voice::setVolume(float vol)
vox->setVolume(vol);
}
void Voice::_panLaw(float coefs[8], float frontPan, float backPan, float totalSpan) const
{
/* -3dB panning law for various channel configs */
switch (m_engine.m_channelSet)
{
case AudioChannelSet::Stereo:
default:
/* Left */
coefs[0] = -frontPan * 0.5f + 0.5f;
/* Right */
coefs[1] = frontPan * 0.5f + 0.5f;
break;
case AudioChannelSet::Quad:
/* Left */
coefs[0] = -frontPan * 0.5f + 0.5f;
coefs[0] *= -totalSpan * 0.5f + 0.5f;
/* Right */
coefs[1] = frontPan * 0.5f + 0.5f;
coefs[1] *= -totalSpan * 0.5f + 0.5f;
/* Back Left */
coefs[2] = -backPan * 0.5f + 0.5f;
coefs[2] *= totalSpan * 0.5f + 0.5f;
/* Back Right */
coefs[3] = backPan * 0.5f + 0.5f;
coefs[3] *= totalSpan * 0.5f + 0.5f;
break;
case AudioChannelSet::Surround51:
/* Left */
coefs[0] = (frontPan <= 0.f) ? -frontPan : 0.f;
coefs[0] *= -totalSpan * 0.5f + 0.5f;
/* Right */
coefs[1] = (frontPan >= 0.f) ? frontPan : 0.f;
coefs[1] *= -totalSpan * 0.5f + 0.5f;
/* Back Left */
coefs[2] = -backPan * 0.5f + 0.5f;
coefs[2] *= totalSpan * 0.5f + 0.5f;
/* Back Right */
coefs[3] = backPan * 0.5f + 0.5f;
coefs[3] *= totalSpan * 0.5f + 0.5f;
/* Center */
coefs[4] = 1.f - std::fabs(frontPan);
coefs[4] *= -totalSpan * 0.5f + 0.5f;
/* LFE */
coefs[5] = 1.f;
break;
case AudioChannelSet::Surround71:
/* Left */
coefs[0] = (frontPan <= 0.f) ? -frontPan : 0.f;
coefs[0] *= (totalSpan <= 0.f) ? -totalSpan : 0.f;
/* Right */
coefs[1] = (frontPan >= 0.f) ? frontPan : 0.f;
coefs[1] *= (totalSpan <= 0.f) ? -totalSpan : 0.f;
/* Back Left */
coefs[2] = -backPan * 0.5f + 0.5f;
coefs[2] *= (totalSpan >= 0.f) ? totalSpan : 0.f;
/* Back Right */
coefs[3] = backPan * 0.5f + 0.5f;
coefs[3] *= (totalSpan >= 0.f) ? totalSpan : 0.f;
/* Center */
coefs[4] = 1.f - std::fabs(frontPan);
coefs[4] *= (totalSpan <= 0.f) ? -totalSpan : 0.f;
/* LFE */
coefs[5] = 1.f;
/* Side Left */
coefs[6] = (backPan <= 0.f) ? -backPan : 0.f;
coefs[6] *= 1.f - std::fabs(totalSpan);
/* Side Right */
coefs[7] = (backPan >= 0.f) ? backPan : 0.f;
coefs[7] *= 1.f - std::fabs(totalSpan);
break;
}
}
void Voice::_setPan(float pan)
{
if (m_emitter)
return;
m_curPan = clamp(-1.f, pan, 1.f);
float totalPan = clamp(-1.f, m_curPan + m_userPan, 1.f);
float totalSpan = clamp(-1.f, m_curSpan + m_userSpan, 1.f);
float coefs[8];
/* Left */
coefs[0] = (totalPan <= 0.f) ? 1.f : (1.f - totalPan);
coefs[0] *= (totalSpan <= 0.f) ? 1.f : (1.f - totalSpan);
/* Right */
coefs[1] = (totalPan >= 0.f) ? 1.f : (1.f + totalPan);
coefs[1] *= (totalSpan <= 0.f) ? 1.f : (1.f - totalSpan);
/* Back Left */
coefs[2] = (totalPan <= 0.f) ? 1.f : (1.f - totalPan);
coefs[2] *= (totalSpan >= 0.f) ? 1.f : (1.f + totalSpan);
/* Back Right */
coefs[3] = (totalPan >= 0.f) ? 1.f : (1.f + totalPan);
coefs[3] *= (totalSpan >= 0.f) ? 1.f : (1.f + totalSpan);
/* Center */
coefs[4] = 1.f - std::fabs(totalPan);
/* LFE */
coefs[5] = 1.f;
/* Side Left */
coefs[6] = (totalPan <= 0.f) ? 1.f : (1.f - totalPan);
coefs[6] *= 1.f - std::fabs(totalSpan);
/* Side Right */
coefs[7] = (totalPan >= 0.f) ? 1.f : (1.f + totalPan);
coefs[7] *= 1.f - std::fabs(totalSpan);
m_backendVoice->setChannelLevels(m_studio->getMaster().m_backendSubmix.get(), coefs, true);
m_backendVoice->setChannelLevels(m_studio->getAuxA().m_backendSubmix.get(), coefs, true);
m_backendVoice->setChannelLevels(m_studio->getAuxB().m_backendSubmix.get(), coefs, true);
float coefs[8] = {};
_panLaw(coefs, totalPan, totalPan, totalSpan);
_setChannelCoefs(coefs);
}
void Voice::setPan(float pan)
@ -1011,6 +1078,20 @@ void Voice::setSurroundPan(float span)
vox->setSurroundPan(span);
}
void Voice::_setChannelCoefs(const float coefs[8])
{
m_backendVoice->setChannelLevels(m_studio->getMaster().m_backendSubmix.get(), coefs, true);
m_backendVoice->setChannelLevels(m_studio->getAuxA().m_backendSubmix.get(), coefs, true);
m_backendVoice->setChannelLevels(m_studio->getAuxB().m_backendSubmix.get(), coefs, true);
}
void Voice::setChannelCoefs(const float coefs[8])
{
_setChannelCoefs(coefs);
for (std::shared_ptr<Voice>& vox : m_childVoices)
vox->setChannelCoefs(coefs);
}
void Voice::startEnvelope(double dur, float vol, const Curve* envCurve)
{
m_envelopeTime = 0.f;