The volume keys wanted a partner, and the bass trim could not be one as it stood: it scaled the sample data as it loaded, so by the time anyone pressed a key the audio was already sitting in OpenAL buffers and nothing short of a restart would move it. So the trim is now a per-zone gain applied in the mix instead. Each buffer's depth - how much of the low band it occupies - is still worked out once at load from its playback rate, but the trim itself is read every frame, which is what lets Home and End move it while sounds are playing. It is the better form regardless: no rewriting of sample data, and no quantisation on top of audio that has already been through one gain stage. Home raises, End lowers, in steps of 0.05, and the setting is written to bass.cfg beside the exe exactly as the volume writes volume.cfg. Together with PageUp and PageDown that is the amplifier and the crossover the cabinets had in hardware and a desktop does not. Builds clean, runs, and neither knob fires unprompted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2428 lines
67 KiB
C++
2428 lines
67 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();
|
|
|
|
//
|
|
// sources[] was left uninitialized here, and RequestAudioChannels decides
|
|
// whether a slot already holds a source by asking alIsSource about it.
|
|
// Garbage that happened to match a live name meant silently sharing another
|
|
// source -- a real hazard now that the pool recycles small integer names.
|
|
// 0 is never a valid AL name.
|
|
//
|
|
for (int i = 0; i < (int)(sizeof(channelSet.sources) / sizeof(channelSet.sources[0])); i++)
|
|
{
|
|
channelSet.sources[i] = 0;
|
|
}
|
|
|
|
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
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
float direct_gainhf = 1.0f;
|
|
|
|
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; the distance rolloff belongs to
|
|
// the 3D paths.
|
|
//
|
|
direct_gainhf = EFX_CutoffScaleToGainHF(
|
|
(float)midi_filter_cutoff / (float)MIDI_MAX_CONTROL_VALUE
|
|
);
|
|
}
|
|
|
|
//
|
|
// Applied OUTSIDE the brightness gate: a source that does not use brightness
|
|
// still has to be told, because the same call carries the player's bass trim.
|
|
// At unity on both axes it detaches the filter, so this costs nothing in the
|
|
// default configuration.
|
|
//
|
|
if (EFX_Available())
|
|
{
|
|
for (int i = 0; i < channelSet.count; i++)
|
|
{
|
|
EFX_SetSourceLowpassGainHF(channelSet.sources[i], direct_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());
|
|
PatchResource *direct_patch = Cast_Object(PatchResource*, GetAudioResource());
|
|
for (int i=0; i < channelSet.count; i++)
|
|
{
|
|
alSourcef(channelSet.sources[i], AL_GAIN,
|
|
volume_scale * volume_scale * direct_patch->GetZoneBassGain(i));
|
|
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());
|
|
PatchResource *dynamic_patch = Cast_Object(PatchResource*, GetAudioResource());
|
|
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 * dynamic_patch->GetZoneBassGain(i));
|
|
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 * patch_resource->GetZoneBassGain(i));
|
|
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;
|
|
}*/
|
|
}
|