The OpenAL port kept the whole authored audio model and then threw most of
its output away. Every frame the engine computed a distance-attenuation
curve, a high-frequency rolloff, doppler cents, a reverb level and a
front/rear placement, and every one of those consumers had been commented
out when the two AWE32 cards were replaced. What reached the speakers was
OpenAL's own defaults instead: a straight-line fade to silence, no
filtering, doppler at the wrong constants with an inverted velocity, no
reverb, and every cockpit sound dead centre.
Restored, per AUDIO.INI, which is byte-identical to the file that shipped
in August 1995:
- the authored knee/rolloff distance curve, replacing AL_LINEAR_DISTANCE.
This also un-blinds the transient cull, the voice-steal weighting and
the mix ducking, which all key off it and were treating far sources as
full presence
- the CC7 squared volume law; writing the scale linearly ran everything
about 6 dB hot at mid-scale
- brightness and distance muffling, and the wet-exterior/dry-cockpit
reverb split, both through a new OpenAL EFX bridge
- doppler on the moving-source path only, as the original had it
- front/rear placement from the authored position enum
The larger find is that AL_PITCH was never called anywhere in the tree, so
the entire pitch chain was inert - not only doppler but pitch_mix_offset,
which our own sequences author 97 times. Doppler alone would have changed
nothing audible.
Note pitch is applied for parity with the BT engine but is identity here:
our content predates NoteAudioControlID, so every source runs at note 60.
Builds clean on VS2022 Release|Win32. Smoke-tested against vRIO on COM1 -
reaches gameplay and holds a steady frame loop. ALC_EXT_EFX is present on
the build machine with all nine entry points, so the filter and reverb work
is live rather than inert. Not yet listened to on the pod, which is the
real test: the volume law changes the level of everything.
docs/SOUND.md documents the original two-card quadraphonic design, where
the surviving original assets are, and what remains.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2402 lines
66 KiB
C++
2402 lines
66 KiB
C++
#include "mungal4.h"
|
|
#pragma hdrstop
|
|
|
|
#include "l4audio.h"
|
|
#include "l4audefx.h"
|
|
#include "l4audlvl.h"
|
|
#include "l4app.h"
|
|
#include "l4audrnd.h"
|
|
#include "..\munga\namelist.h"
|
|
#include "..\munga\player.h"
|
|
#include "..\rp\vtv.h"
|
|
|
|
//
|
|
// FIDELITY (docs/SOUND.md): the AWE32 played each patch at the requested MIDI
|
|
// note relative to the sample root (60). RP's authored 4.10 content predates
|
|
// NoteAudioControlID -- its AudioControlID enum stops at AttackTimeAudioControlID
|
|
// -- so every source runs at DEFAULT_NOTE and this factor is 1.0 today. It is
|
|
// applied anyway so the pitch path is complete if authored notes ever appear,
|
|
// and to keep the shared MUNGA engine in step with the BT tree.
|
|
//
|
|
static inline float RPNotePitchFactor(int note_value)
|
|
{
|
|
return (float)pow(2.0, ((double)note_value - 60.0) / 12.0);
|
|
}
|
|
|
|
//
|
|
// FIDELITY (docs/SOUND.md F12): the authored DirectPatchSource `position=`
|
|
// enum picked a SOUND CARD (front pair for Front/FrontLeft/FrontRight, rear
|
|
// pair for Rear/RearLeft/RearRight) and a MIDI pan (CC10 centre/left/right).
|
|
// The port read audioPosition from the stream and then discarded it -- every
|
|
// cockpit sound played dead centre because SetupPatch pins each source
|
|
// AL_SOURCE_RELATIVE at the origin.
|
|
//
|
|
// Sources are listener-relative and no AL_ORIENTATION is ever set, so OpenAL's
|
|
// default listener frame applies: facing -Z with +Y up. Front is therefore
|
|
// -Z, rear +Z, left -X, right +X; the corner values combine both at equal
|
|
// weight. RP's own content only ever authors Front (28 sites) and Rear (13),
|
|
// but the corners are mapped for completeness since the enum allows them.
|
|
//
|
|
static void RPGetDirectPatchPosition(DirectPatchPosition p, float *x, float *z)
|
|
{
|
|
const float diag = 0.7071068f; // unit vector split across both axes
|
|
|
|
switch (p)
|
|
{
|
|
case FrontDirectPatchPosition: *x = 0.0f; *z = -1.0f; break;
|
|
case RearDirectPatchPosition: *x = 0.0f; *z = 1.0f; break;
|
|
case FrontLeftDirectPatchPosition: *x = -diag; *z = -diag; break;
|
|
case FrontRightDirectPatchPosition: *x = diag; *z = -diag; break;
|
|
case RearLeftDirectPatchPosition: *x = -diag; *z = diag; break;
|
|
case RearRightDirectPatchPosition: *x = diag; *z = diag; break;
|
|
default: *x = 0.0f; *z = 0.0f; break;
|
|
}
|
|
}
|
|
|
|
//#############################################################################
|
|
//####################### L4AudioSpatialization #########################
|
|
//#############################################################################
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
L4AudioSpatialization::L4AudioSpatialization()
|
|
{
|
|
azimuthOfSource = 0.0f;
|
|
|
|
frontLeftScale = 0.0f;
|
|
frontRightScale = 0.0f;
|
|
rearLeftScale = 0.0f;
|
|
rearRightScale = 0.0f;
|
|
|
|
frontLeftDelay = 0.0f;
|
|
frontRightDelay = 0.0f;
|
|
rearLeftDelay = 0.0f;
|
|
rearRightDelay = 0.0f;
|
|
|
|
quadrant = Quadrant1;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
L4AudioSpatialization::~L4AudioSpatialization()
|
|
{
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
L4AudioSpatialization::TestInstance() const
|
|
{
|
|
return True;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioSpatialization::CalculateSpatialization(Radian azimuth_of_source)
|
|
{
|
|
azimuthOfSource = azimuth_of_source;
|
|
|
|
//
|
|
//---------------------------------------------------------------
|
|
// Make azimuth usable for channel volume calculations
|
|
//---------------------------------------------------------------
|
|
//
|
|
#define DEG_90 (90.0f*(RAD_PER_DEG))
|
|
#define DEG_180 (180.0f*(RAD_PER_DEG))
|
|
#define DEG_360 (360.0f*(RAD_PER_DEG))
|
|
|
|
azimuthOfSource = DEG_180 + azimuthOfSource;
|
|
if (azimuthOfSource > DEG_180)
|
|
azimuthOfSource = azimuthOfSource - DEG_360;
|
|
Verify(
|
|
azimuthOfSource <= DEG_180 &&
|
|
azimuthOfSource >= -DEG_180
|
|
);
|
|
|
|
//
|
|
//---------------------------------------------------------------
|
|
// Channel volume scale and ITD calculations
|
|
//---------------------------------------------------------------
|
|
//
|
|
|
|
//
|
|
// Get audio head constants
|
|
//
|
|
L4AudioRenderer *audio_renderer;
|
|
AudioHead *audio_head;
|
|
|
|
Check(application);
|
|
audio_renderer =
|
|
Cast_Object(L4AudioRenderer*, application->GetAudioRenderer());
|
|
Check(audio_renderer);
|
|
audio_head = audio_renderer->GetAudioHead();
|
|
Check(audio_head);
|
|
|
|
//
|
|
// Init channel volume scale variables
|
|
//
|
|
const Radian azimuth_max = 45.0*RAD_PER_DEG; // HACK - should come from audio.ini
|
|
Scalar tangent_ratio;
|
|
|
|
frontLeftScale = 0.0f;
|
|
frontRightScale = 0.0f;
|
|
rearLeftScale = 0.0f;
|
|
rearRightScale = 0.0f;
|
|
|
|
//
|
|
// Init ITD parameters
|
|
//
|
|
const Scalar itd_delay = audio_head->GetITDDifference();
|
|
|
|
frontLeftDelay = 0.0f;
|
|
frontRightDelay = 0.0f;
|
|
rearLeftDelay = 0.0f;
|
|
rearRightDelay = 0.0f;
|
|
|
|
//
|
|
//---------------------------------------------------------------
|
|
// Quadrant 1
|
|
//---------------------------------------------------------------
|
|
//
|
|
if (
|
|
azimuthOfSource > -azimuth_max &&
|
|
azimuthOfSource < azimuth_max
|
|
)
|
|
{
|
|
quadrant = Quadrant1;
|
|
|
|
// volume scale
|
|
Verify(!Small_Enough(tan(azimuth_max)));
|
|
tangent_ratio =
|
|
(tan(azimuthOfSource) / tan(azimuth_max)) * 0.5f;
|
|
|
|
frontLeftScale = Sqrt(0.5f + tangent_ratio);
|
|
frontRightScale = Sqrt(0.5f - tangent_ratio);
|
|
Verify(frontLeftScale >= 0.0f && frontLeftScale <= 1.0f);
|
|
Verify(frontRightScale >= 0.0f && frontRightScale <= 1.0f);
|
|
|
|
// pitch offset
|
|
if (azimuthOfSource > 0.0f)
|
|
{
|
|
frontLeftDelay = itd_delay * tangent_ratio * 2.0f;
|
|
}
|
|
else
|
|
{
|
|
frontRightDelay = itd_delay * -tangent_ratio * 2.0f;
|
|
}
|
|
}
|
|
//
|
|
//---------------------------------------------------------------
|
|
// Quadrant 2
|
|
//---------------------------------------------------------------
|
|
//
|
|
else if (
|
|
azimuthOfSource > azimuth_max &&
|
|
azimuthOfSource < (DEG_180 - azimuth_max)
|
|
)
|
|
{
|
|
quadrant = Quadrant2;
|
|
|
|
// volume scale
|
|
azimuthOfSource = azimuthOfSource - DEG_90;
|
|
|
|
Verify(!Small_Enough(tan(DEG_90 - azimuth_max)));
|
|
tangent_ratio =
|
|
(tan(azimuthOfSource) / tan(DEG_90 - azimuth_max)) * 0.5f;
|
|
|
|
rearLeftScale = Sqrt(0.5f + tangent_ratio);
|
|
frontLeftScale = Sqrt(0.5f - tangent_ratio);
|
|
Verify(rearLeftScale >= 0.0f && rearLeftScale <= 1.0f);
|
|
Verify(frontLeftScale >= 0.0f && frontLeftScale <= 1.0f);
|
|
|
|
// pitch offset
|
|
if (azimuthOfSource > 0.0f)
|
|
{
|
|
rearLeftDelay = itd_delay * tangent_ratio * 2.0f;
|
|
}
|
|
else
|
|
{
|
|
frontLeftDelay = itd_delay * -tangent_ratio * 2.0f;
|
|
}
|
|
}
|
|
//
|
|
//---------------------------------------------------------------
|
|
// Quadrant 3
|
|
//---------------------------------------------------------------
|
|
//
|
|
else if (
|
|
azimuthOfSource > (DEG_180 - azimuth_max) ||
|
|
azimuthOfSource < (-DEG_180 + azimuth_max)
|
|
)
|
|
{
|
|
quadrant = Quadrant3;
|
|
|
|
// volume scale
|
|
azimuthOfSource = azimuthOfSource - DEG_180;
|
|
|
|
Verify(!Small_Enough(tan(azimuth_max)));
|
|
tangent_ratio =
|
|
(tan(azimuthOfSource) / tan(azimuth_max)) * 0.5f;
|
|
|
|
rearRightScale = Sqrt(0.5f + tangent_ratio);
|
|
rearLeftScale = Sqrt(0.5f - tangent_ratio);
|
|
Verify(rearRightScale >= 0.0f && rearRightScale <= 1.0f);
|
|
Verify(rearLeftScale >= 0.0f && rearLeftScale <= 1.0f);
|
|
|
|
// pitch offset
|
|
if (azimuthOfSource > -azimuth_max)
|
|
{
|
|
rearLeftDelay = -(itd_delay * tangent_ratio * 2.0f);
|
|
}
|
|
else
|
|
{
|
|
rearRightDelay = -(itd_delay * -tangent_ratio * 2.0f);
|
|
}
|
|
|
|
#if 0
|
|
Tell(
|
|
"azimuthOfSource " << azimuthOfSource <<
|
|
" rearRightDelay " << rearRightDelay <<
|
|
" rearLeftDelay " << rearLeftDelay <<
|
|
"\n"
|
|
);
|
|
#endif
|
|
}
|
|
//
|
|
//---------------------------------------------------------------
|
|
// Quadrant 4
|
|
//---------------------------------------------------------------
|
|
//
|
|
else
|
|
{
|
|
quadrant = Quadrant4;
|
|
|
|
Verify(
|
|
azimuthOfSource > (-DEG_180 + azimuth_max) &&
|
|
azimuthOfSource < -azimuth_max
|
|
);
|
|
|
|
// volume scale
|
|
azimuthOfSource = azimuthOfSource + DEG_90;
|
|
|
|
Verify(!Small_Enough(tan(DEG_90 - azimuth_max)));
|
|
tangent_ratio =
|
|
(tan(azimuthOfSource) / tan(DEG_90 - azimuth_max)) * 0.5f;
|
|
|
|
frontRightScale = Sqrt(0.5f + tangent_ratio);
|
|
rearRightScale = Sqrt(0.5f - tangent_ratio);
|
|
Verify(frontRightScale >= 0.0f && frontRightScale <= 1.0f);
|
|
Verify(rearRightScale >= 0.0f && rearRightScale <= 1.0f);
|
|
|
|
// pitch offset
|
|
if (azimuthOfSource > 0.0f)
|
|
{
|
|
frontRightDelay = itd_delay * tangent_ratio * 2.0f;
|
|
}
|
|
else
|
|
{
|
|
rearRightDelay = itd_delay * -tangent_ratio * 2.0f;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
Tell(
|
|
"FL " << frontLeftScale <<
|
|
" FR " << frontRightScale <<
|
|
" RL " << rearLeftScale <<
|
|
" RR " << rearRightScale <<
|
|
"\n"
|
|
);
|
|
#endif
|
|
|
|
#if 0
|
|
Tell(
|
|
"Quadrant " << quadrant <<
|
|
" FL " << frontLeftDelay <<
|
|
" FR " << frontRightDelay <<
|
|
" RL " << rearLeftDelay <<
|
|
" RR " << rearRightDelay <<
|
|
"\n"
|
|
);
|
|
#endif
|
|
|
|
Verify(frontLeftScale >= 0.0f && frontLeftScale <= 1.0f);
|
|
Verify(frontRightScale >= 0.0f && frontRightScale <= 1.0f);
|
|
Verify(rearLeftScale >= 0.0f && rearLeftScale <= 1.0f);
|
|
Verify(rearRightScale >= 0.0f && rearRightScale <= 1.0f);
|
|
}
|
|
|
|
//#############################################################################
|
|
//######################### L4AudioLocation #############################
|
|
//#############################################################################
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
L4AudioLocation::L4AudioLocation(
|
|
PlugStream *stream,
|
|
Entity *entity
|
|
):
|
|
AudioLocation(stream, entity)
|
|
{
|
|
L4AudioLocationX();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioLocation::L4AudioLocationX()
|
|
{
|
|
//
|
|
// Get audio renderer frame duration
|
|
//
|
|
Check(application)
|
|
L4AudioRenderer *audio_renderer =
|
|
Cast_Object(L4AudioRenderer*, application->GetAudioRenderer());
|
|
Check(audio_renderer);
|
|
|
|
Time frame_duration =
|
|
audio_renderer->GetCalibrationFrameDuration();
|
|
|
|
//
|
|
// Init ITD pitch offset variables
|
|
//
|
|
lastITDFrameTime = Now();
|
|
lastITDFrameTime -= frame_duration;
|
|
|
|
currentFrontLeftDelay = 0.0f;
|
|
currentFrontRightDelay = 0.0f;
|
|
currentRearLeftDelay = 0.0f;
|
|
currentRearRightDelay = 0.0f;
|
|
|
|
frontLeftITDPitchOffset = 0.0f;
|
|
frontRightITDPitchOffset = 0.0f;
|
|
rearLeftITDPitchOffset = 0.0f;
|
|
rearRightITDPitchOffset = 0.0f;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
L4AudioLocation::~L4AudioLocation()
|
|
{
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
L4AudioLocation::TestInstance() const
|
|
{
|
|
AudioLocation::TestInstance();
|
|
Check(&spatialization);
|
|
return True;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioLocation::UpdateSpatialModelImplementation(AudioHead *audio_head)
|
|
{
|
|
Check(this);
|
|
Check(audio_head);
|
|
|
|
//
|
|
//---------------------------------------------------------------
|
|
// Call inherited method
|
|
//---------------------------------------------------------------
|
|
//
|
|
AudioLocation::UpdateSpatialModelImplementation(audio_head);
|
|
|
|
//
|
|
//---------------------------------------------------------------
|
|
// Catch case of head == source
|
|
//---------------------------------------------------------------
|
|
//
|
|
if (IsHeadSource())
|
|
{
|
|
spatialization.frontLeftScale = 1.0f;
|
|
spatialization.frontRightScale = 1.0f;
|
|
spatialization.rearLeftScale = 1.0f;
|
|
spatialization.rearRightScale = 1.0f;
|
|
|
|
frontLeftITDPitchOffset = 0.0f;
|
|
frontRightITDPitchOffset = 0.0f;
|
|
rearLeftITDPitchOffset = 0.0f;
|
|
rearRightITDPitchOffset = 0.0f;
|
|
return;
|
|
}
|
|
|
|
//
|
|
//---------------------------------------------------------------
|
|
// Calculate channel spatialization
|
|
//---------------------------------------------------------------
|
|
//
|
|
spatialization.CalculateSpatialization(GetAzimuthOfSource());
|
|
|
|
//
|
|
//---------------------------------------------------------------
|
|
// Calculate ITD pitch offsets
|
|
//---------------------------------------------------------------
|
|
//
|
|
const Scalar itd_pitch_offset_constant =
|
|
0.003831f / 0.000002f; // period / delay // HACK - should come from audio head
|
|
|
|
Time itd_delta_time;
|
|
|
|
itd_delta_time = Now();
|
|
itd_delta_time -= lastITDFrameTime;
|
|
lastITDFrameTime = Now();
|
|
|
|
frontLeftITDPitchOffset = 0.0f;
|
|
frontRightITDPitchOffset = 0.0f;
|
|
rearLeftITDPitchOffset = 0.0f;
|
|
rearRightITDPitchOffset = 0.0f;
|
|
|
|
if (!Small_Enough((Scalar)itd_delta_time))
|
|
{
|
|
if (currentFrontLeftDelay != spatialization.frontLeftDelay)
|
|
{
|
|
Verify(!Small_Enough((Scalar)itd_delta_time));
|
|
|
|
frontLeftITDPitchOffset =
|
|
itd_pitch_offset_constant *
|
|
(spatialization.frontLeftDelay - currentFrontLeftDelay) /
|
|
(Scalar)itd_delta_time;
|
|
currentFrontLeftDelay = spatialization.frontLeftDelay;
|
|
}
|
|
if (currentFrontRightDelay != spatialization.frontRightDelay)
|
|
{
|
|
Verify(!Small_Enough((Scalar)itd_delta_time));
|
|
|
|
frontRightITDPitchOffset =
|
|
itd_pitch_offset_constant *
|
|
(spatialization.frontRightDelay - currentFrontRightDelay) /
|
|
(Scalar)itd_delta_time;
|
|
currentFrontRightDelay = spatialization.frontRightDelay;
|
|
}
|
|
if (currentRearLeftDelay != spatialization.rearLeftDelay)
|
|
{
|
|
Verify(!Small_Enough((Scalar)itd_delta_time));
|
|
|
|
rearLeftITDPitchOffset =
|
|
itd_pitch_offset_constant *
|
|
(spatialization.rearLeftDelay - currentRearLeftDelay) /
|
|
(Scalar)itd_delta_time;
|
|
currentRearLeftDelay = spatialization.rearLeftDelay;
|
|
}
|
|
if (currentRearRightDelay != spatialization.rearRightDelay)
|
|
{
|
|
Verify(!Small_Enough((Scalar)itd_delta_time));
|
|
|
|
rearRightITDPitchOffset =
|
|
itd_pitch_offset_constant *
|
|
(spatialization.rearRightDelay - currentRearRightDelay) /
|
|
(Scalar)itd_delta_time;
|
|
currentRearRightDelay = spatialization.rearRightDelay;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
Tell(
|
|
"Quadrant " << spatialization.quadrant <<
|
|
" FL " << frontLeftITDPitchOffset <<
|
|
" FR " << frontRightITDPitchOffset <<
|
|
" RL " << rearLeftITDPitchOffset <<
|
|
" RR " << rearRightITDPitchOffset <<
|
|
"\n"
|
|
);
|
|
#endif
|
|
|
|
//
|
|
//---------------------------------------------------------------
|
|
// Check for source "between ears"
|
|
//---------------------------------------------------------------
|
|
//
|
|
const Scalar ear_radius =
|
|
audio_head->GetDistanceBetweenEars() * 0.5;
|
|
Scalar distance_to_source_listener_plane =
|
|
GetDistanceToSourceListenerPlane();
|
|
|
|
if (distance_to_source_listener_plane < ear_radius)
|
|
{
|
|
Verify(!Small_Enough(ear_radius));
|
|
|
|
const Scalar proportion =
|
|
0.5f *
|
|
(ear_radius - distance_to_source_listener_plane) /
|
|
ear_radius;
|
|
|
|
Scalar powerA, powerB;
|
|
|
|
#if DEBUG_LEVEL>0
|
|
if (!(proportion >= 0.0f && proportion <= 1.0f))
|
|
{
|
|
Dump(ear_radius);
|
|
Dump(distance_to_source_listener_plane);
|
|
Dump(proportion);
|
|
}
|
|
Verify(proportion >= 0.0f && proportion <= 1.0f);
|
|
#endif
|
|
|
|
powerA = Sqrt(1.0f - proportion);
|
|
powerB = Sqrt(proportion);
|
|
Verify(powerA >= 0.0f && powerA <= 1.0f);
|
|
Verify(powerB >= 0.0f && powerB <= 1.0f);
|
|
|
|
switch (spatialization.quadrant)
|
|
{
|
|
case L4AudioSpatialization::Quadrant1:
|
|
spatialization.rearLeftScale = spatialization.frontLeftScale * powerB;
|
|
spatialization.rearRightScale = spatialization.frontRightScale * powerB;
|
|
|
|
spatialization.frontLeftScale *= powerA;
|
|
spatialization.frontRightScale *= powerA;
|
|
break;
|
|
|
|
case L4AudioSpatialization::Quadrant2:
|
|
spatialization.rearRightScale = spatialization.rearLeftScale * powerB;
|
|
spatialization.frontRightScale = spatialization.frontLeftScale * powerB;
|
|
|
|
spatialization.rearLeftScale *= powerA;
|
|
spatialization.frontLeftScale *= powerA;
|
|
break;
|
|
|
|
case L4AudioSpatialization::Quadrant3:
|
|
spatialization.frontRightScale = spatialization.rearRightScale * powerB;
|
|
spatialization.frontLeftScale = spatialization.rearLeftScale * powerB;
|
|
|
|
spatialization.rearRightScale *= powerA;
|
|
spatialization.rearLeftScale *= powerA;
|
|
break;
|
|
|
|
case L4AudioSpatialization::Quadrant4:
|
|
spatialization.frontLeftScale = spatialization.frontRightScale * powerB;
|
|
spatialization.rearLeftScale = spatialization.rearRightScale * powerB;
|
|
|
|
spatialization.frontRightScale *= powerA;
|
|
spatialization.rearRightScale *= powerA;
|
|
break;
|
|
}
|
|
|
|
#if 0
|
|
Tell(
|
|
"FL " << spatialization.frontLeftScale <<
|
|
" FR " << spatialization.frontRightScale <<
|
|
" RL " << spatialization.rearLeftScale <<
|
|
" RR " << spatialization.rearRightScale <<
|
|
"\n"
|
|
);
|
|
#endif
|
|
}
|
|
|
|
Verify(spatialization.frontLeftScale >= 0.0f && spatialization.frontLeftScale <= 1.0f);
|
|
Verify(spatialization.frontRightScale >= 0.0f && spatialization.frontRightScale <= 1.0f);
|
|
Verify(spatialization.rearLeftScale >= 0.0f && spatialization.rearLeftScale <= 1.0f);
|
|
Verify(spatialization.rearRightScale >= 0.0f && spatialization.rearRightScale <= 1.0f);
|
|
}
|
|
|
|
//#############################################################################
|
|
//######################### AudioChannelSet #############################
|
|
//#############################################################################
|
|
|
|
//AudioChannelSet AudioChannelSet::Null;
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
/*AudioChannelSet::AudioChannelSet()
|
|
{
|
|
for (int i = 0; i < AudioChannelSetSize; i++)
|
|
{
|
|
channels[i] = NULL;
|
|
enables[i] = False;
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
AudioChannelSet::TestInstance() const
|
|
{
|
|
for (int i = 0; i < AudioChannelSetSize; i++)
|
|
{
|
|
if (channels[i] != NULL)
|
|
{
|
|
Verify(enables[i] == True);
|
|
Check(channels[i]);
|
|
}
|
|
}
|
|
return True;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioChannelSet::EnableAll()
|
|
{
|
|
//
|
|
// Must set all channels to null and all enables to True
|
|
//
|
|
for (int i = 0; i < AudioChannelSetSize; i++)
|
|
{
|
|
channels[i] = NULL;
|
|
enables[i] = True;
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioChannelSet::ReleaseAll()
|
|
{
|
|
//
|
|
// Must release all channels without changing enable values
|
|
//
|
|
for (int i = 0; i < AudioChannelSetSize; i++)
|
|
{
|
|
if (channels[i] != NULL)
|
|
{
|
|
Check(channels[i]);
|
|
channels[i]->Release();
|
|
channels[i] = NULL;
|
|
}
|
|
}
|
|
}*/
|
|
|
|
//#############################################################################
|
|
//########################## L4AudioSource ##############################
|
|
//#############################################################################
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
L4AudioSource::L4AudioSource(
|
|
PlugStream *stream,
|
|
Entity *entity
|
|
):
|
|
AudioSource(stream, entity)
|
|
{
|
|
channelSet.count = GetAudioVoiceCount();
|
|
L4AudioSourceX();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioSource::BuildFromPage(
|
|
PlugStream *stream,
|
|
NameList *name_list,
|
|
ClassID class_ID,
|
|
ObjectID object_ID
|
|
)
|
|
{
|
|
AudioSource::BuildFromPage(stream, name_list, class_ID, object_ID);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioSource::L4AudioSourceX()
|
|
{
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
L4AudioSource::~L4AudioSource()
|
|
{
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
L4AudioSource::TestInstance() const
|
|
{
|
|
AudioSource::TestInstance();
|
|
Check(&channelSet);
|
|
return True;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioSource::StopMessageImplementation()
|
|
{
|
|
Check(this);
|
|
|
|
AudioSource::StopMessageImplementation();
|
|
|
|
//
|
|
// Request stop from renderer
|
|
//
|
|
Check(application);
|
|
Cast_Object(L4AudioRenderer*, application->GetAudioRenderer())->
|
|
StopRequest(this);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioSource::SetAudioChannelSet(const SourceSet &audio_channel_set)
|
|
{
|
|
Check(this);
|
|
channelSet = audio_channel_set;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
SourceSet*
|
|
L4AudioSource::GetAudioChannelSet()
|
|
{
|
|
Check(this);
|
|
return &channelSet;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4AudioSource::ReleaseChannels()
|
|
{
|
|
Check(this);
|
|
//How do I release the sources, man?
|
|
application->GetAudioRenderer()->ReleaseSourceSet(channelSet);
|
|
|
|
for (int i=0; i < channelSet.count; i++)
|
|
{
|
|
channelSet.sources[i] = 0;
|
|
}
|
|
//channelSet.ReleaseAll();
|
|
}
|
|
|
|
//#############################################################################
|
|
//######################## DirectPatchSource ############################
|
|
//#############################################################################
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
DirectPatchSource::DirectPatchSource(
|
|
PlugStream *stream,
|
|
Entity *entity
|
|
):
|
|
L4AudioSource(stream, entity)
|
|
{
|
|
MemoryStream_Read(stream, &audioPosition);
|
|
// channel = NULL;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
DirectPatchSource::BuildFromPage(
|
|
PlugStream *stream,
|
|
NameList *name_list,
|
|
ClassID class_ID,
|
|
ObjectID object_ID
|
|
)
|
|
{
|
|
L4AudioSource::BuildFromPage(stream, name_list, class_ID, object_ID);
|
|
MEM_STRM_WRITE_ENTRY(*stream, name_list, Enumeration, position);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
DirectPatchSource::~DirectPatchSource()
|
|
{
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
DirectPatchSource::TestInstance() const
|
|
{
|
|
L4AudioSource::TestInstance();
|
|
/* if (channel != NULL)
|
|
{
|
|
Check(channel);
|
|
}*/
|
|
return True;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
DirectPatchSource::StartMessageImplementation()
|
|
{
|
|
Check(this);
|
|
|
|
L4AudioSource::StartMessageImplementation();
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Init channel set to requested channels
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
/* channelSet = AudioChannelSet::Null;
|
|
switch (audioPosition)
|
|
{
|
|
case FrontDirectPatchPosition:
|
|
case FrontLeftDirectPatchPosition:
|
|
case FrontRightDirectPatchPosition:
|
|
channelSet.EnableFrontLeft(True);
|
|
break;
|
|
|
|
case RearDirectPatchPosition:
|
|
case RearLeftDirectPatchPosition:
|
|
case RearRightDirectPatchPosition:
|
|
channelSet.EnableRearLeft(True);
|
|
break;
|
|
}*/
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Request start from renderer
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Check(application);
|
|
Cast_Object(L4AudioRenderer*, application->GetAudioRenderer())->
|
|
StartRequest(this);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
DirectPatchSource::StartImplementation()
|
|
{
|
|
Check(this);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Call inherited method
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
L4AudioSource::StartImplementation();
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Remember the channel
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
/* Verify(channel == NULL);
|
|
switch (audioPosition)
|
|
{
|
|
case FrontDirectPatchPosition:
|
|
case FrontLeftDirectPatchPosition:
|
|
case FrontRightDirectPatchPosition:
|
|
channel = channelSet.GetFrontLeft();
|
|
break;
|
|
|
|
case RearDirectPatchPosition:
|
|
case RearLeftDirectPatchPosition:
|
|
case RearRightDirectPatchPosition:
|
|
channel = channelSet.GetRearLeft();
|
|
break;
|
|
}
|
|
Check(channel);*/
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Setup the channel
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
ExecuteWatchers();
|
|
|
|
//
|
|
// Turn the volume down while setting up the channel
|
|
//
|
|
// channel->SendController(MIDI_VOLUME_CONTROL, MIDI_MIN_CONTROL_VALUE);
|
|
|
|
//
|
|
// Set the channel to the bank and program
|
|
//
|
|
PatchResource *patch_resource;
|
|
|
|
patch_resource = Cast_Object(PatchResource*, GetAudioResource());
|
|
Check(patch_resource);
|
|
patch_resource->SetDistance(GetDistanceToSource());
|
|
patch_resource->SetupPatch(channelSet);
|
|
|
|
//
|
|
// FIDELITY (docs/SOUND.md F12): place the source per the authored position
|
|
// enum. SetupPatch has just pinned it AL_SOURCE_RELATIVE at the origin, so
|
|
// this must run after it. With AL_NONE as the distance model the unit
|
|
// radius costs no attenuation -- it only supplies direction.
|
|
//
|
|
{
|
|
float pos_x, pos_z;
|
|
|
|
RPGetDirectPatchPosition(audioPosition, &pos_x, &pos_z);
|
|
for (int i = 0; i < channelSet.count; i++)
|
|
{
|
|
alSource3f(channelSet.sources[i], AL_POSITION, pos_x, 0.0f, pos_z);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set the channel to default control values
|
|
//
|
|
// channel->SendController(MIDI_REVERB_CONTROL, MIDI_MIN_CONTROL_VALUE);
|
|
// channel->SendController(MIDI_CHORUS_CONTROL, MIDI_MIN_CONTROL_VALUE);
|
|
|
|
/* switch (audioPosition)
|
|
{
|
|
case FrontDirectPatchPosition:
|
|
case RearDirectPatchPosition:
|
|
channel->SendController(MIDI_PAN_CONTROL, MIDI_CENTER_PAN_VALUE);
|
|
break;
|
|
case FrontLeftDirectPatchPosition:
|
|
case RearLeftDirectPatchPosition:
|
|
channel->SendController(MIDI_PAN_CONTROL, MIDI_LEFT_PAN_VALUE);
|
|
break;
|
|
case FrontRightDirectPatchPosition:
|
|
case RearRightDirectPatchPosition:
|
|
channel->SendController(MIDI_PAN_CONTROL, MIDI_RIGHT_PAN_VALUE);
|
|
break;
|
|
}*/
|
|
|
|
//
|
|
// Set midi history to default control values
|
|
//
|
|
lastMIDIVolume = MIDI_MIN_CONTROL_VALUE;
|
|
lastMIDIPitchBend = 0; // Set by force update
|
|
lastMIDIFilterCutoff = MIDI_MAX_CONTROL_VALUE; // Set by patch load
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Calculate attack time, velocity, and execute model
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
MIDIValue midi_velocity =
|
|
CalculateSourceAttackVolumeScale() *
|
|
(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;
|
|
|
|
if (UseSourceAttackTime())
|
|
{
|
|
MIDINRPNValue midi_attack_time =
|
|
CalculateSourceAttackTime() *
|
|
(Scalar)AWE_VOL_ATTACK_TIME_RANGE + 0.5f;
|
|
|
|
// channel->SendNRPN(AWE_VOL_ATTACK_TIME_NRPN, midi_attack_time);
|
|
}
|
|
|
|
ExecuteModel(True);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Start note
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
//TODO: Start OpenAL source playing
|
|
// channel->SendNoteOn(GetCurrentNoteValue(), midi_velocity);
|
|
patch_resource->PlayNote(channelSet);
|
|
|
|
#if 0
|
|
Tell("On " << (int)GetCurrentNoteValue() << "\n");
|
|
#endif
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
DirectPatchSource::StopImplementation()
|
|
{
|
|
Check(this);
|
|
|
|
//TODO: Stop OpenAL source playing
|
|
PatchResource *patch_resource;
|
|
|
|
patch_resource = Cast_Object(PatchResource*, GetAudioResource());
|
|
Check(patch_resource);
|
|
patch_resource->StopNote(channelSet);
|
|
|
|
//
|
|
// Stop note
|
|
//
|
|
// Check(channel);
|
|
// channel->SendNoteOff(GetCurrentNoteValue(), MIDI_MIN_CONTROL_VALUE);
|
|
// channel = NULL;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
DirectPatchSource::ExecuteModel(Logical force_update)
|
|
{
|
|
Check(this);
|
|
|
|
//TODO: Probably don't have to do anything in this function- maybe watch
|
|
//to mess with looping appropriately.
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Apply pitch offset
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
const MIDIPitchBend pitch_resolution = 12; // HACK - should come from audio.ini
|
|
AudioPitchCents pitch_offset;
|
|
|
|
pitch_offset = CalculateSourcePitchOffset();
|
|
double relativePitch = pow(2.0,pitch_offset/1200.0);
|
|
Clamp(relativePitch,0.5,2.0);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Apply filter scale
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if (UseSourceBrightnessScale())
|
|
{
|
|
const MIDINRPNValue filter_resolution = 2;// HACK - should come from audio.ini
|
|
Scalar filter_scale;
|
|
PatchResource *patch_resource;
|
|
MIDINRPNValue max_midi_filter_cutoff;
|
|
MIDINRPNValue midi_filter_cutoff;
|
|
MIDINRPNValue filter_difference;
|
|
|
|
filter_scale = CalculateSourceBrightnessScale();
|
|
patch_resource = Cast_Object(PatchResource*, GetAudioResource());
|
|
max_midi_filter_cutoff = patch_resource->GetMaxMIDIFilterCutoff();
|
|
midi_filter_cutoff = filter_scale * (Scalar)max_midi_filter_cutoff + 0.5f;
|
|
filter_difference = lastMIDIFilterCutoff - midi_filter_cutoff;
|
|
|
|
if (Abs(filter_difference) >= filter_resolution)
|
|
{
|
|
lastMIDIFilterCutoff = midi_filter_cutoff;
|
|
}
|
|
|
|
//
|
|
// FIDELITY (docs/SOUND.md F9): this block previously computed the AWE
|
|
// initial-filter-cutoff (NRPN 21) and then only updated its own
|
|
// bookkeeping member -- the cutoff was never applied to anything, so
|
|
// authored brightness (ctl 5) was inert. Route it through EFX instead.
|
|
// Direct sources take brightness alone, gated on use_brightness_scale;
|
|
// the distance rolloff belongs to the 3D paths.
|
|
//
|
|
if (EFX_Available())
|
|
{
|
|
const float cutoff_scale =
|
|
(float)midi_filter_cutoff / (float)MIDI_MAX_CONTROL_VALUE;
|
|
const float gainhf = EFX_CutoffScaleToGainHF(cutoff_scale);
|
|
|
|
for (int i = 0; i < channelSet.count; i++)
|
|
{
|
|
EFX_SetSourceLowpassGainHF(channelSet.sources[i], gainhf);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Apply volume scale
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Scalar volume_scale;
|
|
const MIDIValue volume_resolution = 2; // HACK - should come from audio.ini
|
|
|
|
volume_scale = CalculateSourceVolumeScale();
|
|
|
|
//
|
|
// FIDELITY (docs/SOUND.md F4): the original ended its volume path in MIDI
|
|
// CC7, whose GM/SoundFont curve is concave -- amplitude ~ (v/127)^2. Writing
|
|
// volume_scale linearly to AL_GAIN played every intermediate level about
|
|
// +6 dB hot at mid-scale and compressed the authored dynamic range.
|
|
//
|
|
// AL_MAX_DISTANCE is no longer written here: the distance model is AL_NONE
|
|
// (see MUNGA/AUDIO.cpp) so it has no effect, and DirectPatch is the
|
|
// non-positional cockpit path which never took distance attenuation anyway.
|
|
//
|
|
const float direct_note_pitch = RPNotePitchFactor((int)GetCurrentNoteValue());
|
|
for (int i=0; i < channelSet.count; i++)
|
|
{
|
|
alSourcef(channelSet.sources[i], AL_GAIN, volume_scale * volume_scale);
|
|
alSourcef(channelSet.sources[i], AL_PITCH, (float)relativePitch * direct_note_pitch);
|
|
}
|
|
}
|
|
|
|
//#############################################################################
|
|
//###################### Dynamic3DPatchSource ###########################
|
|
//#############################################################################
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Dynamic3DPatchSource::Dynamic3DPatchSource(
|
|
PlugStream *stream,
|
|
Entity *entity
|
|
):
|
|
L4AudioSource(stream, entity)
|
|
{
|
|
}
|
|
|
|
Logical
|
|
Dynamic3DPatchSource::IsAudioSourceClipped(AudioHead *audio_head)
|
|
{
|
|
if (AudioSource::IsAudioSourceClipped(audio_head) || l4_application->GetMissionPlayer()->GetPlayerVehicle()->GetSimulationState() == VTV::BurningState)
|
|
{
|
|
return true;
|
|
} else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Dynamic3DPatchSource::~Dynamic3DPatchSource()
|
|
{
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
Dynamic3DPatchSource::TestInstance() const
|
|
{
|
|
L4AudioSource::TestInstance();
|
|
return True;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
Dynamic3DPatchSource::StartMessageImplementation()
|
|
{
|
|
Check(this);
|
|
|
|
L4AudioSource::StartMessageImplementation();
|
|
|
|
//
|
|
// Init channel set to requested channels
|
|
//
|
|
// channelSet.EnableAll();
|
|
|
|
//
|
|
// Request start from renderer
|
|
//
|
|
Check(application);
|
|
Cast_Object(L4AudioRenderer*, application->GetAudioRenderer())->
|
|
StartRequest(this);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
Dynamic3DPatchSource::StartImplementation()
|
|
{
|
|
Check(this);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Call inherited method
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
L4AudioSource::StartImplementation();
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Get application, renderer and head pointers
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
L4AudioRenderer *audio_renderer;
|
|
AudioHead *audio_head;
|
|
|
|
Check(application);
|
|
audio_renderer =
|
|
Cast_Object(L4AudioRenderer*, application->GetAudioRenderer());
|
|
Check(audio_renderer);
|
|
audio_head = audio_renderer->GetAudioHead();
|
|
Check(audio_head);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Setup the channel
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
ExecuteWatchers();
|
|
|
|
//
|
|
// Turn the volume down while setting up the channels
|
|
//
|
|
|
|
//
|
|
// Set the channels to the bank and program
|
|
//
|
|
PatchResource *patch_resource;
|
|
|
|
patch_resource = Cast_Object(PatchResource*, GetAudioResource());
|
|
Check(patch_resource);
|
|
patch_resource->SetDistance(GetDistanceToSource());
|
|
patch_resource->SetupPatch(channelSet);
|
|
|
|
//
|
|
// FIDELITY (docs/SOUND.md F11): wet exterior. The original sent CC91 =
|
|
// global_reverb_scale on all four channels of a 3D source and CC91 = 0 on
|
|
// the cockpit DirectPatch channels -- a deliberate outside/inside contrast
|
|
// that the port lost when every send site was commented out.
|
|
//
|
|
for (int i = 0; i < channelSet.count; i++)
|
|
{
|
|
EFX_AttachReverbSend(channelSet.sources[i]);
|
|
}
|
|
|
|
/*patch_resource->SetDistance(GetDistanceToSource());
|
|
for (i = 0; i < AudioChannelSetSize; i++)
|
|
{
|
|
channel = channelSet.GetNth(i);
|
|
patch_resource->SetupPatch(channel);
|
|
}*/
|
|
|
|
//
|
|
// Set the channels to default control values
|
|
//
|
|
//MIDIValue midi_reverb_level =
|
|
// audio_head->GetGlobalReverbScale() *
|
|
// (Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;
|
|
|
|
/* for (i = 0; i < AudioChannelSetSize; i++)
|
|
{
|
|
channel = channelSet.GetNth(i);
|
|
Check(channel);
|
|
channel->SendController(MIDI_REVERB_CONTROL, midi_reverb_level);
|
|
channel->SendController(MIDI_CHORUS_CONTROL, MIDI_MIN_CONTROL_VALUE);
|
|
}*/
|
|
|
|
//
|
|
// Set the channels to correct pan
|
|
//
|
|
/* channel = channelSet.GetFrontLeft();
|
|
Check(channel);
|
|
channel->SendController(MIDI_PAN_CONTROL, MIDI_LEFT_PAN_VALUE);
|
|
|
|
channel = channelSet.GetFrontRight();
|
|
Check(channel);
|
|
channel->SendController(MIDI_PAN_CONTROL, MIDI_RIGHT_PAN_VALUE);
|
|
|
|
channel = channelSet.GetRearLeft();
|
|
Check(channel);
|
|
channel->SendController(MIDI_PAN_CONTROL, MIDI_LEFT_PAN_VALUE);
|
|
|
|
channel = channelSet.GetRearRight();
|
|
Check(channel);
|
|
channel->SendController(MIDI_PAN_CONTROL, MIDI_RIGHT_PAN_VALUE);*/
|
|
|
|
//
|
|
// Set midi history to default control values
|
|
//
|
|
/*lastFrontLeftMIDIVolume = MIDI_MIN_CONTROL_VALUE;
|
|
lastFrontRightMIDIVolume = MIDI_MIN_CONTROL_VALUE;
|
|
lastRearLeftMIDIVolume = MIDI_MIN_CONTROL_VALUE;
|
|
lastRearRightMIDIVolume = MIDI_MIN_CONTROL_VALUE;
|
|
lastFrontLeftMIDIPitchBend = 0; // Set by force update
|
|
lastFrontRightMIDIPitchBend = 0; // Set by force update
|
|
lastRearLeftMIDIPitchBend = 0; // Set by force update
|
|
lastRearRightMIDIPitchBend = 0; // Set by force update
|
|
lastMIDIFilterCutoff = MIDI_MAX_CONTROL_VALUE; // Set by patch
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Calculate attack time, velocity, and execute model
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
MIDIValue midi_velocity =
|
|
CalculateSourceAttackVolumeScale() *
|
|
(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;
|
|
|
|
if (UseSourceAttackTime())
|
|
{
|
|
MIDINRPNValue midi_attack_time =
|
|
CalculateSourceAttackTime() *
|
|
(Scalar)AWE_VOL_ATTACK_TIME_RANGE + 0.5f;
|
|
|
|
for (i = 0; i < AudioChannelSetSize; i++)
|
|
{
|
|
channel = channelSet.GetNth(i);
|
|
Check(channel);
|
|
channel->SendNRPN(AWE_VOL_ATTACK_TIME_NRPN, midi_attack_time);
|
|
}
|
|
}*/
|
|
|
|
ExecuteModel(True);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Start note
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
|
|
//TODO: Start OpenAL source
|
|
|
|
/* for (i = 0; i < AudioChannelSetSize; i++)
|
|
{
|
|
channel = channelSet.GetNth(i);
|
|
Check(channel);
|
|
channel->SendNoteOn(GetCurrentNoteValue(), midi_velocity);
|
|
}*/
|
|
patch_resource->PlayNote(channelSet);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
Dynamic3DPatchSource::StopImplementation()
|
|
{
|
|
Check(this);
|
|
|
|
//
|
|
// Stop note
|
|
//
|
|
PatchResource *patch_resource;
|
|
|
|
patch_resource = Cast_Object(PatchResource*, GetAudioResource());
|
|
Check(patch_resource);
|
|
patch_resource->StopNote(channelSet);
|
|
|
|
/* AudioChannel *channel;
|
|
for (int i = 0; i < AudioChannelSetSize; i++)
|
|
{
|
|
channel = channelSet.GetNth(i);
|
|
Check(channel);
|
|
channel->SendNoteOff(GetCurrentNoteValue(), MIDI_MIN_CONTROL_VALUE);
|
|
}*/
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
Dynamic3DPatchSource::ExecuteModel(Logical force_update)
|
|
{
|
|
Check(this);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Call inherited method
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
L4AudioSource::Execute();
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Get audio resource
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
PatchResource *patch_resource;
|
|
|
|
patch_resource = Cast_Object(PatchResource*, GetAudioResource());
|
|
Check(patch_resource);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Get audio location
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
L4AudioLocation *audio_location;
|
|
L4AudioRenderer *audio_renderer;
|
|
AudioHead *audio_head;
|
|
|
|
audio_location = Cast_Object(L4AudioLocation*, GetAudioLocation());
|
|
Check(audio_location);
|
|
|
|
Check(application);
|
|
audio_renderer =
|
|
Cast_Object(L4AudioRenderer*, application->GetAudioRenderer());
|
|
Check(audio_renderer);
|
|
audio_head = audio_renderer->GetAudioHead();
|
|
Check(audio_head);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Render spatial model
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
|
|
//TODO: Sync location info to OpenAL sources
|
|
Vector3D pos;
|
|
|
|
Vector3D locationPosition = audio_location->GetLinkedEntity()->localOrigin.linearPosition;
|
|
Vector3D headPosition = audio_head->GetHeadEntity()->localOrigin.linearPosition;
|
|
|
|
pos.Subtract(locationPosition, headPosition);
|
|
|
|
float posMag = pos.Length();
|
|
|
|
pos.MultiplyByInverse(pos, audio_head->GetHeadEntity()->localToWorld);
|
|
|
|
Vector3D relative_velocity;
|
|
relative_velocity.MultiplyByInverse(
|
|
audio_location->GetLinkedEntity()->GetWorldLinearVelocity(),
|
|
audio_head->GetHeadEntity()->localToWorld
|
|
);
|
|
Check(&relative_velocity);
|
|
|
|
Scalar volume_scale = CalculateSourceVolumeScale();
|
|
|
|
Scalar pitch_offset;
|
|
|
|
pitch_offset = CalculateSourcePitchOffset();
|
|
|
|
//
|
|
// FIDELITY (docs/SOUND.md F10): add the AUTHORED doppler. AUDIO.INI's
|
|
// doppler_range=600 / speed_of_sound=250 are computed into
|
|
// AudioLocation::dopplerCents on every spatial update, and the original
|
|
// applied it on this dynamic path only -- static and direct sources stayed
|
|
// doppler-free. GetDopplerCents() previously had no callers at all.
|
|
//
|
|
pitch_offset += GetAudioLocation()->GetDopplerCents();
|
|
|
|
double relativePitch = pow(2.0,pitch_offset/1200.0);
|
|
Clamp(relativePitch,0.5,2.0);
|
|
|
|
//
|
|
// FIDELITY (docs/SOUND.md): relativePitch was computed here and never
|
|
// applied -- there was no AL_PITCH call anywhere in the tree, so the whole
|
|
// authored pitch chain (pitch_mix_offset / PitchAudioControlID, authored 97
|
|
// times across RP's sequences) was inert along with doppler.
|
|
//
|
|
// AL_VELOCITY is still written for bookkeeping but is now inert: doppler
|
|
// factor is 0 (see MUNGA/AUDIO.cpp) because this feed is sign-inverted
|
|
// relative to the AL_POSITION frame and never subtracted head velocity.
|
|
// AL_MAX_DISTANCE is dropped -- the distance model is AL_NONE and the
|
|
// authored curve is applied in CalculateSourceVolumeScale instead.
|
|
//
|
|
//
|
|
// FIDELITY (docs/SOUND.md F9): the AUTHORED high-frequency rolloff. The
|
|
// original drove the AWE filter cutoff on this path from
|
|
// highFreqCutoffScale x brightnessScale, ungated, on all four quadrant
|
|
// channels -- every moving 3D sound got duller with distance. AUDIO.INI
|
|
// still computes highFreqCutoffScale each frame (rolloff 2.0, knee 60,
|
|
// scale 0.005) and GetHighFreqCutoffScale() previously had zero callers.
|
|
//
|
|
float dynamic_gainhf = 1.0f;
|
|
|
|
if (EFX_Available())
|
|
{
|
|
PatchResource *filter_patch =
|
|
Cast_Object(PatchResource*, GetAudioResource());
|
|
Check(filter_patch);
|
|
|
|
Scalar filter_scale =
|
|
GetAudioLocation()->GetHighFreqCutoffScale() *
|
|
CalculateSourceBrightnessScale();
|
|
Scalar max_cutoff = (Scalar)filter_patch->GetMaxMIDIFilterCutoff();
|
|
Scalar midi_cutoff = filter_scale * max_cutoff;
|
|
|
|
dynamic_gainhf = EFX_CutoffScaleToGainHF(
|
|
(float)(midi_cutoff / (Scalar)MIDI_MAX_CONTROL_VALUE)
|
|
);
|
|
}
|
|
|
|
const float dynamic_note_pitch = RPNotePitchFactor((int)GetCurrentNoteValue());
|
|
for (int i=0; i < channelSet.count; i++)
|
|
{
|
|
alSource3f(channelSet.sources[i],AL_POSITION,pos.x,pos.y,pos.z);
|
|
alSourcef(channelSet.sources[i], AL_GAIN, volume_scale * volume_scale);
|
|
alSourcef(channelSet.sources[i], AL_PITCH, (float)relativePitch * dynamic_note_pitch);
|
|
alSource3f(channelSet.sources[i],AL_VELOCITY,-relative_velocity.x,-relative_velocity.y,-relative_velocity.z);
|
|
|
|
if (EFX_Available())
|
|
{
|
|
EFX_SetSourceLowpassGainHF(channelSet.sources[i], dynamic_gainhf);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
AudioControlValue
|
|
Dynamic3DPatchSource::CalculateSourceVolumeScale()
|
|
{
|
|
Check(this);
|
|
|
|
//
|
|
// Call inherited method to calculate volume scale
|
|
//
|
|
Scalar
|
|
volume_scale = L4AudioSource::CalculateSourceVolumeScale();
|
|
|
|
//
|
|
// FIDELITY (docs/SOUND.md F3): apply the AUTHORED distance attenuation.
|
|
// AUDIO.INI's knee/rolloff curve (amplitude_rolloff=2.0, knee=60,
|
|
// distance_scale=0.003, clipping_radius=550) is computed into
|
|
// distanceVolumeScale on every spatial update; this multiply was commented
|
|
// out behind an early return and AL_LINEAR_DISTANCE substituted, which faded
|
|
// distant audio on a straight line to zero instead of the authored
|
|
// 1/(1+(k(d-knee))^2). Restoring it also un-blinds the volume-based
|
|
// transient cull, the AudioWeighting voice-steal, and the CalculateMix
|
|
// ducking chain, all of which key off this value and were treating far
|
|
// sources as full-presence.
|
|
//
|
|
// The spatial model is already refreshed each Execute, so the
|
|
// UpdateSpatialModel call the original comment carried is not needed here.
|
|
//
|
|
Check(GetAudioLocation());
|
|
volume_scale *= GetAudioLocation()->GetDistanceVolumeScale();
|
|
return volume_scale;
|
|
}
|
|
|
|
//#############################################################################
|
|
//####################### Static3DPatchSource ###########################
|
|
//#############################################################################
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Static3DPatchSource::Static3DPatchSource(
|
|
PlugStream *stream,
|
|
Entity *entity
|
|
):
|
|
L4AudioSource(stream, entity),
|
|
position(0.0f, 0.0f, 0.0f)
|
|
{
|
|
MemoryStream_Read(stream, &useInternalSpatialization);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
AudioControlValue
|
|
Static3DPatchSource::CalculateSourceVolumeScale()
|
|
{
|
|
Check(this);
|
|
|
|
//
|
|
// FIDELITY (docs/SOUND.md F3): same authored distance attenuation as the
|
|
// dynamic path. The spatial model computes distanceVolumeScale on every
|
|
// execute; without this multiply statics were left to AL_LINEAR_DISTANCE.
|
|
//
|
|
Scalar volume_scale = L4AudioSource::CalculateSourceVolumeScale();
|
|
Check(GetAudioLocation());
|
|
volume_scale *= GetAudioLocation()->GetDistanceVolumeScale();
|
|
return volume_scale;
|
|
}
|
|
|
|
Logical Static3DPatchSource::IsAudioSourceClipped(AudioHead *audio_head)
|
|
{
|
|
if (AudioSource::IsAudioSourceClipped(audio_head) || l4_application->GetMissionPlayer()->GetPlayerVehicle()->GetSimulationState() == VTV::BurningState)
|
|
{
|
|
return true;
|
|
} else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Static3DPatchSource::~Static3DPatchSource()
|
|
{
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
Static3DPatchSource::BuildFromPage(
|
|
PlugStream *stream,
|
|
NameList *name_list,
|
|
ClassID class_ID,
|
|
ObjectID object_ID
|
|
)
|
|
{
|
|
L4AudioSource::BuildFromPage(stream, name_list, class_ID, object_ID);
|
|
|
|
if (name_list->FindData("use_internal") != NULL)
|
|
{
|
|
MEM_STRM_WRITE_ENTRY(*stream, name_list, Logical, use_internal);
|
|
}
|
|
else
|
|
{
|
|
Logical use_internal = True;
|
|
MemoryStream_Write(stream, &use_internal);
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
Static3DPatchSource::TestInstance() const
|
|
{
|
|
L4AudioSource::TestInstance();
|
|
if (useInternalSpatialization)
|
|
{
|
|
Check(&internalSpatialization);
|
|
}
|
|
return True;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
Static3DPatchSource::SetPosition(const Vector3D &vector3D)
|
|
{
|
|
position = vector3D;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
Static3DPatchSource::StartMessageImplementation()
|
|
{
|
|
Check(this);
|
|
|
|
L4AudioSource::StartMessageImplementation();
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Get audio renderer
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
L4AudioRenderer *audio_renderer;
|
|
|
|
Check(application);
|
|
audio_renderer =
|
|
Cast_Object(L4AudioRenderer*, application->GetAudioRenderer());
|
|
Check(audio_renderer);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Update spatial parameters
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if (useInternalSpatialization)
|
|
{
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// Calculate azimuth of position
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
Radian azimuth_of_source;
|
|
|
|
if (Small_Enough(position.x) && Small_Enough(position.z))
|
|
{
|
|
azimuth_of_source = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
Verify(!(Small_Enough(position.x) && Small_Enough(position.z)));
|
|
azimuth_of_source = Arctan(position.x, position.z);
|
|
}
|
|
Verify(azimuth_of_source <= PI && azimuth_of_source >= -PI);
|
|
|
|
internalSpatialization.CalculateSpatialization(azimuth_of_source);
|
|
}
|
|
else
|
|
{
|
|
UpdateSpatialModel(audio_renderer->GetAudioHead());
|
|
Cast_Object(L4AudioLocation*, GetAudioLocation())->GetSpatialization(
|
|
&internalSpatialization
|
|
);
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Init channel set to required channels
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
/* channelSet.EnableFrontLeft(False);
|
|
channelSet.EnableFrontRight(False);
|
|
channelSet.EnableRearLeft(False);
|
|
channelSet.EnableRearRight(False);
|
|
|
|
switch (GetQuadrant())
|
|
{
|
|
case L4AudioSpatialization::Quadrant1:
|
|
channelSet.EnableFrontLeft(True);
|
|
break;
|
|
|
|
case L4AudioSpatialization::Quadrant2:
|
|
channelSet.EnableFrontLeft(True);
|
|
channelSet.EnableRearLeft(True);
|
|
break;
|
|
|
|
case L4AudioSpatialization::Quadrant3:
|
|
channelSet.EnableRearLeft(True);
|
|
break;
|
|
|
|
case L4AudioSpatialization::Quadrant4:
|
|
channelSet.EnableFrontRight(True);
|
|
channelSet.EnableRearRight(True);
|
|
break;
|
|
}*/
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Request start from renderer
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
//audio_renderer->StartRequest(this);
|
|
(static_cast<L4AudioRenderer*>(application->GetAudioRenderer()))->StartRequest(this);
|
|
// Cast_Object(L4AudioRenderer*, application->GetAudioRenderer())->
|
|
// StartRequest(this);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
Static3DPatchSource::StartImplementation()
|
|
{
|
|
Check(this);
|
|
|
|
L4AudioSource::StartImplementation();
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Get audio head constants
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
L4AudioRenderer *audio_renderer;
|
|
AudioHead *audio_head;
|
|
|
|
Check(application);
|
|
audio_renderer =
|
|
Cast_Object(L4AudioRenderer*, application->GetAudioRenderer());
|
|
Check(audio_renderer);
|
|
audio_head = audio_renderer->GetAudioHead();
|
|
Check(audio_head);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Setup the channel
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
ExecuteWatchers();
|
|
|
|
//
|
|
// Turn the volume down while setting up the channels
|
|
//
|
|
int i;
|
|
/* AudioChannel *channel;
|
|
|
|
for (i = 0; i < AudioChannelSetSize; i++)
|
|
{
|
|
if ((channel = channelSet.GetNth(i)) != NULL)
|
|
{
|
|
Check(channel);
|
|
channel->SendController(MIDI_VOLUME_CONTROL, MIDI_MIN_CONTROL_VALUE);
|
|
}
|
|
}*/
|
|
|
|
//
|
|
// Set the channels to the bank and program
|
|
//
|
|
PatchResource *patch_resource;
|
|
|
|
patch_resource = Cast_Object(PatchResource*, GetAudioResource());
|
|
Check(patch_resource);
|
|
patch_resource->SetDistance(GetDistanceToSource());
|
|
patch_resource->SetupPatch(channelSet);
|
|
|
|
//
|
|
// FIDELITY (docs/SOUND.md F11): statics are exterior sources too, so they
|
|
// take the same wet send as the dynamic path.
|
|
//
|
|
for (int i = 0; i < channelSet.count; i++)
|
|
{
|
|
EFX_AttachReverbSend(channelSet.sources[i]);
|
|
}
|
|
|
|
/*for (i = 0; i < AudioChannelSetSize; i++)
|
|
{
|
|
if ((channel = channelSet.GetNth(i)) != NULL)
|
|
{
|
|
patch_resource->SetupPatch(channel);
|
|
}
|
|
}*/
|
|
|
|
//
|
|
// Set the channels to default control values
|
|
//
|
|
/*MIDIValue midi_reverb_level =
|
|
audio_head->GetGlobalReverbScale() *
|
|
(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;*/
|
|
|
|
/*for (i = 0; i < AudioChannelSetSize; i++)
|
|
{
|
|
if ((channel = channelSet.GetNth(i)) != NULL)
|
|
{
|
|
Check(channel);
|
|
channel->SendController(MIDI_REVERB_CONTROL, midi_reverb_level);
|
|
channel->SendController(MIDI_CHORUS_CONTROL, MIDI_MIN_CONTROL_VALUE);
|
|
}
|
|
}*/
|
|
|
|
//
|
|
// Set the channels to correct pan
|
|
//
|
|
|
|
/*
|
|
switch (GetQuadrant())
|
|
{
|
|
case L4AudioSpatialization::Quadrant1:
|
|
{
|
|
const Radian azimuth_max =
|
|
45.0*RAD_PER_DEG; // HACK - should come from head
|
|
const Scalar slope =
|
|
-(Scalar)MIDI_MAX_CONTROL_VALUE / (2.0f * azimuth_max);
|
|
const Scalar intercept =
|
|
(MIDI_MAX_CONTROL_VALUE * 0.5f);
|
|
MIDIValue
|
|
midi_pan;
|
|
|
|
midi_pan = slope * GetAzimuthOfSource() + intercept;
|
|
Check(channelSet.GetFrontLeft());
|
|
channelSet.GetFrontLeft()->SendController(
|
|
MIDI_PAN_CONTROL,
|
|
midi_pan
|
|
);
|
|
}
|
|
break;
|
|
|
|
case L4AudioSpatialization::Quadrant2:
|
|
{
|
|
Check(channelSet.GetFrontLeft());
|
|
Check(channelSet.GetRearLeft());
|
|
channelSet.GetFrontLeft()->SendController(
|
|
MIDI_PAN_CONTROL,
|
|
MIDI_MIN_CONTROL_VALUE
|
|
);
|
|
channelSet.GetRearLeft()->SendController(
|
|
MIDI_PAN_CONTROL,
|
|
MIDI_MIN_CONTROL_VALUE
|
|
);
|
|
}
|
|
break;
|
|
|
|
case L4AudioSpatialization::Quadrant3:
|
|
{
|
|
const Radian azimuth_max =
|
|
45.0*RAD_PER_DEG; // HACK - should come from head
|
|
const Scalar slope =
|
|
(Scalar)MIDI_MAX_CONTROL_VALUE / (2.0f * azimuth_max);
|
|
const Scalar intercept =
|
|
(MIDI_MAX_CONTROL_VALUE * 0.5f);
|
|
MIDIValue
|
|
midi_pan;
|
|
|
|
midi_pan = slope * GetAzimuthOfSource() + intercept;
|
|
Check(channelSet.GetRearLeft());
|
|
channelSet.GetRearLeft()->SendController(
|
|
MIDI_PAN_CONTROL,
|
|
midi_pan
|
|
);
|
|
}
|
|
break;
|
|
|
|
case L4AudioSpatialization::Quadrant4:
|
|
{
|
|
Check(channelSet.GetFrontRight());
|
|
Check(channelSet.GetRearRight());
|
|
channelSet.GetFrontRight()->SendController(
|
|
MIDI_PAN_CONTROL,
|
|
MIDI_MAX_CONTROL_VALUE
|
|
);
|
|
channelSet.GetRearRight()->SendController(
|
|
MIDI_PAN_CONTROL,
|
|
MIDI_MAX_CONTROL_VALUE
|
|
);
|
|
}
|
|
break;
|
|
}*/
|
|
|
|
//
|
|
// Set midi history to default control values
|
|
//
|
|
/*lastFrontLeftMIDIVolume = MIDI_MIN_CONTROL_VALUE;
|
|
lastFrontRightMIDIVolume = MIDI_MIN_CONTROL_VALUE;
|
|
lastRearLeftMIDIVolume = MIDI_MIN_CONTROL_VALUE;
|
|
lastRearRightMIDIVolume = MIDI_MIN_CONTROL_VALUE;
|
|
lastMIDIPitchBend = 0; // Set by force update
|
|
lastMIDIFilterCutoff = MIDI_MAX_CONTROL_VALUE; // Set by patch
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Calculate attack time, velocity, and execute model
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
MIDIValue midi_velocity =
|
|
CalculateSourceAttackVolumeScale() *
|
|
(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;
|
|
|
|
if (UseSourceAttackTime())
|
|
{
|
|
MIDINRPNValue midi_attack_time =
|
|
CalculateSourceAttackTime() *
|
|
(Scalar)AWE_VOL_ATTACK_TIME_RANGE + 0.5f;
|
|
|
|
for (i = 0; i < AudioChannelSetSize; i++)
|
|
{
|
|
if ((channel = channelSet.GetNth(i)) != NULL)
|
|
{
|
|
Check(channel);
|
|
channel->SendNRPN(AWE_VOL_ATTACK_TIME_NRPN, midi_attack_time);
|
|
}
|
|
}
|
|
}*/
|
|
|
|
ExecuteModel(True);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Start note
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
|
|
//TODO: Start OpenAL source playing
|
|
|
|
/*for (i = 0; i < AudioChannelSetSize; i++)
|
|
{
|
|
if ((channel = channelSet.GetNth(i)) != NULL)
|
|
{
|
|
Check(channel);
|
|
channel->SendNoteOn(GetCurrentNoteValue(), midi_velocity);
|
|
}
|
|
}*/
|
|
patch_resource->PlayNote(channelSet);
|
|
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
Static3DPatchSource::StopImplementation()
|
|
{
|
|
Check(this);
|
|
|
|
//
|
|
//------------------------------------------------------------
|
|
// Stop note
|
|
//------------------------------------------------------------
|
|
//
|
|
|
|
//TODO: Stop OpenAL source playing
|
|
PatchResource *patch_resource;
|
|
|
|
patch_resource = Cast_Object(PatchResource*, GetAudioResource());
|
|
Check(patch_resource);
|
|
patch_resource->StopNote(channelSet);
|
|
|
|
/*AudioChannel *channel;
|
|
|
|
for (int i = 0; i < AudioChannelSetSize; i++)
|
|
{
|
|
if ((channel = channelSet.GetNth(i)) != NULL)
|
|
{
|
|
Check(channel);
|
|
channel->SendNoteOff(GetCurrentNoteValue(), MIDI_MIN_CONTROL_VALUE);
|
|
}
|
|
}*/
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
Static3DPatchSource::ExecuteModel(Logical force_update)
|
|
{
|
|
Check(this);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Get audio resource
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
PatchResource *patch_resource;
|
|
|
|
patch_resource = Cast_Object(PatchResource*, GetAudioResource());
|
|
Check(patch_resource);
|
|
|
|
Scalar volume_scale = CalculateSourceVolumeScale();
|
|
L4AudioLocation *audio_location = Cast_Object(L4AudioLocation*, GetAudioLocation());
|
|
|
|
Scalar pitch_offset;
|
|
|
|
pitch_offset = CalculateSourcePitchOffset();
|
|
double relativePitch = pow(2.0,pitch_offset/1200.0);
|
|
Clamp(relativePitch,0.5,2.0);
|
|
|
|
Vector3D relative_position;
|
|
Vector3D relative_velocity;
|
|
|
|
if (useInternalSpatialization)
|
|
{
|
|
relative_position = position;
|
|
} else
|
|
{
|
|
relative_position = audio_location->GetVectorToSource();
|
|
}
|
|
|
|
//
|
|
// FIDELITY (docs/SOUND.md F4 + pitch): squared CC7 volume law, and the
|
|
// authored pitch chain applied -- see the DirectPatch/Dynamic3D paths. The
|
|
// original left static sources doppler-free, so no doppler term here.
|
|
// AL_MAX_DISTANCE dropped with the AL_NONE distance model; the authored
|
|
// curve is applied in CalculateSourceVolumeScale.
|
|
//
|
|
//Static models have their position freely available as relative positions and stand still
|
|
//
|
|
// FIDELITY (docs/SOUND.md F9): statics took brightness alone in the
|
|
// original -- no distance term on this path.
|
|
//
|
|
float static_gainhf = 1.0f;
|
|
|
|
if (EFX_Available() && UseSourceBrightnessScale())
|
|
{
|
|
PatchResource *filter_patch =
|
|
Cast_Object(PatchResource*, GetAudioResource());
|
|
Check(filter_patch);
|
|
|
|
Scalar midi_cutoff =
|
|
CalculateSourceBrightnessScale() *
|
|
(Scalar)filter_patch->GetMaxMIDIFilterCutoff();
|
|
|
|
static_gainhf = EFX_CutoffScaleToGainHF(
|
|
(float)(midi_cutoff / (Scalar)MIDI_MAX_CONTROL_VALUE)
|
|
);
|
|
}
|
|
|
|
const float static_note_pitch = RPNotePitchFactor((int)GetCurrentNoteValue());
|
|
for (int i=0; i < channelSet.count; i++)
|
|
{
|
|
alSourcef(channelSet.sources[i], AL_GAIN, volume_scale * volume_scale);
|
|
alSourcef(channelSet.sources[i], AL_PITCH, (float)relativePitch * static_note_pitch);
|
|
alSource3f(channelSet.sources[i],AL_POSITION,relative_position.x,relative_position.y,relative_position.z);
|
|
|
|
if (EFX_Available())
|
|
{
|
|
EFX_SetSourceLowpassGainHF(channelSet.sources[i], static_gainhf);
|
|
}
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Render spatial model
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
/*AudioChannel *front_left_channel = channelSet.GetFrontLeft();
|
|
AudioChannel *front_right_channel = channelSet.GetFrontRight();
|
|
AudioChannel *rear_left_channel = channelSet.GetRearLeft();
|
|
AudioChannel *rear_right_channel = channelSet.GetRearRight();*/
|
|
|
|
//TODO: Update spatial stuff?
|
|
|
|
//
|
|
// Apply volume scale offset
|
|
//
|
|
//Scalar volume_scale = CalculateSourceVolumeScale();
|
|
|
|
//
|
|
// Apply pitch offset and calculate MIDI pitch bend
|
|
//
|
|
/*Scalar pitch_offset;
|
|
MIDIPitchBend midi_pitch_bend;
|
|
|
|
pitch_offset = CalculateSourcePitchOffset();
|
|
midi_pitch_bend = pitch_offset * MIDI_PITCH_PER_CENTS + 0.5f;
|
|
Clamp(midi_pitch_bend, -MIDI_PITCH_BEND_MAX, MIDI_PITCH_BEND_MAX);
|
|
|
|
//
|
|
// Apply high frequency scale and calculate NRPN filter cutoff
|
|
//
|
|
Scalar filter_scale;
|
|
MIDINRPNValue max_midi_filter_cutoff;
|
|
MIDINRPNValue midi_filter_cutoff;
|
|
|
|
filter_scale = CalculateSourceBrightnessScale();
|
|
max_midi_filter_cutoff = patch_resource->GetMaxMIDIFilterCutoff();
|
|
midi_filter_cutoff = filter_scale * (Scalar)max_midi_filter_cutoff + 0.5f;
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Send pitch bend
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
const MIDIPitchBend pitch_resolution = 2;
|
|
MIDIPitchBend pitch_difference = midi_pitch_bend - lastMIDIPitchBend;
|
|
|
|
if (Abs(pitch_difference) >= pitch_resolution || force_update)
|
|
{
|
|
lastMIDIPitchBend = midi_pitch_bend;
|
|
|
|
switch (GetQuadrant())
|
|
{
|
|
case L4AudioSpatialization::Quadrant1:
|
|
Check(front_left_channel);
|
|
front_left_channel->SendPitchBend(midi_pitch_bend);
|
|
break;
|
|
|
|
case L4AudioSpatialization::Quadrant2:
|
|
Check(front_left_channel);
|
|
front_left_channel->SendPitchBend(midi_pitch_bend);
|
|
Check(rear_left_channel);
|
|
rear_left_channel->SendPitchBend(midi_pitch_bend);
|
|
break;
|
|
|
|
case L4AudioSpatialization::Quadrant3:
|
|
Check(rear_left_channel);
|
|
rear_left_channel->SendPitchBend(midi_pitch_bend);
|
|
break;
|
|
|
|
case L4AudioSpatialization::Quadrant4:
|
|
Check(front_right_channel);
|
|
front_right_channel->SendPitchBend(midi_pitch_bend);
|
|
Check(rear_right_channel);
|
|
rear_right_channel->SendPitchBend(midi_pitch_bend);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Send NRPN filter cutoff
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if (UseSourceBrightnessScale())
|
|
{
|
|
const MIDIValue filter_resolution = 2;
|
|
MIDINRPNValue filter_difference;
|
|
|
|
filter_difference = midi_filter_cutoff - lastMIDIFilterCutoff;
|
|
|
|
if (Abs(filter_difference) >= filter_resolution)
|
|
{
|
|
lastMIDIFilterCutoff = midi_filter_cutoff;
|
|
|
|
switch (GetQuadrant())
|
|
{
|
|
case L4AudioSpatialization::Quadrant1:
|
|
Check(front_left_channel);
|
|
front_left_channel->SendNRPN(
|
|
AWE_FILTER_CUTOFF_NRPN,
|
|
midi_filter_cutoff
|
|
);
|
|
break;
|
|
|
|
case L4AudioSpatialization::Quadrant2:
|
|
Check(front_left_channel);
|
|
front_left_channel->SendNRPN(
|
|
AWE_FILTER_CUTOFF_NRPN,
|
|
midi_filter_cutoff
|
|
);
|
|
Check(rear_left_channel);
|
|
rear_left_channel->SendNRPN(
|
|
AWE_FILTER_CUTOFF_NRPN,
|
|
midi_filter_cutoff
|
|
);
|
|
break;
|
|
|
|
case L4AudioSpatialization::Quadrant3:
|
|
Check(rear_left_channel);
|
|
rear_left_channel->SendNRPN(
|
|
AWE_FILTER_CUTOFF_NRPN,
|
|
midi_filter_cutoff
|
|
);
|
|
break;
|
|
|
|
case L4AudioSpatialization::Quadrant4:
|
|
Check(front_right_channel);
|
|
front_right_channel->SendNRPN(
|
|
AWE_FILTER_CUTOFF_NRPN,
|
|
midi_filter_cutoff
|
|
);
|
|
Check(rear_right_channel);
|
|
rear_right_channel->SendNRPN(
|
|
AWE_FILTER_CUTOFF_NRPN,
|
|
midi_filter_cutoff
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Send MIDI volume
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
switch (GetQuadrant())
|
|
{
|
|
case L4AudioSpatialization::Quadrant1:
|
|
{
|
|
MIDIValue front_left_midi_volume =
|
|
volume_scale *
|
|
(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;
|
|
|
|
if (front_left_midi_volume != lastFrontLeftMIDIVolume)
|
|
{
|
|
lastFrontLeftMIDIVolume = front_left_midi_volume;
|
|
Check(front_left_channel);
|
|
front_left_channel->SendController(
|
|
MIDI_VOLUME_CONTROL,
|
|
front_left_midi_volume
|
|
);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case L4AudioSpatialization::Quadrant2:
|
|
{
|
|
MIDIValue front_left_midi_volume =
|
|
volume_scale * GetFrontLeftScale() *
|
|
(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;
|
|
|
|
MIDIValue rear_left_midi_volume =
|
|
volume_scale * GetRearLeftScale() *
|
|
(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;
|
|
|
|
if (front_left_midi_volume != lastFrontLeftMIDIVolume)
|
|
{
|
|
lastFrontLeftMIDIVolume = front_left_midi_volume;
|
|
Check(front_left_channel);
|
|
front_left_channel->SendController(
|
|
MIDI_VOLUME_CONTROL,
|
|
front_left_midi_volume
|
|
);
|
|
}
|
|
if (rear_left_midi_volume != lastRearLeftMIDIVolume)
|
|
{
|
|
lastRearLeftMIDIVolume = rear_left_midi_volume;
|
|
Check(rear_left_channel);
|
|
rear_left_channel->SendController(
|
|
MIDI_VOLUME_CONTROL,
|
|
rear_left_midi_volume
|
|
);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case L4AudioSpatialization::Quadrant3:
|
|
{
|
|
MIDIValue rear_left_midi_volume =
|
|
volume_scale *
|
|
(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;
|
|
|
|
if (rear_left_midi_volume != lastRearLeftMIDIVolume)
|
|
{
|
|
lastRearLeftMIDIVolume = rear_left_midi_volume;
|
|
Check(rear_left_channel);
|
|
rear_left_channel->SendController(
|
|
MIDI_VOLUME_CONTROL,
|
|
rear_left_midi_volume
|
|
);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case L4AudioSpatialization::Quadrant4:
|
|
{
|
|
MIDIValue front_right_midi_volume =
|
|
volume_scale * GetFrontRightScale() *
|
|
(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;
|
|
|
|
MIDIValue rear_right_midi_volume =
|
|
volume_scale * GetRearRightScale() *
|
|
(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;
|
|
|
|
if (front_right_midi_volume != lastFrontRightMIDIVolume)
|
|
{
|
|
lastFrontRightMIDIVolume = front_right_midi_volume;
|
|
Check(front_right_channel);
|
|
front_right_channel->SendController(
|
|
MIDI_VOLUME_CONTROL,
|
|
front_right_midi_volume
|
|
);
|
|
}
|
|
if (rear_right_midi_volume != lastRearRightMIDIVolume)
|
|
{
|
|
lastRearRightMIDIVolume = rear_right_midi_volume;
|
|
Check(rear_right_channel);
|
|
rear_right_channel->SendController(
|
|
MIDI_VOLUME_CONTROL,
|
|
rear_right_midi_volume
|
|
);
|
|
}
|
|
}
|
|
break;
|
|
}*/
|
|
}
|