Merge BT411 audio-fidelity + combat work (4e72f0c..abed41e) into BT412
Brings the post-fork BT411 line forward via a local-path merge (never touches the BT411 gitea remote): the full audio-fidelity system (engine AUD*/L4AUD* + audiopresets.cpp + ~600 content wavs + AUDIO_FIDELITY.md), missiles/rear-fire/HUD/gyro/gait tasks (#66-68), FOGDAY.EGG, and refreshed context docs -- 91 commits, ~688 files clean. Only 5 files overlapped the steamification; resolved keeping BOTH: - L4NET.CPP: took BT411's task-#50 fix (don't close the game listener on console loss) over the seam's adaptation of the old buggy close; sends stay on NetTransport_Get(). - L4NETTRANSPORT.cpp: folded BT411's TCP_NODELAY latency fix into WinsockNetTransport::Connect (the seam already had retry + nonblocking). - mechmppr.cpp: combined the device_owns_input gating with BT411's task-#68 look-behind, gating the lookBehind write too. - .gitignore / CMakeLists.txt / mech4.cpp: trivial / auto-merged (deviceOwnsInput gating preserved). Verified: clean build (default + implicitly the Steam TU untouched); solo front-end mode; loopback MP through the seam (mesh completes, both tick, replication works, no NODELAY warnings). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
#include <cstdlib>
|
||||
#include "munga.h"
|
||||
#pragma hdrstop
|
||||
|
||||
@@ -290,6 +291,11 @@ AudioControlSend::AudioControlSend(
|
||||
MemoryStream_Read(stream, &control_ID);
|
||||
MemoryStream_Read(stream, &control_value);
|
||||
|
||||
if (getenv("BT_ATTRBIND_LOG")) { static int s_sd=0; if (s_sd++<80)
|
||||
DEBUG_STREAM << "[sendcfg] tgt=" << (void*)audio_component
|
||||
<< " ctl=" << (int)control_ID << "/" << control_value
|
||||
<< " entity=" << (void*)entity << "\n" << std::flush; }
|
||||
|
||||
Check(entity);
|
||||
entity->AddAudioComponent(this);
|
||||
|
||||
@@ -472,6 +478,9 @@ void
|
||||
while ((audio_component = iterator.ReadAndNext()) != NULL)
|
||||
{
|
||||
Check(audio_component);
|
||||
if (getenv("BT_AUDIO_SPATIAL")) { static int s_sp2=0; if (s_sp2++<200)
|
||||
DEBUG_STREAM << "[split] " << (void*)this << " -> tgt=" << (void*)audio_component
|
||||
<< " ctl=" << (int)control_ID << "/" << control_value << "\n" << std::flush; }
|
||||
audio_component->ReceiveControl(control_ID, control_value);
|
||||
}
|
||||
}
|
||||
@@ -631,6 +640,10 @@ void
|
||||
}
|
||||
|
||||
Check(audioComponentSocket.GetCurrent());
|
||||
if (getenv("BT_AUDIO_SPATIAL")) { static int s_mix=0; if (s_mix++<80)
|
||||
DEBUG_STREAM << "[mix] mixer=" << (void*)this << " -> tgt=" << (void*)audioComponentSocket.GetCurrent()
|
||||
<< " outCtl=" << (int)outputControlID << " in[" << index << "]=" << control_value
|
||||
<< " sum=" << mixed_value << "\n" << std::flush; }
|
||||
audioComponentSocket.GetCurrent()->ReceiveControl(
|
||||
outputControlID,
|
||||
mixed_value
|
||||
@@ -793,6 +806,9 @@ void
|
||||
}
|
||||
|
||||
Check(audioComponentSocket.GetCurrent());
|
||||
if (getenv("BT_AUDIO_SPATIAL") && mult_value <= 0.0f) { static int s_mx=0; if (s_mx++<300)
|
||||
DEBUG_STREAM << "[mult0] mult=" << (void*)this << " -> tgt=" << (void*)audioComponentSocket.GetCurrent()
|
||||
<< " in[" << index << "]=" << control_value << " product=" << mult_value << "\n" << std::flush; }
|
||||
audioComponentSocket.GetCurrent()->ReceiveControl(
|
||||
outputControlID,
|
||||
mult_value
|
||||
@@ -840,6 +856,11 @@ AudioControlSmoother::AudioControlSmoother(
|
||||
MemoryStream_Read(stream, &initial_fill_value);
|
||||
audioControlAverage.SetSize(number_of_samples, initial_fill_value);
|
||||
|
||||
if (getenv("BT_ATTRBIND_LOG")) { static int s_sm=0; if (s_sm++<40)
|
||||
DEBUG_STREAM << "[smoothcfg] this=" << (void*)this << " ctlID=" << (int)control_ID
|
||||
<< " samples=" << (int)number_of_samples << " fill=" << initial_fill_value
|
||||
<< "\n" << std::flush; }
|
||||
|
||||
AudioControlSmootherX(
|
||||
audio_component,
|
||||
entity,
|
||||
@@ -939,6 +960,14 @@ void
|
||||
{
|
||||
audioControlAverage.Add(control_value);
|
||||
|
||||
if (getenv("BT_AUDIO_SPATIAL")) { static int s_sa=0;
|
||||
if ((controlID == 100 || controlID == 101) && s_sa++ < 3000)
|
||||
DEBUG_STREAM << "[smooth] t=" << (GetTickCount() % 1000000)
|
||||
<< " this=" << (void*)this << " ctl=" << (int)controlID
|
||||
<< " in=" << control_value
|
||||
<< " avg=" << audioControlAverage.CalculateOlympicAverage()
|
||||
<< "\n" << std::flush; }
|
||||
|
||||
Check(audioComponentSocket.GetCurrent());
|
||||
audioComponentSocket.GetCurrent()->ReceiveControl(
|
||||
controlID,
|
||||
@@ -1315,6 +1344,10 @@ void
|
||||
nextSampleTime += sampleDuration;
|
||||
|
||||
Check(audioComponentSocket.GetCurrent());
|
||||
if (getenv("BT_AUDIO_SPATIAL")) { static int s_sh=0; if (s_sh++<60)
|
||||
DEBUG_STREAM << "[snh] this=" << (void*)this << " -> tgt=" << (void*)audioComponentSocket.GetCurrent()
|
||||
<< " ctlID=" << (int)audioControlID << " val=" << currentValue
|
||||
<< " range=[" << minValue << "," << maxValue << "]\n" << std::flush; }
|
||||
audioComponentSocket.GetCurrent()->ReceiveControl(
|
||||
audioControlID,
|
||||
currentValue
|
||||
|
||||
@@ -232,6 +232,12 @@ class AudioControlMixer:
|
||||
public AudioComponent
|
||||
{
|
||||
public:
|
||||
// DIAG/game-facing: expose the authored output control so game-side control
|
||||
// broadcasts (the foot-plant intensity send) can target only volume/brightness
|
||||
// mix stages and never Start-outputting stages.
|
||||
AudioControlID GetOutputControlID() const { return outputControlID; }
|
||||
AudioComponent *GetTargetComponent() { return audioComponentSocket.GetCurrent(); }
|
||||
|
||||
//
|
||||
//--------------------------------------------------------------------
|
||||
// Construction, Destruction, Testing
|
||||
@@ -307,6 +313,12 @@ class AudioControlMultiplier:
|
||||
public AudioComponent
|
||||
{
|
||||
public:
|
||||
// DIAG/game-facing: expose the authored output control so game-side control
|
||||
// broadcasts (the foot-plant intensity send) can target only volume/brightness
|
||||
// mix stages and never Start-outputting stages.
|
||||
AudioControlID GetOutputControlID() const { return outputControlID; }
|
||||
AudioComponent *GetTargetComponent() { return audioComponentSocket.GetCurrent(); }
|
||||
|
||||
//
|
||||
//--------------------------------------------------------------------
|
||||
// Construction, Destruction, Testing
|
||||
|
||||
+91
-3
@@ -1,3 +1,4 @@
|
||||
#include <cstdlib>
|
||||
#include "munga.h"
|
||||
#pragma hdrstop
|
||||
|
||||
@@ -94,8 +95,15 @@ void
|
||||
}
|
||||
headEntitySocket.Add(entity);
|
||||
|
||||
alDistanceModel(AL_LINEAR_DISTANCE);
|
||||
alDopplerFactor(0.3f);
|
||||
// FIDELITY (AUDIO_FIDELITY.md F3/F10): the engine computes the AUTHORED
|
||||
// distance-attenuation curve (AUDIO.INI amplitude_rolloff knee/exponent ->
|
||||
// AudioLocation::distanceVolumeScale) and the authored doppler-cents model.
|
||||
// Disable OpenAL's own models so they can't double-apply / fight them:
|
||||
// AL_LINEAR_DISTANCE made far battle audio fade to zero on a straight line
|
||||
// (-6 dB vs authored at 300u), and AL doppler ran with the wrong constants
|
||||
// AND a sign-inverted velocity feed (approaching sources pitched DOWN).
|
||||
alDistanceModel(AL_NONE);
|
||||
alDopplerFactor(0.0f);
|
||||
|
||||
#if 0
|
||||
//
|
||||
@@ -131,8 +139,39 @@ void
|
||||
//
|
||||
// Increment frame counter
|
||||
//
|
||||
audioFrameCount = Now().ticks;
|
||||
// AUDIO CLOCK CALIBRATION FIX: AudioTime consumers (sequence event timing,
|
||||
// compression curves) assume audioFrameCount advances at the renderer's
|
||||
// calibrationRate (DefaultRendererRate = 30 frames/sec:
|
||||
// Seconds_To_Frames = s * rate). The WinTesla port set raw Now().ticks here
|
||||
// (~hundreds/sec), running sequences ~18x off the authored timing -- the
|
||||
// AudioControlSequence events carrying the real footstep volumes never
|
||||
// landed where authored. Convert ticks -> calibrated frames properly.
|
||||
{
|
||||
double tps = (double)SystemClock::GetTicksPerSecond();
|
||||
if (tps <= 0.0) tps = 1000.0; // GetTickCount ms fallback (static not yet measured)
|
||||
audioFrameCount = (AudioFrameCount)(
|
||||
(double)Now().ticks
|
||||
* (double)application->GetAudioRenderer()->GetCalibrationRate()
|
||||
/ tps);
|
||||
}
|
||||
Verify(audioFrameCount < LONG_MAX);
|
||||
if (getenv("BT_AUDIO_SPATIAL")) { static long s_hx=0; if ((++s_hx % 300)==0)
|
||||
DEBUG_STREAM << "[audioclock] frame=" << audioFrameCount << " execs=" << s_hx << "\n" << std::flush; }
|
||||
|
||||
// (task #50, AUDIO_FIDELITY F13) service the authored release fades
|
||||
// registered by PatchLevelOfDetail::StopNote (dB-linear note-off ramps).
|
||||
{
|
||||
extern void PRESET_serviceReleaseFades(float elapsed_seconds);
|
||||
static long s_lastFadeTicks = 0;
|
||||
long now_ticks = Now().ticks;
|
||||
if (s_lastFadeTicks != 0 && now_ticks > s_lastFadeTicks)
|
||||
{
|
||||
double tps = (double)SystemClock::GetTicksPerSecond();
|
||||
if (tps <= 0.0) tps = 1000.0;
|
||||
PRESET_serviceReleaseFades((float)((now_ticks - s_lastFadeTicks) / tps));
|
||||
}
|
||||
s_lastFadeTicks = now_ticks;
|
||||
}
|
||||
|
||||
//set current listener orientation
|
||||
Vector3D headVelocity;
|
||||
@@ -140,6 +179,32 @@ void
|
||||
headVelocity.MultiplyByInverse(this->GetHeadEntity()->GetWorldLinearVelocity(), this->GetHeadEntity()->localToWorld);
|
||||
alListener3f(AL_VELOCITY, -headVelocity.x, -headVelocity.y, -headVelocity.z);
|
||||
|
||||
// LIVE-PLAYING DUMP (BT_AUDIO_DUMP): once a second list every playing AL
|
||||
// source with its sample name / gain / pitch / loop -- catches "mystery
|
||||
// sounds" (wrong sample, wrong pitch, chopped) red-handed.
|
||||
if (getenv("BT_AUDIO_DUMP")) {
|
||||
static long s_dumpTick = 0;
|
||||
if ((++s_dumpTick % 45) == 0) {
|
||||
extern ALuint *g_buffers; extern int g_numBuffers; extern const char *g_bufferNames[512];
|
||||
for (ALuint sid = 1; sid <= 40; ++sid) {
|
||||
if (!alIsSource(sid)) continue;
|
||||
ALint st = 0; alGetSourcei(sid, AL_SOURCE_STATE, &st);
|
||||
if (st != AL_PLAYING) continue;
|
||||
ALint buf = 0, looping = 0; ALfloat gain = 0, pitch = 0;
|
||||
alGetSourcei(sid, AL_BUFFER, &buf);
|
||||
alGetSourcei(sid, AL_LOOPING, &looping);
|
||||
alGetSourcef(sid, AL_GAIN, &gain);
|
||||
alGetSourcef(sid, AL_PITCH, &pitch);
|
||||
const char *nm = "?";
|
||||
for (int b = 0; b < g_numBuffers && b < 512; ++b)
|
||||
if ((ALint)g_buffers[b] == buf) { nm = g_bufferNames[b] ? g_bufferNames[b] : "?"; break; }
|
||||
DEBUG_STREAM << "[playing] src=" << sid << " " << nm
|
||||
<< " gain=" << gain << " pitch=" << pitch
|
||||
<< " loop=" << looping << "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
//
|
||||
// Get the entity
|
||||
@@ -398,6 +463,29 @@ void
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
AudioComponent::PrimeWatchers(int passes)
|
||||
{
|
||||
Check(this);
|
||||
for (int pass = 0; pass < passes; ++pass)
|
||||
{
|
||||
ChainIteratorOf<Component*> iterator(&audioWatcherSocket);
|
||||
Component *component;
|
||||
while ((component = iterator.ReadAndNext()) != NULL)
|
||||
{
|
||||
Check(component);
|
||||
// recurse gate-free: an AudioComponent child (mixer/smoother/
|
||||
// splitter) pumps ITS chain; an attribute watcher re-reads +
|
||||
// re-sends. One pass per level keeps total work = passes x chain.
|
||||
component->PrimeWatchers(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
//#############################################################################
|
||||
|
||||
@@ -454,6 +454,14 @@ public:
|
||||
void
|
||||
ExecuteWatchers();
|
||||
|
||||
// (task #50, AUDIO_FIDELITY F19) gate-free watcher pump for the transient
|
||||
// cold-start prime: ExecuteWatchers above is frame-gated
|
||||
// (DefaultAudioFrameDelay), so N calls in one tick collapse to one.
|
||||
// Recurses through the watcher CHAIN (source <- mixer <- smoother <-
|
||||
// scale), each hop gate-free.
|
||||
virtual void
|
||||
PrimeWatchers(int passes);
|
||||
|
||||
void
|
||||
Execute();
|
||||
|
||||
|
||||
@@ -162,6 +162,11 @@ protected:
|
||||
AudioLevelOfDetail*
|
||||
GetAudioLevelOfDetail();
|
||||
|
||||
public:
|
||||
// (task #50) public read of the active LOD -- the game-side footstep
|
||||
// intensity send identifies footstep sources by their patch bank/id.
|
||||
AudioLevelOfDetail* PeekAudioLevelOfDetail() { return GetAudioLevelOfDetail(); }
|
||||
|
||||
private:
|
||||
//
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
@@ -174,6 +174,10 @@ void
|
||||
|
||||
if (audio_source->IsAudioSourceClipped(GetAudioHead()))
|
||||
{
|
||||
if (getenv("BT_AUDIO_SPATIAL")) { static int s_cl=0; if (s_cl++<40)
|
||||
DEBUG_STREAM << "[spatial] CLIPPED src=" << (void*)audio_source
|
||||
<< " headPos=(" << GetAudioHead()->GetHeadEntity()->localOrigin.linearPosition.x
|
||||
<< "," << GetAudioHead()->GetHeadEntity()->localOrigin.linearPosition.z << ")\n" << std::flush; }
|
||||
//
|
||||
// If it is a transient source then ignore request
|
||||
//
|
||||
@@ -181,7 +185,7 @@ void
|
||||
{
|
||||
#ifdef LAB_ONLY
|
||||
sourceClippedCount++;
|
||||
#endif
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
audio_source_priority = audio_source->GetAudioSourcePriority();
|
||||
@@ -190,7 +194,25 @@ void
|
||||
else
|
||||
{
|
||||
audio_source_priority = audio_source->GetAudioSourcePriority();
|
||||
// (task #50, AUDIO_FIDELITY F19) COLD-START PRIME: an idle source's own
|
||||
// watcher socket only executes at Start attempts, so its authored
|
||||
// AudioControlSmoothers (footstep volume: N=30/15, fill 0) warmed ONE
|
||||
// sample per attempt -- and the transient drop gate below (vol < 0.3)
|
||||
// rejected the first ~25 footfalls (~10-20 s of silent steps) before
|
||||
// the average could cross the gate. On a Start request, pump the
|
||||
// source's watchers a full smoother window so the volume chain is
|
||||
// evaluated at its true steady state; the smoother keeps its authored
|
||||
// smoothing role for live variation once the source is playing.
|
||||
if (message->controlID == StartAudioControlID)
|
||||
{
|
||||
audio_source->PrimeWatchers(30);
|
||||
}
|
||||
audio_source_volume_scale = audio_source->CalculateSourceVolumeScale();
|
||||
if (getenv("BT_AUDIO_SPATIAL")) { static int s_vs=0; if ((s_vs++ % 120)==0)
|
||||
DEBUG_STREAM << "[spatial] request src=" << (void*)audio_source
|
||||
<< " vol=" << audio_source_volume_scale
|
||||
<< " headPos=(" << GetAudioHead()->GetHeadEntity()->localOrigin.linearPosition.x
|
||||
<< "," << GetAudioHead()->GetHeadEntity()->localOrigin.linearPosition.z << ")\n" << std::flush; }
|
||||
}
|
||||
|
||||
//
|
||||
@@ -199,17 +221,38 @@ void
|
||||
// Then return
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// DIAG (BT_AUDIO_NODROP): deliver low-volume transient starts anyway (at a
|
||||
// floor volume) to isolate volume-feed problems from the rest of the chain.
|
||||
if (getenv("BT_AUDIO_NODROP") &&
|
||||
message->controlID == StartAudioControlID &&
|
||||
audio_source->GetAudioRenderType() == TransientAudioRenderType &&
|
||||
audio_source_volume_scale < LowAudioVolumeThreshold)
|
||||
{
|
||||
DEBUG_STREAM << "[spatial] NODROP forcing start src=" << (void*)audio_source
|
||||
<< " vol=" << audio_source_volume_scale << "\n" << std::flush;
|
||||
audio_source->ReceiveControl(VolumeAudioControlID, 0.7f);
|
||||
audio_source_volume_scale = 0.7f;
|
||||
}
|
||||
if (
|
||||
message->controlID == StartAudioControlID &&
|
||||
audio_source->GetAudioRenderType() == TransientAudioRenderType &&
|
||||
audio_source_volume_scale < LowAudioVolumeThreshold
|
||||
)
|
||||
{
|
||||
if (getenv("BT_AUDIO_SPATIAL")) { static int s_dr=0; if (s_dr++<40)
|
||||
DEBUG_STREAM << "[spatial] DROP transient start t=" << (GetTickCount() % 1000000)
|
||||
<< " src=" << (void*)audio_source
|
||||
<< " vol=" << audio_source_volume_scale
|
||||
<< " (below threshold " << LowAudioVolumeThreshold << ")\n" << std::flush; }
|
||||
#ifdef LAB_ONLY
|
||||
sourceClippedCount++;
|
||||
#endif
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
if (getenv("BT_AUDIO_SPATIAL") && message->controlID == StartAudioControlID) {
|
||||
static int s_st=0; if (s_st++<40)
|
||||
DEBUG_STREAM << "[spatial] START request src=" << (void*)audio_source
|
||||
<< " vol=" << audio_source_volume_scale << "\n" << std::flush; }
|
||||
|
||||
//
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <cstdlib>
|
||||
#include "munga.h"
|
||||
#pragma hdrstop
|
||||
|
||||
@@ -67,6 +68,9 @@ void
|
||||
{
|
||||
Check(this);
|
||||
Check(audio_component);
|
||||
if (getenv("BT_AUDIO_SPATIAL")) { static int s_ev=0; if (s_ev++<200)
|
||||
DEBUG_STREAM << "[seqev] tgt=" << (void*)audio_component
|
||||
<< " ctl=" << (int)audioControlID << "/" << audioControlValue << "\n" << std::flush; }
|
||||
audio_component->ReceiveControl(audioControlID, audioControlValue);
|
||||
}
|
||||
|
||||
@@ -189,6 +193,16 @@ AudioControlSequence::AudioControlSequence(
|
||||
audioControlEventSocket.Add(audio_control_event);
|
||||
}
|
||||
|
||||
if (getenv("BT_ATTRBIND_LOG")) { static int s_sq=0; if (s_sq++<60) {
|
||||
DEBUG_STREAM << "[seqcfg] seq=" << (void*)this << " tgt=" << (void*)audio_component
|
||||
<< " looped=" << (int)is_looped << " div=" << (int)divisions_per_beat
|
||||
<< " tempo=" << (int)tempo << " events=" << (int)number_of_control_events << " ";
|
||||
{ SChainIteratorOf<AudioControlEvent*> it(&audioControlEventSocket);
|
||||
AudioControlEvent *e; int n=0;
|
||||
while ((e = it.ReadAndNext()) != NULL && n++ < 24)
|
||||
DEBUG_STREAM << *e; }
|
||||
DEBUG_STREAM << "\n" << std::flush; } }
|
||||
|
||||
AudioControlSequenceX(
|
||||
audio_component,
|
||||
entity,
|
||||
@@ -378,6 +392,9 @@ Logical
|
||||
void
|
||||
AudioControlSequence::StartSequence()
|
||||
{
|
||||
if (getenv("BT_ATTRBIND_LOG")) { static int s_ss=0; if (s_ss++<40)
|
||||
DEBUG_STREAM << "[seqstart] seq=" << (void*)this << "\n" << std::flush; }
|
||||
|
||||
Check(this);
|
||||
|
||||
//
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <cstdlib>
|
||||
#include "munga.h"
|
||||
#pragma hdrstop
|
||||
|
||||
@@ -416,6 +417,12 @@ AudioControlValue
|
||||
volume_scale;
|
||||
|
||||
volume_scale = volumeScale * volumeMixScale * volumeCompressionScale;
|
||||
if (getenv("BT_AUDIO_SPATIAL") && volume_scale <= 0.0f) {
|
||||
static int s_z=0; if (s_z++<40)
|
||||
DEBUG_STREAM << "[spatial] vol=0 breakdown src=" << (void*)this
|
||||
<< " volumeScale=" << volumeScale
|
||||
<< " mixScale=" << volumeMixScale
|
||||
<< " compression=" << volumeCompressionScale << "\n" << std::flush; }
|
||||
Clamp(volume_scale, MinAudioVolume, MaxAudioVolume);
|
||||
return volume_scale;
|
||||
}
|
||||
@@ -656,6 +663,8 @@ void
|
||||
Check(this);
|
||||
Clamp(control_value, MinAudioVolume, MaxAudioVolume);
|
||||
volumeScale = control_value / (MaxAudioVolume - MinAudioVolume);
|
||||
if (getenv("BT_AUDIO_SPATIAL")) { static int s_vh=0; if (s_vh++<300)
|
||||
DEBUG_STREAM << "[volset] src=" << (void*)this << " vol=" << volumeScale << "\n" << std::flush; }
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <cstdlib>
|
||||
#include "munga.h"
|
||||
#pragma hdrstop
|
||||
|
||||
@@ -134,6 +135,12 @@ AudioMotionTrigger::AudioMotionTrigger(
|
||||
MemoryStream_Read(stream, &motionType);
|
||||
MemoryStream_Read(stream, &motionValue);
|
||||
|
||||
if (getenv("BT_ATTRBIND_LOG")) { static int s_mt=0; if (s_mt++<40)
|
||||
DEBUG_STREAM << "[motiontrigcfg] attrPtr=" << (void*)attributePointer
|
||||
<< " motionType=" << (int)motionType << " (0=linear,1=angular)"
|
||||
<< " motionValue=" << (int)motionValue << " (0=X,1=Y,2=Z,3=len)"
|
||||
<< "\n" << std::flush; }
|
||||
|
||||
PrimeWatcher();
|
||||
}
|
||||
|
||||
@@ -283,6 +290,12 @@ AudioMotionScale::AudioMotionScale(
|
||||
MemoryStream_Read(stream, &motionType);
|
||||
MemoryStream_Read(stream, &motionValue);
|
||||
|
||||
if (getenv("BT_ATTRBIND_LOG")) { static int s_ms=0; if (s_ms++<40)
|
||||
DEBUG_STREAM << "[motionscalecfg] attrPtr=" << (void*)attributePointer
|
||||
<< " motionType=" << (int)motionType << " (0=linear,1=angular)"
|
||||
<< " motionValue=" << (int)motionValue << " (0=X,1=Y,2=Z,3=len)"
|
||||
<< "\n" << std::flush; }
|
||||
|
||||
PrimeWatcher();
|
||||
}
|
||||
|
||||
@@ -873,6 +886,31 @@ AudioStateWatcher::AudioStateWatcher(
|
||||
AudioWatcherOf<StateIndicator>(stream, entity)
|
||||
{
|
||||
Check_Pointer(attributePointer);
|
||||
// BRING-UP GUARD [T3, temporary]: an AudioStateWatcher binds to a StateIndicator
|
||||
// attribute BY NAME. The Mech's own state indicators (SimulationState, Animation/
|
||||
// ReplicantAnimationState, CollisionState) are real; but audio also binds state
|
||||
// attrs on subsystems that are not fully reconstructed yet (GeneratorState,
|
||||
// CondenserState, Torso MotionState, ...). Those resolve to an unconstructed
|
||||
// object -- a null/garbage vtable at +0 or a debug-fill (0xCDCDCDCD) watcher chain
|
||||
// at +0x18 -- and AddAudioWatcher would AV in SChainOf::Add. Skip the register
|
||||
// (that subsystem's state audio stays silent) until the subsystem is built; this
|
||||
// is SELF-CLEARING (a real StateIndicator passes). See docs: audio subsystem wave.
|
||||
// Validate the AUDIO SOCKET at +0x18 (what AddAudioWatcher touches), NOT the
|
||||
// object's +0 vtable: a real StateIndicator has a vtable at +0, but the binary's
|
||||
// 0x54 subsystem alarm (GaugeAlarm54) is non-polymorphic there (a raw header) yet
|
||||
// has a real, constructed SChainOf socket at +0x18. A skip means the socket is
|
||||
// unconstructed (null / debug-fill 0xCDCDCDCD) -- the inert pad or a subsystem not
|
||||
// yet reconstructed. Registering there would AV in SChainOf::Add.
|
||||
{
|
||||
unsigned chain = *(unsigned*)((char*)attributePointer + 0x18);
|
||||
if (chain == 0 || chain == 0xCDCDCDCD)
|
||||
{
|
||||
if (getenv("BT_AUDIO_LOG"))
|
||||
DEBUG_STREAM << "[audiostate] skip watcher on unbuilt StateIndicator "
|
||||
<< attributePointer << " (chain=" << (void*)chain << ")\n" << std::flush;
|
||||
return;
|
||||
}
|
||||
}
|
||||
Cast_Object(StateIndicator*, attributePointer)->AddAudioWatcher(this);
|
||||
}
|
||||
|
||||
@@ -936,6 +974,14 @@ AudioStateTrigger::AudioStateTrigger(
|
||||
MemoryStream_Read(stream, &excludeTransition);
|
||||
MemoryStream_Read(stream, &excludeState);
|
||||
|
||||
if (getenv("BT_ATTRBIND_LOG")) { static int s_stc=0; if (s_stc++<120)
|
||||
DEBUG_STREAM << "[statecfg] attrPtr=" << (void*)attributePointer
|
||||
<< " comp=" << (void*)audioComponentSocket.GetCurrent()
|
||||
<< " trigState=" << triggerState << " inv=" << (int)inverseTrigger
|
||||
<< " ctl=" << (int)controlID << "/" << controlValue
|
||||
<< " excl=" << (int)excludeTransition << "/" << excludeState
|
||||
<< "\n" << std::flush; }
|
||||
|
||||
PrimeWatcher();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include <cstdlib>
|
||||
|
||||
#include "watcher.h"
|
||||
#include "audio.h"
|
||||
@@ -304,6 +305,14 @@ template <class T>
|
||||
MemoryStream_Read(stream, &controlValueOff);
|
||||
|
||||
triggerOn = False;
|
||||
|
||||
if (getenv("BT_ATTRBIND_LOG")) { static int s_tc=0; if (s_tc++<80)
|
||||
DEBUG_STREAM << "[trigcfg] attrPtr=" << (void*)attributePointer << " comp=" << (void*)audioComponentSocket.GetCurrent()
|
||||
<< " thresh=" << attributeValueThreshold
|
||||
<< " inverse=" << (int)inverseTrigger
|
||||
<< " onID=" << (int)controlIDOn << "/" << controlValueOn
|
||||
<< " offID=" << (int)controlIDOff << "/" << controlValueOff
|
||||
<< "\n" << std::flush; }
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -357,6 +366,10 @@ template <class T> void
|
||||
Check_Pointer(attribute_ptr);
|
||||
Scalar current_value = ExtractInterestingValue(attribute_ptr);
|
||||
|
||||
if (getenv("BT_AUDIO_SPATIAL") && current_value > 0.0f) { static int s_tg=0; if (s_tg++<120)
|
||||
DEBUG_STREAM << "[trigger] attrPtr=" << (void*)attributePointer
|
||||
<< " val=" << current_value << " thresh=" << attributeValueThreshold
|
||||
<< " armed=" << (int)!triggerOn << "\n" << std::flush; }
|
||||
if (triggerOn)
|
||||
{
|
||||
if (
|
||||
@@ -432,6 +445,30 @@ public:
|
||||
);
|
||||
~AudioScaleOf();
|
||||
|
||||
//
|
||||
//--------------------------------------------------------------------
|
||||
// Execute -- scales send EVERY poll (task #50, AUDIO_FIDELITY F19).
|
||||
//
|
||||
// The base watcher gate is a BITWISE compare (Motion::operator== is
|
||||
// memcmp) that existed to skip truly static values. In the original,
|
||||
// scale-watched attributes (velocities, accelerations, temperatures)
|
||||
// were noisy physics floats that practically changed every poll, so
|
||||
// scales streamed per-poll values into their authored consumers -- the
|
||||
// footstep AudioControlSmoothers (N=30/15, fill 0) are SIZED for that
|
||||
// cadence. Our reconstruction's math can be deterministic (the gait
|
||||
// integrator lands on bit-identical derived values during smooth
|
||||
// acceleration), which froze the gate and starved the smoothers (the
|
||||
// 10-20 s footstep warm-up). Sending unconditionally restores the
|
||||
// original's practical behavior; triggers/matchers keep the change
|
||||
// gate (their semantics are edge-based).
|
||||
//--------------------------------------------------------------------
|
||||
//
|
||||
void
|
||||
Execute()
|
||||
{
|
||||
GrabCurrentValue();
|
||||
}
|
||||
|
||||
//
|
||||
//--------------------------------------------------------------------
|
||||
// BuildFromPage
|
||||
@@ -508,6 +545,13 @@ template <class T>
|
||||
MemoryStream_Read(stream, &controlValueBoundary1);
|
||||
MemoryStream_Read(stream, &controlValueBoundary2);
|
||||
MemoryStream_Read(stream, &exponent);
|
||||
|
||||
if (getenv("BT_ATTRBIND_LOG")) { static int s_scf=0; if (s_scf++<160)
|
||||
DEBUG_STREAM << "[scalecfg] attrPtr=" << (void*)attributePointer
|
||||
<< " comp=" << (void*)audioComponentSocket.GetCurrent()
|
||||
<< " ctlID=" << (int)controlID
|
||||
<< " aB=[" << attributeValueBoundary1 << "," << attributeValueBoundary2 << "]"
|
||||
<< " cB=[" << controlValueBoundary1 << "," << controlValueBoundary2 << "]\n" << std::flush; }
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -625,6 +669,16 @@ template <class T> void
|
||||
|
||||
Check(&audioComponentSocket);
|
||||
Check(audioComponentSocket.GetCurrent());
|
||||
if (getenv("BT_AUDIO_SPATIAL") && control_value <= 0.0f) {
|
||||
static int s_sc=0; if (s_sc++<400)
|
||||
DEBUG_STREAM << "[spatial] scale->0 attrPtr=" << (void*)attributePointer << " comp=" << (void*)audioComponentSocket.GetCurrent() << " raw=" << current_value << " ctlID=" << (int)controlID << " ctl=" << control_value << " aB=[" << attributeValueBoundary1 << "," << attributeValueBoundary2 << "]" << " cB=[" << controlValueBoundary1 << "," << controlValueBoundary2 << "]\n" << std::flush; }
|
||||
if (getenv("BT_AUDIO_SPATIAL") && (controlID == 100 || controlID == 101)) {
|
||||
static int s_fs2=0; if (s_fs2++<2000)
|
||||
DEBUG_STREAM << "[fsscale] t=" << (GetTickCount() % 1000000)
|
||||
<< " attrPtr=" << (void*)attributePointer
|
||||
<< " comp=" << (void*)audioComponentSocket.GetCurrent()
|
||||
<< " ctl" << (int)controlID << " raw=" << current_value
|
||||
<< " out=" << control_value << "\n" << std::flush; }
|
||||
audioComponentSocket.GetCurrent()->ReceiveControl(
|
||||
controlID,
|
||||
control_value
|
||||
@@ -721,6 +775,12 @@ template <class T>
|
||||
MemoryStream_Read(stream, &attributeMatchValue);
|
||||
MemoryStream_Read(stream, &controlID);
|
||||
MemoryStream_Read(stream, &controlValue);
|
||||
|
||||
if (getenv("BT_ATTRBIND_LOG")) { static int s_mc=0; if (s_mc++<80)
|
||||
DEBUG_STREAM << "[matchcfg] attrPtr=" << (void*)attributePointer
|
||||
<< " match=" << attributeMatchValue
|
||||
<< " ctlID=" << (int)controlID << " ctlVal=" << controlValue
|
||||
<< "\n" << std::flush; }
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -767,6 +827,10 @@ template <class T> void
|
||||
|
||||
if (current_value == attributeMatchValue)
|
||||
{
|
||||
if (getenv("BT_AUDIO_SPATIAL")) { static int s_mf=0; if (s_mf++<40)
|
||||
DEBUG_STREAM << "[matchfire] attrPtr=" << (void*)attributePointer << " comp=" << (void*)audioComponentSocket.GetCurrent() << " compClass=" << (int)audioComponentSocket.GetCurrent()->GetClassID()
|
||||
<< " val=" << current_value << " -> ctl " << (int)controlID
|
||||
<< "/" << controlValue << "\n" << std::flush; }
|
||||
Check(&audioComponentSocket);
|
||||
Check(audioComponentSocket.GetCurrent());
|
||||
#if 1
|
||||
|
||||
@@ -11,6 +11,17 @@ public:
|
||||
|
||||
virtual void Execute();
|
||||
|
||||
// (task #50, AUDIO_FIDELITY F19) gate-free watcher pump for the audio
|
||||
// transient cold-start prime. Default: run Execute() N times (attribute
|
||||
// watchers re-read + re-send each pass). AudioComponent overrides it to
|
||||
// RECURSE through its watcher chain, bypassing the per-component
|
||||
// audio-frame gate that otherwise collapses the passes to one.
|
||||
virtual void PrimeWatchers(int passes)
|
||||
{
|
||||
for (int pass = 0; pass < passes; ++pass)
|
||||
Execute();
|
||||
}
|
||||
|
||||
static Derivation *GetClassDerivations();
|
||||
static SharedData DefaultData;
|
||||
|
||||
|
||||
+60
-8
@@ -11,6 +11,34 @@
|
||||
#include "line.h"
|
||||
#include "app.h"
|
||||
#include "notation.h"
|
||||
#include <math.h>
|
||||
|
||||
//
|
||||
// EXACT axis-angle rotation composition -- matches the 1995 BT binary's angular
|
||||
// integrator (FUN_00409f58): build a unit rotation quaternion from the rotation
|
||||
// VECTOR `rotVec` (angle = |rotVec|, axis = rotVec/angle) as { axis*sin(angle/2),
|
||||
// cos(angle/2) } and Hamilton-multiply it onto `base`. The dead-reckoner previously
|
||||
// did `out.Add(base, rotVec)` -- adding a scaled angular-velocity vector to the heading
|
||||
// quaternion. That is only a small-angle approximation: fine per-frame (tiny angle),
|
||||
// but over a long replicant dead-reckon gap it DIVERGES (the heading drifts to ~180deg
|
||||
// then snaps -- the spinning-peer hesitation). This composition is exact for any angle
|
||||
// and stays on the unit sphere.
|
||||
//
|
||||
static void ExactAngularProject(Quaternion &out, const Quaternion &base, const Vector3D &rotVec)
|
||||
{
|
||||
const Scalar ang = rotVec.Length();
|
||||
if (ang > 1.0e-6f)
|
||||
{
|
||||
const Scalar h = 0.5f * (Scalar)fmodf((float)ang, 6.2831853f); // half of angle mod 2pi
|
||||
const Scalar s = (Scalar)(sinf((float)h) / ang); // sin(angle/2)/angle
|
||||
const Quaternion dq(rotVec.x * s, rotVec.y * s, rotVec.z * s, (Scalar)cosf((float)h));
|
||||
out.Multiply(base, dq); // base (X) dq
|
||||
}
|
||||
else
|
||||
{
|
||||
out = base;
|
||||
}
|
||||
}
|
||||
|
||||
//#############################################################################
|
||||
//############################### Mover #################################
|
||||
@@ -395,10 +423,8 @@ Logical
|
||||
//-------------------------------
|
||||
//
|
||||
position_delta.Multiply(updateVelocity.angularMotion, time_slice);
|
||||
projectedOrigin.angularPosition.Add(
|
||||
updateOrigin.angularPosition,
|
||||
position_delta
|
||||
);
|
||||
ExactAngularProject(projectedOrigin.angularPosition, // was .Add (diverging vector-add)
|
||||
updateOrigin.angularPosition, position_delta);
|
||||
projectedVelocity = updateVelocity;
|
||||
|
||||
Check_Fpu();
|
||||
@@ -460,10 +486,8 @@ Logical
|
||||
updateVelocity.angularMotion,
|
||||
time_slice
|
||||
);
|
||||
projectedOrigin.angularPosition.Add(
|
||||
updateOrigin.angularPosition,
|
||||
position_delta
|
||||
);
|
||||
ExactAngularProject(projectedOrigin.angularPosition, // was .Add (diverging vector-add)
|
||||
updateOrigin.angularPosition, position_delta);
|
||||
|
||||
//
|
||||
//-----------------------------------
|
||||
@@ -1113,6 +1137,34 @@ BoxedSolid*
|
||||
return solid;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// The "static world" tail of FindBoxedSolidHitBy on its own: ray-test ONLY the
|
||||
// zone's static solid tree (world structures -- garages, walls, props), NOT the
|
||||
// tangible movers/doors. Same tree the mech's walk collides against, so a shot
|
||||
// designated through this lands on exactly the geometry that blocks the mech.
|
||||
// FindBoundingBoxHitBy clips line->length to the hit distance (HitByBounded ->
|
||||
// line->length = enter), so the caller reads the entry point via line->FindEnd.
|
||||
//
|
||||
BoxedSolid*
|
||||
Mover::FindStaticSolidHitBy(
|
||||
Line *line
|
||||
)
|
||||
{
|
||||
Check(this);
|
||||
Check(line);
|
||||
|
||||
InterestManager *interest_mgr = application->GetInterestManager();
|
||||
Check(interest_mgr);
|
||||
InterestZone *zone = interest_mgr->GetInterestZone(interestZoneID);
|
||||
Check(zone);
|
||||
BoxedSolidTree* tree = zone->GetCollisionRoot();
|
||||
Check(tree);
|
||||
BoxedSolid *result = (BoxedSolid*)tree->FindBoundingBoxHitBy(line);
|
||||
Check_Fpu();
|
||||
return result;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
BoxedSolidCollisionList*
|
||||
|
||||
@@ -300,6 +300,16 @@ public:
|
||||
Line *line,
|
||||
Entity *except_by
|
||||
);
|
||||
// STATIC-WORLD-ONLY ray query: the "test against the static world" tail of
|
||||
// FindBoxedSolidHitBy factored out -- ray-tests ONLY the zone's static solid
|
||||
// tree (the world structures: garages, walls, props), skipping the tangible
|
||||
// movers/doors. Used by the weapon boresight pick so a shot lands on the SAME
|
||||
// static geometry that already blocks the mech's walk (mechs are picked
|
||||
// separately, with their damage zones/lock). Clips line->length to the entry.
|
||||
BoxedSolid*
|
||||
FindStaticSolidHitBy(
|
||||
Line *line
|
||||
);
|
||||
BoxedSolidCollisionList*
|
||||
CollideCenterOfMotion(
|
||||
Line *line,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <cstdlib>
|
||||
#include "munga.h"
|
||||
#pragma hdrstop
|
||||
|
||||
@@ -486,6 +487,14 @@ void
|
||||
//#############################################################################
|
||||
// Watcher Support
|
||||
//
|
||||
int
|
||||
Simulation::DebugAudioWatcherCount()
|
||||
{
|
||||
SChainIteratorOf<Component*> iterator(audioWatcherSocket);
|
||||
return (int)iterator.GetSize();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Simulation::ExecuteWatchers()
|
||||
{
|
||||
@@ -496,6 +505,8 @@ void
|
||||
// Audio
|
||||
{
|
||||
SChainIteratorOf<Component*> iterator(audioWatcherSocket);
|
||||
if (getenv("BT_AUDIO_SPATIAL")) { static int s_ec=0; if ((s_ec++ % 600)==0)
|
||||
DEBUG_STREAM << "[watchpoll] sim=" << (void*)this << " audioSocket size=" << iterator.GetSize() << "\n" << std::flush; }
|
||||
while ((watcher = iterator.ReadAndNext()) != NULL)
|
||||
{
|
||||
watcher->Execute();
|
||||
|
||||
@@ -247,6 +247,9 @@ public:
|
||||
void
|
||||
ExecuteWatchers();
|
||||
|
||||
int // DEBUG (BT_AUDIO_LOG): how many audio watchers are registered on this sim
|
||||
DebugAudioWatcherCount();
|
||||
|
||||
private:
|
||||
SChainOf<Component*>
|
||||
audioWatcherSocket;
|
||||
|
||||
+40
-15
@@ -1,3 +1,4 @@
|
||||
#include <cstdlib>
|
||||
#include "munga.h"
|
||||
#pragma hdrstop
|
||||
|
||||
@@ -95,21 +96,45 @@ AttributeWatcher::AttributeWatcher(
|
||||
|
||||
attributePointer = simulation->GetAttributePointer(attribute_name);
|
||||
|
||||
#if DEBUG_LEVEL>0
|
||||
if (attributePointer == NULL)
|
||||
{
|
||||
Dump(attribute_name);
|
||||
}
|
||||
#else
|
||||
if (attributePointer == NULL)
|
||||
{
|
||||
DEBUG_STREAM <<
|
||||
"AttributeWatcher::AttributeWatcher - attribute " <<
|
||||
attribute_name <<
|
||||
"\n";
|
||||
Fail("AttributeWatcher::AttributeWatcher - attribute not found\n");
|
||||
}
|
||||
#endif
|
||||
if (getenv("BT_ATTRBIND_LOG"))
|
||||
{
|
||||
extern int g_curAudioWatcherClass;
|
||||
DEBUG_STREAM << "[attrbind] class=" << g_curAudioWatcherClass
|
||||
<< " subsys=[" << subsystem_name
|
||||
<< "] attr=[" << attribute_name << "] -> ptr=" << attributePointer
|
||||
<< " vtbl=" << (attributePointer ? *(void**)attributePointer : (void*)0)
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
|
||||
// BRING-UP GUARD [T3, temporary]: audio references attributes on subsystems that
|
||||
// are not fully reconstructed yet (GeneratorState/On, CondenserState, ReportLeak,
|
||||
// Torso SpeedOfTorsoHorizontal/MotionState, Reservoir/Avionics/ControlsMapper ...).
|
||||
// GetAttributePointer returns NULL for those, and the original engine Fail()s
|
||||
// (fatal) -- which would abort every audio-enabled run. Redirect NULL to a shared
|
||||
// inert zero pad so scalar/vector watchers read 0 (that sound stays silent) instead
|
||||
// of crashing; state watchers on it are skipped by the AudioStateWatcher guard.
|
||||
// Self-clearing: a registered attribute resolves to its real member. Remove once
|
||||
// the subsystem attribute tables are reconstructed (the audio subsystem wave).
|
||||
if (attributePointer == NULL)
|
||||
{
|
||||
static char s_missingAttrPad[64] = {0};
|
||||
// TYPED pad: ConfigureActivePress is the held-configure-button INDEX whose
|
||||
// authored idle is -1 (none) -- NINE audio triggers gate the configure-mode
|
||||
// ticker on it with threshold -1 across the configurable subsystems. The
|
||||
// zero pad read as "button 0 held" and started the ticker at load (the
|
||||
// eternal 2.7/s chirp). Redirect this attribute to a -1 pad; others keep 0.
|
||||
static int s_configureIdlePad = -1;
|
||||
CString configure_name("ConfigureActivePress");
|
||||
if (attribute_name == configure_name)
|
||||
attributePointer = &s_configureIdlePad;
|
||||
else
|
||||
attributePointer = s_missingAttrPad;
|
||||
if (getenv("BT_AUDIO_LOG"))
|
||||
DEBUG_STREAM << "[attrnull] " << subsystem_name << "." << attribute_name
|
||||
<< " not reconstructed -> "
|
||||
<< (attributePointer == (void*)&s_configureIdlePad ? "-1 pad" : "inert pad")
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
|
||||
Check_Pointer(attributePointer);
|
||||
#endif
|
||||
|
||||
+30
-1
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include <cstdlib>
|
||||
|
||||
#include "cmpnnt.h"
|
||||
#include "slot.h"
|
||||
@@ -155,14 +156,18 @@ private:
|
||||
void
|
||||
InitializeCurrentValue();
|
||||
|
||||
protected:
|
||||
//
|
||||
//-----------------------------------------------------------------------
|
||||
// GrabCurrentValue
|
||||
// GrabCurrentValue -- protected (task #50): AudioScaleOf's per-poll
|
||||
// Execute override calls it directly (see AUDWTHR.h).
|
||||
//-----------------------------------------------------------------------
|
||||
//
|
||||
void
|
||||
GrabCurrentValue();
|
||||
|
||||
private:
|
||||
|
||||
//
|
||||
//-----------------------------------------------------------------------
|
||||
// DumpValue
|
||||
@@ -262,8 +267,32 @@ template <class T> void
|
||||
Check(this);
|
||||
|
||||
Check_Pointer(attributePointer);
|
||||
if (getenv("BT_AUDIO_SPATIAL")) { // poll-rate probe: total watcher polls + change events
|
||||
static long s_polls=0; if ((++s_polls % 2000)==0)
|
||||
DEBUG_STREAM << "[watchpoll] total polls=" << s_polls << "\n" << std::flush;
|
||||
extern void *g_btFootStepAddr; // DIAG: uncapped tracer on THE footstep watcher
|
||||
if ((void*)attributePointer == g_btFootStepAddr) {
|
||||
static long s_fsp=0;
|
||||
if ((++s_fsp % 120)==0 || !(currentValue == *(T*)attributePointer))
|
||||
DEBUG_STREAM << "[fswatch] poll#" << s_fsp << " cur=" << (int)*(int*)¤tValue
|
||||
<< " mem=" << *(int*)attributePointer << "\n" << std::flush;
|
||||
}
|
||||
extern void *g_btAccelAddr; // DIAG: poll-vs-change split on the accel attr
|
||||
if ((void*)attributePointer == g_btAccelAddr) {
|
||||
static long s_ap=0, s_ac=0;
|
||||
int changed = !(currentValue == *(T*)attributePointer);
|
||||
if (changed) ++s_ac;
|
||||
if ((++s_ap % 120)==0 || (changed && (s_ac % 30)==0))
|
||||
DEBUG_STREAM << "[accwatch] t=" << (GetTickCount() % 1000000)
|
||||
<< " poll#" << s_ap << " changes=" << s_ac
|
||||
<< " memY=" << ((float*)attributePointer)[1]
|
||||
<< " curY=" << ((float*)¤tValue)[1] << "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
if (!(currentValue == *(T*)attributePointer))
|
||||
{
|
||||
if (getenv("BT_AUDIO_SPATIAL")) { static int s_chg=0; if (s_chg++<60)
|
||||
DEBUG_STREAM << "[watchpoll] CHANGE attrPtr=" << (void*)attributePointer << "\n" << std::flush; }
|
||||
#if DEBUG_LEVEL>0
|
||||
if (dumpValue)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
//###########################################################################
|
||||
//
|
||||
// L4AUDEFX.cpp -- OpenAL EFX bridge (task #50, AUDIO_FIDELITY F9/F11).
|
||||
// See L4AUDEFX.h for the fidelity rationale.
|
||||
//
|
||||
//###########################################################################
|
||||
#include <cstdlib>
|
||||
#include "mungal4.h"
|
||||
#pragma hdrstop
|
||||
|
||||
#include "l4audefx.h"
|
||||
#include "openal/alc.h"
|
||||
#include "openal/efx.h"
|
||||
|
||||
#ifndef AL_EFFECT_EAXREVERB
|
||||
#define AL_EFFECT_EAXREVERB 0x8000 // newer efx.h constant; OpenAL Soft supports it
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
bool s_available = false;
|
||||
ALuint s_reverbSlot = 0;
|
||||
ALuint s_reverbEffect = 0;
|
||||
ALuint s_scratchFilter = 0;
|
||||
|
||||
LPALGENEFFECTS p_alGenEffects = 0;
|
||||
LPALEFFECTI p_alEffecti = 0;
|
||||
LPALEFFECTF p_alEffectf = 0;
|
||||
LPALGENAUXILIARYEFFECTSLOTS p_alGenAuxiliaryEffectSlots = 0;
|
||||
LPALAUXILIARYEFFECTSLOTI p_alAuxiliaryEffectSloti = 0;
|
||||
LPALAUXILIARYEFFECTSLOTF p_alAuxiliaryEffectSlotf = 0;
|
||||
LPALGENFILTERS p_alGenFilters = 0;
|
||||
LPALFILTERI p_alFilteri = 0;
|
||||
LPALFILTERF p_alFilterf = 0;
|
||||
}
|
||||
|
||||
bool EFX_Available()
|
||||
{
|
||||
return s_available;
|
||||
}
|
||||
|
||||
bool EFX_Initialize(float global_reverb_scale)
|
||||
{
|
||||
ALCcontext *context = alcGetCurrentContext();
|
||||
if (context == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ALCdevice *device = alcGetContextsDevice(context);
|
||||
if (device == 0 || !alcIsExtensionPresent(device, "ALC_EXT_EFX"))
|
||||
{
|
||||
if (getenv("BT_AUDIO_LOG"))
|
||||
DEBUG_STREAM << "[audio] EFX: ALC_EXT_EFX NOT present -- filters/reverb inert\n" << std::flush;
|
||||
return false;
|
||||
}
|
||||
|
||||
p_alGenEffects = (LPALGENEFFECTS)alGetProcAddress("alGenEffects");
|
||||
p_alEffecti = (LPALEFFECTI)alGetProcAddress("alEffecti");
|
||||
p_alEffectf = (LPALEFFECTF)alGetProcAddress("alEffectf");
|
||||
p_alGenAuxiliaryEffectSlots = (LPALGENAUXILIARYEFFECTSLOTS)alGetProcAddress("alGenAuxiliaryEffectSlots");
|
||||
p_alAuxiliaryEffectSloti = (LPALAUXILIARYEFFECTSLOTI)alGetProcAddress("alAuxiliaryEffectSloti");
|
||||
p_alAuxiliaryEffectSlotf = (LPALAUXILIARYEFFECTSLOTF)alGetProcAddress("alAuxiliaryEffectSlotf");
|
||||
p_alGenFilters = (LPALGENFILTERS)alGetProcAddress("alGenFilters");
|
||||
p_alFilteri = (LPALFILTERI)alGetProcAddress("alFilteri");
|
||||
p_alFilterf = (LPALFILTERF)alGetProcAddress("alFilterf");
|
||||
|
||||
if (!p_alGenEffects || !p_alEffecti || !p_alEffectf
|
||||
|| !p_alGenAuxiliaryEffectSlots || !p_alAuxiliaryEffectSloti || !p_alAuxiliaryEffectSlotf
|
||||
|| !p_alGenFilters || !p_alFilteri || !p_alFilterf)
|
||||
{
|
||||
if (getenv("BT_AUDIO_LOG"))
|
||||
DEBUG_STREAM << "[audio] EFX: entry points missing -- filters/reverb inert\n" << std::flush;
|
||||
return false;
|
||||
}
|
||||
|
||||
alGetError();
|
||||
p_alGenAuxiliaryEffectSlots(1, &s_reverbSlot);
|
||||
p_alGenEffects(1, &s_reverbEffect);
|
||||
if (alGetError() != AL_NO_ERROR)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// EAXReverb where available (OpenAL Soft: yes), plain reverb otherwise.
|
||||
p_alEffecti(s_reverbEffect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB);
|
||||
if (alGetError() != AL_NO_ERROR)
|
||||
{
|
||||
p_alEffecti(s_reverbEffect, AL_EFFECT_TYPE, AL_EFFECT_REVERB);
|
||||
}
|
||||
p_alAuxiliaryEffectSloti(s_reverbSlot, AL_EFFECTSLOT_EFFECT, (ALint)s_reverbEffect);
|
||||
// The authentic wet level: the original sent CC91 = global_reverb_scale
|
||||
// (0.3 -> value 38) on every 3D channel; one global slot gain reproduces
|
||||
// the same uniform send.
|
||||
p_alAuxiliaryEffectSlotf(s_reverbSlot, AL_EFFECTSLOT_GAIN,
|
||||
(global_reverb_scale < 0.0f) ? 0.0f :
|
||||
(global_reverb_scale > 1.0f) ? 1.0f : global_reverb_scale);
|
||||
|
||||
p_alGenFilters(1, &s_scratchFilter);
|
||||
p_alFilteri(s_scratchFilter, AL_FILTER_TYPE, AL_FILTER_LOWPASS);
|
||||
|
||||
s_available = (alGetError() == AL_NO_ERROR);
|
||||
if (getenv("BT_AUDIO_LOG"))
|
||||
DEBUG_STREAM << "[audio] EFX: " << (s_available ? "READY" : "FAILED")
|
||||
<< " (reverb slot gain=" << global_reverb_scale << ")\n" << std::flush;
|
||||
return s_available;
|
||||
}
|
||||
|
||||
void EFX_SetSourceLowpassGainHF(ALuint source, float gainhf)
|
||||
{
|
||||
if (!s_available)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (gainhf < 0.001f) gainhf = 0.001f;
|
||||
if (gainhf > 1.0f) gainhf = 1.0f;
|
||||
// filter params are COPIED at attach, so one scratch filter serves all
|
||||
p_alFilterf(s_scratchFilter, AL_LOWPASS_GAIN, 1.0f);
|
||||
p_alFilterf(s_scratchFilter, AL_LOWPASS_GAINHF, gainhf);
|
||||
alSourcei(source, AL_DIRECT_FILTER, (ALint)s_scratchFilter);
|
||||
}
|
||||
|
||||
void EFX_AttachReverbSend(ALuint source)
|
||||
{
|
||||
if (!s_available)
|
||||
{
|
||||
return;
|
||||
}
|
||||
alSource3i(source, AL_AUXILIARY_SEND_FILTER, (ALint)s_reverbSlot, 0, AL_FILTER_NULL);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
//###########################################################################
|
||||
//
|
||||
// L4AUDEFX.h -- OpenAL EFX bridge for the authored filter/reverb chains
|
||||
// (task #50, AUDIO_FIDELITY F9/F11).
|
||||
//
|
||||
// The original drove the AWE32 initial-filter-cutoff NRPN per frame
|
||||
// (brightness x distance HF rolloff) and sent CC91 reverb on the 3D
|
||||
// channels (global_reverb_scale=0.3, AUDIO.INI) while keeping the cockpit
|
||||
// Direct channels dry. This bridge reproduces both through OpenAL Soft's
|
||||
// EFX extension: one EAXReverb aux slot + a scratch AL_FILTER_LOWPASS
|
||||
// whose parameters are copied at attach time.
|
||||
//
|
||||
//###########################################################################
|
||||
|
||||
#include "openal/al.h"
|
||||
|
||||
// Load the EFX entry points, create the reverb slot (gain = the authentic
|
||||
// global_reverb_scale) and the scratch lowpass. Call once, with the AL
|
||||
// context current. Returns false (and stays inert) without ALC_EXT_EFX.
|
||||
bool EFX_Initialize(float global_reverb_scale);
|
||||
|
||||
bool EFX_Available();
|
||||
|
||||
// Per-frame direct-path lowpass: gainhf is the linear HF gain at the EFX
|
||||
// 5 kHz reference. Callers map the AWE cutoff (100 + scale*7900/127 Hz)
|
||||
// through EFX_CutoffScaleToGainHF below.
|
||||
void EFX_SetSourceLowpassGainHF(ALuint source, float gainhf);
|
||||
|
||||
// AWE NRPN 21 curve -> EFX gainhf: cutoff_scale in [0,1] of the 100-8000 Hz
|
||||
// span; approximated as the attenuation of a 2-pole lowpass at the 5 kHz
|
||||
// reference [T3 -- curve shape approximate, endpoints exact].
|
||||
inline float EFX_CutoffScaleToGainHF(float cutoff_scale)
|
||||
{
|
||||
if (cutoff_scale < 0.0f) cutoff_scale = 0.0f;
|
||||
if (cutoff_scale > 1.0f) cutoff_scale = 1.0f;
|
||||
float cutoff_hz = 100.0f + cutoff_scale * 7900.0f;
|
||||
float g = (cutoff_hz / 5000.0f) * (cutoff_hz / 5000.0f);
|
||||
return (g > 1.0f) ? 1.0f : ((g < 0.001f) ? 0.001f : g);
|
||||
}
|
||||
|
||||
// Wet-exterior routing: attach the source's aux send to the reverb slot
|
||||
// (Dynamic3D/Static3D). Direct cockpit sources stay dry by default.
|
||||
void EFX_AttachReverbSend(ALuint source);
|
||||
+158
-24
@@ -1,14 +1,29 @@
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include "mungal4.h"
|
||||
#pragma hdrstop
|
||||
|
||||
#include "l4audio.h"
|
||||
#include "l4audlvl.h"
|
||||
#include "l4audefx.h"
|
||||
#include "l4app.h"
|
||||
#include "l4audrnd.h"
|
||||
#include "..\munga\namelist.h"
|
||||
#include "..\munga\player.h"
|
||||
#include "..\rp\vtv.h"
|
||||
|
||||
|
||||
// NOTE -> PITCH (task #50): the AWE32 played every patch at the requested MIDI
|
||||
// note relative to the sample root (60); sequences author notes (klaxon/ambience
|
||||
// rhythms are PITCHED patterns). The port captured noteValue but never applied
|
||||
// any pitch to the AL sources -- everything played at root (the "chirping"
|
||||
// ambience: an authored deep tick at note 51 sounded as a high click). Apply
|
||||
// pitch = 2^((note-60)/12) x the control-chain cents offset at start + per frame.
|
||||
static inline float BTNotePitchFactor(int note_value)
|
||||
{
|
||||
return (float)pow(2.0, ((double)note_value - 60.0) / 12.0);
|
||||
}
|
||||
|
||||
//#############################################################################
|
||||
//####################### L4AudioSpatialization #########################
|
||||
//#############################################################################
|
||||
@@ -921,7 +936,13 @@ void
|
||||
patch_resource = Cast_Object(PatchResource*, GetAudioResource());
|
||||
Check(patch_resource);
|
||||
patch_resource->SetDistance(GetDistanceToSource());
|
||||
patch_resource->SetupPatch(channelSet);
|
||||
patch_resource->SetupPatch(channelSet, (int)GetCurrentNoteValue());
|
||||
|
||||
{ // apply the requested note's pitch to the freshly attached sources
|
||||
float note_pitch = BTNotePitchFactor((int)GetCurrentNoteValue());
|
||||
for (int _i = 0; _i < channelSet.count; ++_i)
|
||||
alSourcef(channelSet.sources[_i], AL_PITCH, note_pitch);
|
||||
}
|
||||
|
||||
//
|
||||
// Set the channel to default control values
|
||||
@@ -944,6 +965,31 @@ void
|
||||
channel->SendController(MIDI_PAN_CONTROL, MIDI_RIGHT_PAN_VALUE);
|
||||
break;
|
||||
}*/
|
||||
// (task #50, AUDIO_FIDELITY F12) the original routed each Direct cockpit
|
||||
// source by its authored 6-value position enum (front/rear CARD + pan
|
||||
// CC10 left/center/right). Reproduce as a listener-relative placement;
|
||||
// a zone's own L/R pan (stereo-pair presets) offsets on top. The
|
||||
// distance model is AL_NONE, so this affects direction only, never gain.
|
||||
{
|
||||
float px = 0.0f, pz = -1.0f; // Front, centered
|
||||
switch (audioPosition)
|
||||
{
|
||||
case FrontDirectPatchPosition: px = 0.0f; pz = -1.0f; break;
|
||||
case RearDirectPatchPosition: px = 0.0f; pz = 1.0f; break;
|
||||
case FrontLeftDirectPatchPosition: px = -0.7f; pz = -0.7f; break;
|
||||
case FrontRightDirectPatchPosition: px = 0.7f; pz = -0.7f; break;
|
||||
case RearLeftDirectPatchPosition: px = -0.7f; pz = 0.7f; break;
|
||||
case RearRightDirectPatchPosition: px = 0.7f; pz = 0.7f; break;
|
||||
}
|
||||
for (int _i = 0; _i < channelSet.count; ++_i)
|
||||
{
|
||||
SAMPLEINFO zinfo = PRESET_getSampleInfo(
|
||||
patch_resource->GetBankID(), patch_resource->GetPatchID(), _i);
|
||||
float pan_x = (zinfo.chan == CHANNEL_LEFT) ? -0.5f :
|
||||
(zinfo.chan == CHANNEL_RIGHT) ? 0.5f : 0.0f;
|
||||
alSource3f(channelSet.sources[_i], AL_POSITION, px + pan_x, 0.0f, pz);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Set midi history to default control values
|
||||
@@ -979,7 +1025,7 @@ void
|
||||
//
|
||||
//TODO: Start OpenAL source playing
|
||||
// channel->SendNoteOn(GetCurrentNoteValue(), midi_velocity);
|
||||
patch_resource->PlayNote(channelSet);
|
||||
patch_resource->PlayNote(channelSet, (int)GetCurrentNoteValue());
|
||||
|
||||
#if 0
|
||||
Tell("On " << (int)GetCurrentNoteValue() << "\n");
|
||||
@@ -1033,6 +1079,11 @@ void
|
||||
pitch_offset = CalculateSourcePitchOffset();
|
||||
double relativePitch = pow(2.0,pitch_offset/1200.0);
|
||||
Clamp(relativePitch,0.5,2.0);
|
||||
{ // apply the combined control-chain + note pitch per frame
|
||||
float _np = BTNotePitchFactor((int)GetCurrentNoteValue());
|
||||
for (int _i = 0; _i < channelSet.count; ++_i)
|
||||
alSourcef(channelSet.sources[_i], AL_PITCH, (float)relativePitch * _np);
|
||||
}
|
||||
|
||||
//
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -1057,6 +1108,13 @@ void
|
||||
if (Abs(filter_difference) >= filter_resolution)
|
||||
{
|
||||
lastMIDIFilterCutoff = midi_filter_cutoff;
|
||||
// (task #50, AUDIO_FIDELITY F9) the original sent this cutoff as
|
||||
// AWE NRPN 21; the EFX lowpass is the modern sink (was a dead
|
||||
// bookkeeping write -- ctl-5 Brightness played full-bright).
|
||||
float gainhf = EFX_CutoffScaleToGainHF(
|
||||
(float)lastMIDIFilterCutoff / (float)MIDI_MAX_CONTROL_VALUE);
|
||||
for (int _i = 0; _i < channelSet.count; ++_i)
|
||||
EFX_SetSourceLowpassGainHF(channelSet.sources[_i], gainhf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1076,10 +1134,12 @@ void
|
||||
Check(audio_renderer);
|
||||
AudioHead *audio_head = audio_renderer->GetAudioHead();
|
||||
Check(audio_head);
|
||||
if (getenv("BT_AUDIO_SPATIAL")) { static int s_dp=0; if ((s_dp++ % 120)==0)
|
||||
DEBUG_STREAM << "[spatial] direct src=" << (channelSet.count>0?channelSet.sources[0]:0)
|
||||
<< " vol=" << volume_scale << "\n" << std::flush; }
|
||||
for (int i=0; i < channelSet.count; i++)
|
||||
{
|
||||
alSourcef(channelSet.sources[i],AL_MAX_DISTANCE,audio_location->getMaxDistance(audio_head));
|
||||
alSourcef(channelSet.sources[i], AL_GAIN, volume_scale);
|
||||
alSourcef(channelSet.sources[i], AL_GAIN, volume_scale * volume_scale); // FIDELITY F4: CC7 squared law (linear was ~+6 dB at mid volumes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1204,7 +1264,13 @@ void
|
||||
patch_resource = Cast_Object(PatchResource*, GetAudioResource());
|
||||
Check(patch_resource);
|
||||
patch_resource->SetDistance(GetDistanceToSource());
|
||||
patch_resource->SetupPatch(channelSet);
|
||||
patch_resource->SetupPatch(channelSet, (int)GetCurrentNoteValue());
|
||||
|
||||
{ // apply the requested note's pitch to the freshly attached sources
|
||||
float note_pitch = BTNotePitchFactor((int)GetCurrentNoteValue());
|
||||
for (int _i = 0; _i < channelSet.count; ++_i)
|
||||
alSourcef(channelSet.sources[_i], AL_PITCH, note_pitch);
|
||||
}
|
||||
|
||||
/*patch_resource->SetDistance(GetDistanceToSource());
|
||||
for (i = 0; i < AudioChannelSetSize; i++)
|
||||
@@ -1227,6 +1293,12 @@ void
|
||||
channel->SendController(MIDI_REVERB_CONTROL, midi_reverb_level);
|
||||
channel->SendController(MIDI_CHORUS_CONTROL, MIDI_MIN_CONTROL_VALUE);
|
||||
}*/
|
||||
// (task #50, AUDIO_FIDELITY F11) the CC91 send above is the authentic
|
||||
// wet-exterior contrast (global_reverb_scale on every 3D channel, decomp
|
||||
// part_008.c:7278-7394); the EFX aux slot is the modern sink. Cockpit
|
||||
// Direct sources stay dry (their CC91 was 0).
|
||||
for (int _i = 0; _i < channelSet.count; ++_i)
|
||||
EFX_AttachReverbSend(channelSet.sources[_i]);
|
||||
|
||||
//
|
||||
// Set the channels to correct pan
|
||||
@@ -1299,7 +1371,7 @@ void
|
||||
Check(channel);
|
||||
channel->SendNoteOn(GetCurrentNoteValue(), midi_velocity);
|
||||
}*/
|
||||
patch_resource->PlayNote(channelSet);
|
||||
patch_resource->PlayNote(channelSet, (int)GetCurrentNoteValue());
|
||||
}
|
||||
|
||||
//
|
||||
@@ -1405,15 +1477,43 @@ void
|
||||
|
||||
pitch_offset = CalculateSourcePitchOffset();
|
||||
|
||||
// FIDELITY (AUDIO_FIDELITY.md F10): the AUTHORED doppler (AUDIO.INI
|
||||
// doppler_range/speed_of_sound -> AudioLocation::dopplerCents, computed each
|
||||
// spatial update) -- the decomp proves the original added it to the pitch
|
||||
// cents on THIS dynamic path (part_008.c:7466). AL's own doppler is now
|
||||
// disabled (it ran with wrong constants and a sign-inverted velocity feed).
|
||||
pitch_offset += GetAudioLocation()->GetDopplerCents();
|
||||
|
||||
double relativePitch = pow(2.0,pitch_offset/1200.0);
|
||||
Clamp(relativePitch,0.5,2.0);
|
||||
{ // apply the combined control-chain + note pitch per frame
|
||||
float _np = BTNotePitchFactor((int)GetCurrentNoteValue());
|
||||
for (int _i = 0; _i < channelSet.count; ++_i)
|
||||
alSourcef(channelSet.sources[_i], AL_PITCH, (float)relativePitch * _np);
|
||||
}
|
||||
|
||||
if (getenv("BT_AUDIO_SPATIAL")) { static int s_sp=0; if ((s_sp++ % 120) == 0)
|
||||
DEBUG_STREAM << "[spatial] dyn src=" << (channelSet.count>0?channelSet.sources[0]:0)
|
||||
<< " head=(" << headPosition.x << "," << headPosition.z << ")"
|
||||
<< " loc=(" << locationPosition.x << "," << locationPosition.z << ")"
|
||||
<< " dist=" << posMag << " vol=" << volume_scale << "\n" << std::flush; }
|
||||
|
||||
// (task #50, AUDIO_FIDELITY F9) the original drove the AWE filter cutoff
|
||||
// from highFreqCutoffScale x brightnessScale on this dynamic path --
|
||||
// UNGATED, all channels (part_008.c:7496,7589-7604): every moving 3D
|
||||
// sound gets duller with distance (AUDIO.INI knee 60 / exponent 2.0).
|
||||
float dyn_gainhf = EFX_CutoffScaleToGainHF(
|
||||
(float)(GetAudioLocation()->GetHighFreqCutoffScale()
|
||||
* CalculateSourceBrightnessScale()
|
||||
* (Scalar)patch_resource->GetMaxMIDIFilterCutoff()
|
||||
/ (Scalar)MIDI_MAX_CONTROL_VALUE));
|
||||
|
||||
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);
|
||||
alSourcef(channelSet.sources[i], AL_GAIN, volume_scale * volume_scale); // FIDELITY F4: CC7 squared law (linear was ~+6 dB at mid volumes)
|
||||
alSource3f(channelSet.sources[i],AL_VELOCITY,-relative_velocity.x,-relative_velocity.y,-relative_velocity.z);
|
||||
alSourcef(channelSet.sources[i],AL_MAX_DISTANCE,audio_location->getMaxDistance(audio_head));
|
||||
EFX_SetSourceLowpassGainHF(channelSet.sources[i], dyn_gainhf);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1432,22 +1532,18 @@ AudioControlValue
|
||||
//
|
||||
Scalar
|
||||
volume_scale = L4AudioSource::CalculateSourceVolumeScale();
|
||||
return volume_scale;
|
||||
|
||||
//
|
||||
// Update the spatial model that will result in the value
|
||||
// for distance related volume attenuation
|
||||
//
|
||||
/*Check(application);
|
||||
Check(application->GetAudioRenderer());
|
||||
UpdateSpatialModel(application->GetAudioRenderer()->GetAudioHead());
|
||||
|
||||
//
|
||||
// Apply distance attenuation to the volume scale
|
||||
//
|
||||
// FIDELITY (AUDIO_FIDELITY.md F3): apply the AUTHORED distance attenuation
|
||||
// (AUDIO.INI knee/rolloff curve, computed into distanceVolumeScale on every
|
||||
// spatial update). This multiply was commented out and AL_LINEAR_DISTANCE
|
||||
// substituted -- far sounds faded on a straight line to zero instead of the
|
||||
// authored 1/(1+(k(d-knee))^2) curve, and the volume-based cull / voice
|
||||
// steal / ducking chains were distance-blind. AL's model is now AL_NONE.
|
||||
//
|
||||
Check(GetAudioLocation());
|
||||
volume_scale *= GetAudioLocation()->GetDistanceVolumeScale();
|
||||
return volume_scale;*/
|
||||
return volume_scale;
|
||||
}
|
||||
|
||||
//#############################################################################
|
||||
@@ -1637,6 +1733,22 @@ void
|
||||
// StartRequest(this);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
//#############################################################################
|
||||
//
|
||||
AudioControlValue
|
||||
Static3DPatchSource::CalculateSourceVolumeScale()
|
||||
{
|
||||
Check(this);
|
||||
// FIDELITY (AUDIO_FIDELITY.md F3): same authored distance attenuation as the
|
||||
// dynamic path -- the spatial model computes distanceVolumeScale per execute.
|
||||
Scalar volume_scale = L4AudioSource::CalculateSourceVolumeScale();
|
||||
Check(GetAudioLocation());
|
||||
volume_scale *= GetAudioLocation()->GetDistanceVolumeScale();
|
||||
return volume_scale;
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
//#############################################################################
|
||||
@@ -1693,7 +1805,13 @@ void
|
||||
patch_resource = Cast_Object(PatchResource*, GetAudioResource());
|
||||
Check(patch_resource);
|
||||
patch_resource->SetDistance(GetDistanceToSource());
|
||||
patch_resource->SetupPatch(channelSet);
|
||||
patch_resource->SetupPatch(channelSet, (int)GetCurrentNoteValue());
|
||||
|
||||
{ // apply the requested note's pitch to the freshly attached sources
|
||||
float note_pitch = BTNotePitchFactor((int)GetCurrentNoteValue());
|
||||
for (int _i = 0; _i < channelSet.count; ++_i)
|
||||
alSourcef(channelSet.sources[_i], AL_PITCH, note_pitch);
|
||||
}
|
||||
/*for (i = 0; i < AudioChannelSetSize; i++)
|
||||
{
|
||||
if ((channel = channelSet.GetNth(i)) != NULL)
|
||||
@@ -1718,6 +1836,10 @@ void
|
||||
channel->SendController(MIDI_CHORUS_CONTROL, MIDI_MIN_CONTROL_VALUE);
|
||||
}
|
||||
}*/
|
||||
// (task #50, AUDIO_FIDELITY F11) static 3D sources are wet like the
|
||||
// dynamic ones (the CC91 block above); Direct cockpit stays dry.
|
||||
for (int _i = 0; _i < channelSet.count; ++_i)
|
||||
EFX_AttachReverbSend(channelSet.sources[_i]);
|
||||
|
||||
//
|
||||
// Set the channels to correct pan
|
||||
@@ -1850,7 +1972,7 @@ void
|
||||
channel->SendNoteOn(GetCurrentNoteValue(), midi_velocity);
|
||||
}
|
||||
}*/
|
||||
patch_resource->PlayNote(channelSet);
|
||||
patch_resource->PlayNote(channelSet, (int)GetCurrentNoteValue());
|
||||
|
||||
}
|
||||
|
||||
@@ -1921,6 +2043,11 @@ void
|
||||
pitch_offset = CalculateSourcePitchOffset();
|
||||
double relativePitch = pow(2.0,pitch_offset/1200.0);
|
||||
Clamp(relativePitch,0.5,2.0);
|
||||
{ // apply the combined control-chain + note pitch per frame
|
||||
float _np = BTNotePitchFactor((int)GetCurrentNoteValue());
|
||||
for (int _i = 0; _i < channelSet.count; ++_i)
|
||||
alSourcef(channelSet.sources[_i], AL_PITCH, (float)relativePitch * _np);
|
||||
}
|
||||
|
||||
Vector3D relative_position;
|
||||
Vector3D relative_velocity;
|
||||
@@ -1933,12 +2060,19 @@ void
|
||||
relative_position = audio_location->GetVectorToSource();
|
||||
}
|
||||
|
||||
// (task #50, AUDIO_FIDELITY F9) static sources: brightness-driven cutoff
|
||||
// only (part_008.c:7831-7884 -- the NRPN block commented below).
|
||||
float static_gainhf = EFX_CutoffScaleToGainHF(
|
||||
(float)(CalculateSourceBrightnessScale()
|
||||
* (Scalar)patch_resource->GetMaxMIDIFilterCutoff()
|
||||
/ (Scalar)MIDI_MAX_CONTROL_VALUE));
|
||||
|
||||
//Static models have their position freely available as relative positions and stand still
|
||||
for (int i=0; i < channelSet.count; i++)
|
||||
{
|
||||
alSourcef(channelSet.sources[i], AL_GAIN, volume_scale);
|
||||
alSourcef(channelSet.sources[i], AL_GAIN, volume_scale * volume_scale); // FIDELITY F4: CC7 squared law (linear was ~+6 dB at mid volumes)
|
||||
alSource3f(channelSet.sources[i],AL_POSITION,relative_position.x,relative_position.y,relative_position.z);
|
||||
alSourcef(channelSet.sources[i],AL_MAX_DISTANCE,audio_location->getMaxDistance(audio_head));
|
||||
EFX_SetSourceLowpassGainHF(channelSet.sources[i], static_gainhf);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -552,6 +552,10 @@ public:
|
||||
// State implementations
|
||||
//
|
||||
public:
|
||||
// FIDELITY (AUDIO_FIDELITY.md F3): statics get the authored distance curve too
|
||||
AudioControlValue
|
||||
CalculateSourceVolumeScale();
|
||||
|
||||
void
|
||||
StartImplementation();
|
||||
void
|
||||
|
||||
+174
-12
@@ -1,3 +1,4 @@
|
||||
#include <cstdlib>
|
||||
#include "mungal4.h"
|
||||
#pragma hdrstop
|
||||
|
||||
@@ -8,6 +9,100 @@
|
||||
#include "..\munga\namelist.h"
|
||||
#include "openal/al.h"
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Release fades ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// (task #50, AUDIO_FIDELITY F13) ~20 looping presets author a releaseVolEnv of
|
||||
// 1.1-3.9 s the AWE32 applied as a note-off fade while the loop continued;
|
||||
// StopNote registers a dB-linear ramp here instead of cutting. Serviced once
|
||||
// per frame from AudioHead::Execute; a fade finishing (or being reclaimed by a
|
||||
// restart) stops + rewinds the source so the next SetupPatch sees AL_INITIAL.
|
||||
//
|
||||
namespace
|
||||
{
|
||||
struct ReleaseFade
|
||||
{
|
||||
ALuint source;
|
||||
float gain0;
|
||||
float duration;
|
||||
float elapsed;
|
||||
bool active;
|
||||
};
|
||||
const int MAX_RELEASE_FADES = 64;
|
||||
ReleaseFade s_releaseFades[MAX_RELEASE_FADES];
|
||||
|
||||
void FinishFade(ReleaseFade &fade)
|
||||
{
|
||||
alSourceStop(fade.source);
|
||||
alSourceRewind(fade.source);
|
||||
alSourcef(fade.source, AL_GAIN, fade.gain0);
|
||||
fade.active = false;
|
||||
}
|
||||
|
||||
void StartReleaseFade(ALuint source, float duration)
|
||||
{
|
||||
for (int i = 0; i < MAX_RELEASE_FADES; i++)
|
||||
{
|
||||
if (s_releaseFades[i].active && s_releaseFades[i].source == source)
|
||||
{
|
||||
return; // already releasing
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < MAX_RELEASE_FADES; i++)
|
||||
{
|
||||
if (!s_releaseFades[i].active)
|
||||
{
|
||||
ReleaseFade &fade = s_releaseFades[i];
|
||||
fade.source = source;
|
||||
alGetSourcef(source, AL_GAIN, &fade.gain0);
|
||||
fade.duration = duration;
|
||||
fade.elapsed = 0.0f;
|
||||
fade.active = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// table full -- fall back to the instant cut
|
||||
alSourceStop(source);
|
||||
alSourceRewind(source);
|
||||
}
|
||||
|
||||
// A restart wants this source back NOW: finalize any fade so the caller
|
||||
// sees AL_INITIAL with the pre-fade gain restored.
|
||||
void ReclaimFadingSource(ALuint source)
|
||||
{
|
||||
for (int i = 0; i < MAX_RELEASE_FADES; i++)
|
||||
{
|
||||
if (s_releaseFades[i].active && s_releaseFades[i].source == source)
|
||||
{
|
||||
FinishFade(s_releaseFades[i]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PRESET_serviceReleaseFades(float elapsed_seconds)
|
||||
{
|
||||
for (int i = 0; i < MAX_RELEASE_FADES; i++)
|
||||
{
|
||||
ReleaseFade &fade = s_releaseFades[i];
|
||||
if (!fade.active)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
fade.elapsed += elapsed_seconds;
|
||||
if (fade.elapsed >= fade.duration)
|
||||
{
|
||||
FinishFade(fade);
|
||||
}
|
||||
else
|
||||
{
|
||||
// dB-linear ramp to -96 dB over the authored release time
|
||||
float attenuation = powf(10.0f, -4.8f * (fade.elapsed / fade.duration));
|
||||
alSourcef(fade.source, AL_GAIN, fade.gain0 * attenuation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PatchLevelOfDetail ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
#if DEBUG_LEVEL>0
|
||||
@@ -26,7 +121,10 @@ PatchLevelOfDetail::PatchLevelOfDetail(PlugStream *stream):
|
||||
MemoryStream_Read(stream, &patchID);
|
||||
MemoryStream_Read(stream, &maxMIDIFilterCutoff);
|
||||
|
||||
Warn(GetVoiceCount() > 4); // HACK - AWE appears to only play 1st 4 voices
|
||||
// (task #50) the AWE 4-voice comment applied to layers SHARING a key range;
|
||||
// full-zone presets legitimately carry up to MAX_PRESET_SAMPLES zones
|
||||
// (key-splits select at most a few per note).
|
||||
Warn(GetVoiceCount() > MAX_PRESET_SAMPLES);
|
||||
|
||||
#ifdef LAB_ONLY
|
||||
setupCount = 0;
|
||||
@@ -118,7 +216,7 @@ Logical
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
PatchLevelOfDetail::SetupPatch(SourceSet sourceSet)
|
||||
PatchLevelOfDetail::SetupPatch(SourceSet sourceSet, int note)
|
||||
{
|
||||
// Check(this);
|
||||
//// Check(channel);
|
||||
@@ -128,24 +226,44 @@ void
|
||||
// #endif
|
||||
SAMPLEINFO info;
|
||||
|
||||
//Attach buffers
|
||||
// (task #50, AUDIO_FIDELITY F1) the authored MIDI note SELECTS zones:
|
||||
// attach only samples whose [keyLo,keyHi] contains the note (key-splits
|
||||
// pick one band, layers attach together); detach the rest so a rewound
|
||||
// source can't replay a stale buffer from a previous note.
|
||||
for (int i=0; i < sourceSet.count; i++)
|
||||
{
|
||||
info = PRESET_getSampleInfo(bankID,patchID,i);
|
||||
if (info.bufferIndex >= 0)
|
||||
{
|
||||
ReclaimFadingSource(sourceSet.sources[i]);
|
||||
|
||||
ALenum sourceState;
|
||||
alGetSourcei(sourceSet.sources[i], AL_SOURCE_STATE, &sourceState);
|
||||
|
||||
bool zone_matches = (note >= info.keyLo && note <= info.keyHi);
|
||||
|
||||
if (getenv("BT_AUDIO_SPATIAL")) { static int s_se=0; if (s_se++<200)
|
||||
DEBUG_STREAM << "[audio] SetupPatch ENTRY bank=" << (int)bankID << " patch=" << (int)patchID << " src=" << sourceSet.sources[i]
|
||||
<< " file=" << (info.file?info.file:"?") << " note=" << note << (zone_matches?"":" ZONESKIP")
|
||||
<< " state=" << sourceState << " (INITIAL=" << AL_INITIAL << ")" << "\n" << std::flush; }
|
||||
|
||||
if (sourceState == AL_INITIAL)
|
||||
{
|
||||
alSourcei(sourceSet.sources[i],AL_BUFFER,AL_getBuffer(info.bufferIndex));
|
||||
alSourcei(sourceSet.sources[i],AL_BUFFER,
|
||||
zone_matches ? AL_getBuffer(info.bufferIndex) : 0);
|
||||
{ static int s_a=0; if (getenv("BT_AUDIO_LOG") && s_a++<24) DEBUG_STREAM << "[audio] SetupPatch src=" << sourceSet.sources[i] << " file=" << (info.file?info.file:"?") << " loop=" << (info.loop==ForceStatic?0:1) << "\n" << std::flush; }
|
||||
alSourcei(sourceSet.sources[i],AL_SOURCE_RELATIVE,AL_TRUE);
|
||||
|
||||
AudioRenderer *render = application->GetAudioRenderer();
|
||||
AudioHead *head = render->GetAudioHead();
|
||||
|
||||
alSource3f(sourceSet.sources[i],AL_POSITION,0,0,0);
|
||||
// (F1) authored stereo pairs: pan hard-left/right zones via a
|
||||
// listener-relative offset (the distance model is AL_NONE, so
|
||||
// this affects direction only, never gain).
|
||||
float pan_x =
|
||||
(info.chan == CHANNEL_LEFT) ? -0.5f :
|
||||
(info.chan == CHANNEL_RIGHT) ? 0.5f : 0.0f;
|
||||
alSource3f(sourceSet.sources[i],AL_POSITION,pan_x,0,0);
|
||||
alSource3f(sourceSet.sources[i],AL_VELOCITY,0,0,0);
|
||||
|
||||
if (info.loop == ForceStatic)
|
||||
@@ -173,7 +291,7 @@ void
|
||||
#endif*/
|
||||
}
|
||||
|
||||
void PatchLevelOfDetail::PlayNote(SourceSet sourceSet)
|
||||
void PatchLevelOfDetail::PlayNote(SourceSet sourceSet, int note)
|
||||
{
|
||||
if (sourceSet.count > 0 && sourceSet.sources != NULL)
|
||||
{
|
||||
@@ -182,12 +300,21 @@ void PatchLevelOfDetail::PlayNote(SourceSet sourceSet)
|
||||
|
||||
for (int i = 0; i < sourceSet.count; i++)
|
||||
{
|
||||
// (F1) play only the zones SetupPatch attached for this note
|
||||
ALint attached = 0;
|
||||
alGetSourcei(sourceSet.sources[i], AL_BUFFER, &attached);
|
||||
if (attached == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ALenum state;
|
||||
alGetSourcei(sourceSet.sources[i], AL_SOURCE_STATE, &state);
|
||||
|
||||
if (state != AL_PLAYING)
|
||||
{
|
||||
alSourcePlay(sourceSet.sources[i]);
|
||||
{ static int s_p=0; if (getenv("BT_AUDIO_LOG") && s_p++<30) DEBUG_STREAM << "[audio] PlayNote alSourcePlay src=" << sourceSet.sources[i] << " note=" << note << "\n" << std::flush; }
|
||||
}
|
||||
|
||||
ALenum error = alGetError();
|
||||
@@ -202,10 +329,27 @@ void PatchLevelOfDetail::PlayNote(SourceSet sourceSet)
|
||||
|
||||
void PatchLevelOfDetail::StopNote(SourceSet sourceSet)
|
||||
{
|
||||
if (getenv("BT_AUDIO_LOG")) { static int s_s=0; if (s_s++<400) { DEBUG_STREAM << "[audio] StopNote"; for (int _i=0;_i<sourceSet.count;_i++) DEBUG_STREAM << " src=" << sourceSet.sources[_i]; DEBUG_STREAM << "\n" << std::flush; } }
|
||||
if (sourceSet.count >0 && sourceSet.sources != NULL)
|
||||
{
|
||||
alSourceStopv(sourceSet.count, sourceSet.sources);
|
||||
alSourceRewindv(sourceSet.count, sourceSet.sources);
|
||||
// (task #50, AUDIO_FIDELITY F13) honor the authored releaseVolEnv:
|
||||
// a playing zone with a release time fades out dB-linearly instead of
|
||||
// cutting; everything else stops instantly (faithful for one-shots).
|
||||
for (int i = 0; i < sourceSet.count; i++)
|
||||
{
|
||||
SAMPLEINFO info = PRESET_getSampleInfo(bankID,patchID,i);
|
||||
ALenum state;
|
||||
alGetSourcei(sourceSet.sources[i], AL_SOURCE_STATE, &state);
|
||||
if (state == AL_PLAYING && info.releaseSec > 0.05f)
|
||||
{
|
||||
StartReleaseFade(sourceSet.sources[i], info.releaseSec);
|
||||
}
|
||||
else
|
||||
{
|
||||
alSourceStop(sourceSet.sources[i]);
|
||||
alSourceRewind(sourceSet.sources[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +403,7 @@ Logical
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
PatchResource::SetupPatch(SourceSet sourceSet)
|
||||
PatchResource::SetupPatch(SourceSet sourceSet, int note)
|
||||
{
|
||||
Check(this);
|
||||
// Check(channel);
|
||||
@@ -268,10 +412,10 @@ void
|
||||
Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail());
|
||||
|
||||
Check(patch_level_of_detail);
|
||||
patch_level_of_detail->SetupPatch(sourceSet);
|
||||
patch_level_of_detail->SetupPatch(sourceSet, note);
|
||||
}
|
||||
|
||||
void PatchResource::PlayNote(SourceSet sourceSet)
|
||||
void PatchResource::PlayNote(SourceSet sourceSet, int note)
|
||||
{
|
||||
Check(this);
|
||||
// Check(channel);
|
||||
@@ -280,7 +424,7 @@ void PatchResource::PlayNote(SourceSet sourceSet)
|
||||
Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail());
|
||||
|
||||
Check(patch_level_of_detail);
|
||||
patch_level_of_detail->PlayNote(sourceSet);
|
||||
patch_level_of_detail->PlayNote(sourceSet, note);
|
||||
}
|
||||
|
||||
void PatchResource::StopNote(SourceSet sourceSet)
|
||||
@@ -295,6 +439,24 @@ void PatchResource::StopNote(SourceSet sourceSet)
|
||||
patch_level_of_detail->StopNote(sourceSet);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
//#############################################################################
|
||||
//
|
||||
int PatchResource::GetBankID()
|
||||
{
|
||||
PatchLevelOfDetail *patch_level_of_detail =
|
||||
Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail());
|
||||
return (patch_level_of_detail != 0) ? patch_level_of_detail->GetBankID() : 0;
|
||||
}
|
||||
|
||||
int PatchResource::GetPatchID()
|
||||
{
|
||||
PatchLevelOfDetail *patch_level_of_detail =
|
||||
Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail());
|
||||
return (patch_level_of_detail != 0) ? patch_level_of_detail->GetPatchID() : 0;
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
//#############################################################################
|
||||
|
||||
@@ -26,22 +26,38 @@ struct SAMPLEINFO
|
||||
const char *file;
|
||||
SampleChannel chan;
|
||||
SampleLoop loop;
|
||||
// (task #50, AUDIO_FIDELITY F1/F13) full-zone metadata from the SF2 banks:
|
||||
// the authored MIDI note SELECTS zones by [keyLo,keyHi]; loop regions are
|
||||
// sub-ranges in sample frames (AL_SOFT_loop_points); releaseSec is the
|
||||
// authored releaseVolEnv fade applied on Stop instead of an instant cut.
|
||||
int keyLo;
|
||||
int keyHi;
|
||||
int loopStart;
|
||||
int loopEnd;
|
||||
float releaseSec;
|
||||
};
|
||||
|
||||
// AllExplosion (bank2 p125) authors 25 layered zones -- the largest preset.
|
||||
const int MAX_PRESET_SAMPLES = 25;
|
||||
|
||||
struct PRESETINFO
|
||||
{
|
||||
int sampleNum;
|
||||
SAMPLEINFO samples[5];
|
||||
SAMPLEINFO samples[MAX_PRESET_SAMPLES];
|
||||
bool is3d;
|
||||
};
|
||||
|
||||
extern PRESETINFO allPresets[2][100];
|
||||
extern PRESETINFO allPresets[2][128];
|
||||
|
||||
bool PRESET_isImplemented(int bank, int preset);
|
||||
int PRESET_getNumSamples(int bank, int preset);
|
||||
SAMPLEINFO PRESET_getSampleInfo(int bank, int preset, int sampleInd);
|
||||
void PRESET_setBufferIndex(int bank, int preset, int sampleInd, int index);
|
||||
|
||||
// (task #50, AUDIO_FIDELITY F13) authored release fades: StopNote registers a
|
||||
// dB-linear gain ramp instead of cutting; AudioHead::Execute services them.
|
||||
void PRESET_serviceReleaseFades(float elapsed_seconds);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PatchLevelOfDetail ~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
typedef MIDIValue SBKPatchID;
|
||||
@@ -59,7 +75,7 @@ public:
|
||||
PatchLevelOfDetail(PlugStream *stream);
|
||||
~PatchLevelOfDetail();
|
||||
|
||||
void PlayNote(SourceSet sourceSet);
|
||||
void PlayNote(SourceSet sourceSet, int note);
|
||||
void StopNote(SourceSet sourceSet);
|
||||
|
||||
Logical
|
||||
@@ -69,6 +85,11 @@ public:
|
||||
GetVoiceCount()
|
||||
{return PRESET_getNumSamples(bankID,patchID);}
|
||||
|
||||
// (task #50) expose the authored bank/patch so the game-side footstep
|
||||
// intensity send can identify footstep sources precisely.
|
||||
int GetBankID() const { return (int)bankID; }
|
||||
int GetPatchID() const { return (int)patchID; }
|
||||
|
||||
//
|
||||
//-----------------------------------------------------------------------
|
||||
// BuildFromPage
|
||||
@@ -88,7 +109,7 @@ public:
|
||||
//-----------------------------------------------------------------------
|
||||
//
|
||||
void
|
||||
SetupPatch(SourceSet sourceSet);
|
||||
SetupPatch(SourceSet sourceSet, int note);
|
||||
|
||||
MIDINRPNValue
|
||||
GetMaxMIDIFilterCutoff()
|
||||
@@ -136,7 +157,7 @@ public:
|
||||
PatchResource(PlugStream *stream);
|
||||
~PatchResource();
|
||||
|
||||
void PlayNote(SourceSet sourceSet);
|
||||
void PlayNote(SourceSet sourceSet, int note);
|
||||
void StopNote(SourceSet sourceSet);
|
||||
|
||||
Logical
|
||||
@@ -161,9 +182,14 @@ public:
|
||||
//-----------------------------------------------------------------------
|
||||
//
|
||||
void
|
||||
SetupPatch(SourceSet sourceSet);
|
||||
SetupPatch(SourceSet sourceSet, int note);
|
||||
|
||||
MIDINRPNValue
|
||||
GetMaxMIDIFilterCutoff();
|
||||
|
||||
// (task #50) authored bank/patch pass-throughs (the LOD accessor is
|
||||
// protected on AudioResource) -- zone metadata lookups at the source layer.
|
||||
int GetBankID();
|
||||
int GetPatchID();
|
||||
};
|
||||
|
||||
|
||||
+132
-64
@@ -1,3 +1,7 @@
|
||||
#include <stdio.h>
|
||||
#include <direct.h>
|
||||
#include <string.h>
|
||||
#include <cstdlib>
|
||||
#include "mungal4.h"
|
||||
#pragma hdrstop
|
||||
|
||||
@@ -13,10 +17,66 @@
|
||||
#include "..\munga\app.h"
|
||||
#include "..\munga\notation.h"
|
||||
#include "openal\al.h"
|
||||
#include "sndfile.h"
|
||||
#include "openal\alc.h"
|
||||
|
||||
ALuint *g_buffers;
|
||||
int g_numBuffers;
|
||||
const char *g_bufferNames[512]; // bufferInd -> sample file (diag: the live-playing dump)
|
||||
int g_curAudioWatcherClass = 0;
|
||||
|
||||
//
|
||||
// Minimal canonical-PCM WAV reader. The repo's libsndfile-1.dll is a STUB
|
||||
// (exports 15 funcs by ordinal only, no names -- sf_open always returns NULL
|
||||
// and sf_strerror(NULL) faults), so the original sf_open path loaded nothing.
|
||||
// Our soundbank samples (AUDIO1/2.RES, cracked by scratchpad/sf2extract.py)
|
||||
// are written as plain 16-bit mono PCM WAVs, so a tiny RIFF/WAVE fmt+data
|
||||
// parser loads the real sample data directly into the AL buffer -- no
|
||||
// external dependency, no stub. Handles 8/16-bit mono/stereo PCM.
|
||||
//
|
||||
static bool LoadWavPCM(const char *path, char **outData, int *outBytes,
|
||||
int *outChannels, int *outBits, int *outRate)
|
||||
{
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (!fp) return false;
|
||||
fseek(fp, 0, SEEK_END); long flen = ftell(fp); fseek(fp, 0, SEEK_SET);
|
||||
if (flen < 44) { fclose(fp); return false; }
|
||||
unsigned char *buf = new unsigned char[flen];
|
||||
size_t got = fread(buf, 1, flen, fp);
|
||||
fclose(fp);
|
||||
if ((long)got != flen) { delete [] buf; return false; }
|
||||
if (memcmp(buf, "RIFF", 4) != 0 || memcmp(buf + 8, "WAVE", 4) != 0) { delete [] buf; return false; }
|
||||
int channels = 0, bits = 0, rate = 0;
|
||||
char *pcm = 0; int pcmBytes = 0;
|
||||
bool haveFmt = false;
|
||||
long p = 12;
|
||||
while (p + 8 <= flen)
|
||||
{
|
||||
const unsigned char *id = buf + p;
|
||||
unsigned int csz = buf[p+4] | (buf[p+5]<<8) | (buf[p+6]<<16) | ((unsigned)buf[p+7]<<24);
|
||||
long body = p + 8;
|
||||
if (memcmp(id, "fmt ", 4) == 0 && csz >= 16)
|
||||
{
|
||||
channels = buf[body+2] | (buf[body+3]<<8);
|
||||
rate = buf[body+4] | (buf[body+5]<<8) | (buf[body+6]<<16) | ((unsigned)buf[body+7]<<24);
|
||||
bits = buf[body+14] | (buf[body+15]<<8);
|
||||
haveFmt = true;
|
||||
}
|
||||
else if (memcmp(id, "data", 4) == 0)
|
||||
{
|
||||
long avail = flen - body;
|
||||
pcmBytes = ((long)csz <= avail) ? (int)csz : (int)avail;
|
||||
if (pcmBytes < 0) pcmBytes = 0;
|
||||
pcm = new char[pcmBytes > 0 ? pcmBytes : 1];
|
||||
memcpy(pcm, buf + body, pcmBytes);
|
||||
}
|
||||
p = body + csz + (csz & 1); // RIFF chunks are word-aligned
|
||||
}
|
||||
delete [] buf;
|
||||
if (!haveFmt || pcm == 0) { delete [] pcm; return false; }
|
||||
*outData = pcm; *outBytes = pcmBytes;
|
||||
*outChannels = channels; *outBits = bits; *outRate = rate;
|
||||
return true;
|
||||
}
|
||||
|
||||
//#############################################################################
|
||||
//####################### AudioObjectStream #############################
|
||||
@@ -82,6 +142,11 @@ RegisteredClass*
|
||||
Check(this);
|
||||
Verify(class_ID != RegisteredClass::NullClassID);
|
||||
|
||||
// DIAG (BT_ATTRBIND_LOG): record which audio-object ClassID is being built so the
|
||||
// AttributeWatcher trace can print the watcher TYPE alongside the bound attribute.
|
||||
extern int g_curAudioWatcherClass;
|
||||
g_curAudioWatcherClass = (int)class_ID;
|
||||
|
||||
//
|
||||
//-----------------------------------------------------------------------
|
||||
// HACK - Constructors should be referenced through registration, for
|
||||
@@ -542,7 +607,7 @@ void
|
||||
|
||||
for(int i=1; i <= 2; i++)
|
||||
{
|
||||
for (int j=0; j < 100; j++)
|
||||
for (int j=0; j < 128; j++)
|
||||
{
|
||||
if (PRESET_isImplemented(i,j))
|
||||
{
|
||||
@@ -558,6 +623,7 @@ void
|
||||
alGetError();
|
||||
g_buffers = new ALuint[g_numBuffers];
|
||||
alGenBuffers(g_numBuffers,g_buffers);
|
||||
if (getenv("BT_AUDIO_LOG")) DEBUG_STREAM << "[audio] alGenBuffers count=" << g_numBuffers << "\n" << std::flush;
|
||||
ALenum error;
|
||||
if ((error = alGetError()) != AL_NO_ERROR)
|
||||
{
|
||||
@@ -572,7 +638,7 @@ void
|
||||
//Load buffers
|
||||
for(int i=1; i<=2 && bufferInd < g_numBuffers; i++)
|
||||
{
|
||||
for(int j=0; j<100 && bufferInd < g_numBuffers; j++)
|
||||
for(int j=0; j<128 && bufferInd < g_numBuffers; j++)
|
||||
{
|
||||
if (PRESET_isImplemented(i,j))
|
||||
{
|
||||
@@ -580,84 +646,86 @@ void
|
||||
{
|
||||
SAMPLEINFO info = PRESET_getSampleInfo(i,j,k);
|
||||
|
||||
//Load WAV to buffer
|
||||
char *data;
|
||||
int size;
|
||||
|
||||
//Open WAV
|
||||
char fullname[50];
|
||||
strcpy_s(fullname,50,"AUDIO\\");
|
||||
strcpy_s(fullname,50,"AUDIO/");
|
||||
strcat_s(fullname,50,info.file);
|
||||
SF_INFO *sfInfo = new SF_INFO[2];
|
||||
//SF_INFO is working different than expected!
|
||||
sfInfo->format = 0;
|
||||
SNDFILE *file = sf_open(fullname,SFM_READ,sfInfo);
|
||||
|
||||
if (file == NULL)
|
||||
char *data = 0; int size = 0;
|
||||
int wavCh = 0, wavBits = 0, wavRate = 0;
|
||||
if (!LoadWavPCM(fullname, &data, &size, &wavCh, &wavBits, &wavRate))
|
||||
{
|
||||
DEBUG_STREAM << "Failed to open." << std::endl;
|
||||
if (getenv("BT_AUDIO_LOG") && bufferInd == 0) {
|
||||
char cwd[512]; cwd[0]=0; _getcwd(cwd, sizeof(cwd));
|
||||
DEBUG_STREAM << "[audio] DIAG cwd=[" << cwd << "] path=[" << fullname << "] WAV load FAILED\n" << std::flush;
|
||||
}
|
||||
DEBUG_STREAM << "[audio] Failed to open AUDIO/" << info.file << " -- skipping\n" << std::flush;
|
||||
PRESET_setBufferIndex(i,j,k,bufferInd);
|
||||
bufferInd++;
|
||||
continue;
|
||||
}
|
||||
|
||||
unsigned long formatBits = 0;
|
||||
unsigned long sampleRate = sfInfo->samplerate;
|
||||
size = sfInfo->frames;
|
||||
|
||||
bool isMono = (sfInfo->channels == 1);
|
||||
|
||||
if (sfInfo->format & SF_FORMAT_PCM_S8)
|
||||
{
|
||||
formatBits = 8;
|
||||
} else if (sfInfo->format & SF_FORMAT_PCM_16)
|
||||
{
|
||||
formatBits = 16;
|
||||
} else
|
||||
{
|
||||
DEBUG_STREAM << "BAD FORMAT" << std::endl;
|
||||
}
|
||||
|
||||
ALenum format;
|
||||
if (formatBits == 8)
|
||||
if (wavBits == 8)
|
||||
format = (wavCh == 1) ? AL_FORMAT_MONO8 : AL_FORMAT_STEREO8;
|
||||
else
|
||||
format = (wavCh == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
|
||||
|
||||
//Feed the buffer with the real PCM sample data
|
||||
alBufferData(g_buffers[bufferInd], format, data, size, wavRate);
|
||||
|
||||
// (task #50, AUDIO_FIDELITY F13) honor the authored loop
|
||||
// REGION -- the EMU8000 looped [dwStartLoop,dwEndLoop], not
|
||||
// the whole sample (MechExplosion's boom carries a ~16 ms
|
||||
// sustain slice that must not replay the full boom).
|
||||
#ifndef AL_LOOP_POINTS_SOFT
|
||||
#define AL_LOOP_POINTS_SOFT 0x2015
|
||||
#endif
|
||||
if (info.loop != ForceStatic && info.loopEnd > info.loopStart)
|
||||
{
|
||||
if (isMono)
|
||||
{
|
||||
format = AL_FORMAT_MONO8;
|
||||
} else
|
||||
{
|
||||
size *= 2;
|
||||
format = AL_FORMAT_STEREO8;
|
||||
}
|
||||
} else if (formatBits == 16)
|
||||
{
|
||||
size *= 2;
|
||||
if (isMono)
|
||||
{
|
||||
format = AL_FORMAT_MONO16;
|
||||
} else
|
||||
{
|
||||
size *= 2;
|
||||
format = AL_FORMAT_STEREO16;
|
||||
}
|
||||
ALint loop_points[2] = { info.loopStart, info.loopEnd };
|
||||
alGetError();
|
||||
alBufferiv(g_buffers[bufferInd], AL_LOOP_POINTS_SOFT, loop_points);
|
||||
ALenum lpErr = alGetError();
|
||||
if (lpErr != AL_NO_ERROR && getenv("BT_AUDIO_LOG"))
|
||||
DEBUG_STREAM << "[audio] loop-points REJECTED buf" << bufferInd
|
||||
<< " " << info.file << " [" << info.loopStart << ","
|
||||
<< info.loopEnd << "] err=" << lpErr << "\n" << std::flush;
|
||||
}
|
||||
|
||||
ALsizei alSampleRate = sampleRate;
|
||||
|
||||
//Load size & data
|
||||
delete [] sfInfo;
|
||||
data = new char[size];
|
||||
sf_read_raw(file,data,size);
|
||||
sf_close(file);
|
||||
|
||||
//Feed the buffer
|
||||
alBufferData(g_buffers[bufferInd],format,data,size,alSampleRate);
|
||||
if (bufferInd < 512) g_bufferNames[bufferInd] = info.file;
|
||||
if (getenv("BT_AUDIO_LOG") && bufferInd < 3)
|
||||
DEBUG_STREAM << "[audio] loaded buf" << bufferInd << " " << info.file
|
||||
<< " ch=" << wavCh << " bits=" << wavBits << " rate=" << wavRate
|
||||
<< " bytes=" << size << " alErr=" << alGetError() << "\n" << std::flush;
|
||||
PRESET_setBufferIndex(i,j,k,bufferInd);
|
||||
bufferInd++;
|
||||
|
||||
delete [] data;
|
||||
delete [] data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (getenv("BT_AUDIO_TEST") && g_numBuffers > 0)
|
||||
{
|
||||
// PROOF OF LIFE: play the first loaded sample so we can confirm the
|
||||
// SF2 -> WAV -> OpenAL buffer -> speakers path works end to end.
|
||||
ALCcontext *ctx = alcGetCurrentContext();
|
||||
alGetError();
|
||||
ALuint testsrc = 0; alGenSources(1, &testsrc);
|
||||
ALenum genErr = alGetError();
|
||||
alSourcei(testsrc, AL_BUFFER, g_buffers[0]);
|
||||
ALenum bufErr = alGetError();
|
||||
alSourcei(testsrc, AL_LOOPING, AL_TRUE);
|
||||
alSourcef(testsrc, AL_GAIN, 1.0f);
|
||||
alSourcePlay(testsrc);
|
||||
ALenum playErr = alGetError();
|
||||
ALint st = 0; alGetSourcei(testsrc, AL_SOURCE_STATE, &st);
|
||||
DEBUG_STREAM << "[audio] TEST-PLAY buffer0 src=" << testsrc
|
||||
<< " ctx=" << (void*)ctx
|
||||
<< " genErr=" << genErr << " bufErr=" << bufErr << " playErr=" << playErr
|
||||
<< " state=" << st << " playing=" << (int)(st==AL_PLAYING) << "\n" << std::flush;
|
||||
}
|
||||
|
||||
//
|
||||
//----------------------------------------------------------------------
|
||||
// Load the sound banks
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <cstdlib>
|
||||
#include "mungal4.h"
|
||||
#pragma hdrstop
|
||||
|
||||
@@ -98,6 +99,7 @@ void
|
||||
L4AudioRenderer::Initialize()
|
||||
{
|
||||
Check(this);
|
||||
if (getenv("BT_AUDIO_LOG")) DEBUG_STREAM << "[audio] L4AudioRenderer::Initialize ENTERED\n" << std::flush;
|
||||
|
||||
//
|
||||
//----------------------------------------------------------------------
|
||||
@@ -133,6 +135,7 @@ void
|
||||
|
||||
NotationFile notation_file(audio_file_name);
|
||||
Check(¬ation_file);
|
||||
if (getenv("BT_AUDIO_LOG")) DEBUG_STREAM << "[audio] notation file opened; about to read scalars\n" << std::flush;
|
||||
|
||||
//
|
||||
//----------------------------------------------------------------------
|
||||
@@ -379,6 +382,23 @@ void
|
||||
{
|
||||
ALCcontext *context = alcCreateContext(device,NULL);
|
||||
alcMakeContextCurrent(context);
|
||||
// Master volume: AL_GAIN on the listener scales EVERY source. Default 0.6
|
||||
// (the raw samples are hot); override with BT_AUDIO_VOLUME=<0.0..1.0+>.
|
||||
float masterVol = 0.6f;
|
||||
if (const char *v = getenv("BT_AUDIO_VOLUME")) { float f = (float)atof(v); if (f >= 0.0f) masterVol = f; }
|
||||
alListenerf(AL_GAIN, masterVol);
|
||||
if (getenv("BT_AUDIO_LOG")) DEBUG_STREAM << "[audio] OpenAL device OPENED + context current; master gain=" << masterVol << "\n" << std::flush;
|
||||
// (task #50, AUDIO_FIDELITY F9/F11) EFX bridge: the authored lowpass
|
||||
// chains + the wet-exterior/dry-cockpit reverb split, at the authentic
|
||||
// AUDIO.INI global_reverb_scale read above.
|
||||
{
|
||||
extern bool EFX_Initialize(float global_reverb_scale);
|
||||
EFX_Initialize(global_reverb_scale);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (getenv("BT_AUDIO_LOG")) DEBUG_STREAM << "[audio] OpenAL device FAILED to open (alcOpenDevice NULL)\n" << std::flush;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -970,8 +970,17 @@ void L4NetworkManager::HostDisconnectedMessageHandler(HostDisconnectedMessage* H
|
||||
//
|
||||
// Post a listen for a console so it can reconnect if it wants to.
|
||||
//
|
||||
NetTransport_Get()->Close(gameListenerSocket);
|
||||
gameListenerSocket = NULL;
|
||||
// FIX (task #50, 2026-07-15): this used to close the GAME listener here
|
||||
// (shutdown/closesocket(gameListenerSocket)) on a CONSOLE disconnect -- a
|
||||
// naming bug: the comment above + the commented-out OpenConnection below
|
||||
// intended to re-post a CONSOLE listen, which CreateConsoleHost() (below)
|
||||
// already does. Closing the game listener on console loss needlessly
|
||||
// bounced the game-accept path (harmless to already-established peer
|
||||
// sockets -- which is why a live 2-node match keeps replicating through a
|
||||
// console loss -- but wrong, and it would have blocked a NEW peer from
|
||||
// joining after a console cycle). Removed; CreateConsoleHost re-listens.
|
||||
// (BT412: the NetTransport seam close that briefly lived here is dropped
|
||||
// with the bug it wrapped.)
|
||||
//unsigned long console_socket = OpenConnection(
|
||||
// NETNUB_TCP_LISTEN,
|
||||
// ((L4Application *)application)->GetNetworkCommonFlatAddress(), // Local port
|
||||
@@ -2138,6 +2147,12 @@ Logical L4NetworkManager::CheckBuffers(NetworkPacket *network_packet)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// TCP_NODELAY on the accepted GAME socket (not the console):
|
||||
// without it Nagle coalesces the inbound per-frame update
|
||||
// records into bursts -> the peer is received in lurches.
|
||||
BOOL gameNoDelay = TRUE;
|
||||
if (setsockopt(tempSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&gameNoDelay, sizeof(gameNoDelay)))
|
||||
DEBUG_STREAM << "WARN: could not set TCP_NODELAY on accepted game socket; setsockopt() failed with " << WSAGetLastError() << "\n" << std::flush;
|
||||
}
|
||||
|
||||
remote_host->SetNetworkSocket(tempSocket);
|
||||
@@ -2683,6 +2698,9 @@ SOCKET L4NetworkManager::OpenConnection(
|
||||
remoteEndpoint.sin_addr.S_un.S_addr = internet_address;
|
||||
remoteEndpoint.sin_port = htons(remote_port);
|
||||
|
||||
// BT412: the raw connect/retry/nonblocking/TCP_NODELAY sequence that
|
||||
// lived here now lives in WinsockNetTransport::Connect (the seam) --
|
||||
// including BT411's task-#50 TCP_NODELAY latency fix, folded in there.
|
||||
return (SOCKET) NetTransport_Get()->Connect(&remoteEndpoint, local_port);
|
||||
}
|
||||
else if(connection_type == NETNUB_TCP_LISTEN)
|
||||
|
||||
@@ -164,6 +164,18 @@ namespace
|
||||
closesocket(sock);
|
||||
return InvalidConnection;
|
||||
}
|
||||
// TCP_NODELAY (BT411 task #50, folded into the seam): disable
|
||||
// Nagle so the small per-frame update records ship immediately
|
||||
// instead of being coalesced. Nagle + delayed-ACK batch the
|
||||
// tiny position packets into ~40-200ms bursts, so a peer moving
|
||||
// at a steady speed is RECEIVED in lurches (dead-reckoned ground
|
||||
// speed swinging 3x per 0.25s window). The pod net carries
|
||||
// steady real-time state where latency, not throughput, matters.
|
||||
BOOL no_delay = TRUE;
|
||||
if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
|
||||
(char *) &no_delay, sizeof(no_delay)))
|
||||
DEBUG_STREAM << "WARN: could not set TCP_NODELAY on active socket; setsockopt() failed with "
|
||||
<< WSAGetLastError() << "\n" << std::flush;
|
||||
return (Connection) sock;
|
||||
}
|
||||
|
||||
|
||||
@@ -8577,6 +8577,37 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
|
||||
const double presentMs = (double)(_rt2.QuadPart - _rt1.QuadPart) * 1000.0 / (double)_rf.QuadPart;
|
||||
static double sAcc = 0.0, sMaxD = 0.0, sMaxP = 0.0; static int sFrames = 0;
|
||||
sAcc += drawMs + presentMs; ++sFrames;
|
||||
// RENDER-frame heading probe (BT_RENDHDG): count render frames that redraw
|
||||
// the SAME peer heading (stale between sim updates) vs frames where it moved.
|
||||
// sameHeading >> moved => the peer sim updates slower than the render draws,
|
||||
// so the rotation stutters regardless of how smooth each sim step is.
|
||||
if (getenv("BT_RENDHDG"))
|
||||
{
|
||||
extern volatile float gBTReplRenderYaw;
|
||||
static float sPrevY = -999.0f, sMaxStep = 0.0f, sSumStep = 0.0f;
|
||||
static int sN = 0, sBack = 0; static double sHAcc = 0.0;
|
||||
const float y = gBTReplRenderYaw;
|
||||
if (sPrevY > -900.0f)
|
||||
{
|
||||
float d = y - sPrevY; // per-RENDER-frame heading change
|
||||
if (d > 3.14159f) d -= 6.28319f; // unwrap
|
||||
if (d < -3.14159f) d += 6.28319f;
|
||||
const float ad = (d < 0.0f) ? -d : d;
|
||||
sSumStep += ad; if (ad > sMaxStep) sMaxStep = ad;
|
||||
if (d > 0.0f) ++sBack; // spin is -yaw here; +step == backward
|
||||
++sN;
|
||||
}
|
||||
sPrevY = y; sHAcc += drawMs + presentMs;
|
||||
if (sHAcc >= 1000.0 && sN > 0)
|
||||
{
|
||||
const float avg = sSumStep / sN;
|
||||
DEBUG_STREAM << "[rendhdg] avgStep=" << avg << " maxStep=" << sMaxStep
|
||||
<< " max/avg=" << (avg > 1e-6f ? sMaxStep / avg : 0.0f)
|
||||
<< " backwardFrames=" << sBack << "/" << sN
|
||||
<< " (smooth: max/avg~1, backward~0)\n" << std::flush;
|
||||
sMaxStep = 0.0f; sSumStep = 0.0f; sN = 0; sBack = 0; sHAcc = 0.0;
|
||||
}
|
||||
}
|
||||
if (drawMs > sMaxD) sMaxD = drawMs;
|
||||
if (presentMs > sMaxP) sMaxP = presentMs;
|
||||
if (drawMs + presentMs > 150.0)
|
||||
|
||||
@@ -4,7 +4,7 @@ bool PRESET_isImplemented(int bank, int presetn)
|
||||
{
|
||||
PRESETINFO preset = allPresets[bank-1][presetn];
|
||||
|
||||
if (preset.sampleNum <= 0 || preset.sampleNum >= 5)
|
||||
if (preset.sampleNum <= 0 || preset.sampleNum > MAX_PRESET_SAMPLES)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -27,15 +27,20 @@ int PRESET_getNumSamples(int bank, int preset)
|
||||
|
||||
SAMPLEINFO PRESET_getSampleInfo(int bank, int preset, int sampleInd)
|
||||
{
|
||||
SAMPLEINFO default;
|
||||
default.chan = SampleChannel::CHANNEL_CENTER;
|
||||
default.file = "";
|
||||
default.implemented = false;
|
||||
default.loop = SampleLoop::LoopAtWill;
|
||||
|
||||
if (sampleInd < 0 || sampleInd >= allPresets[bank-1][preset].sampleNum)
|
||||
{
|
||||
return default;
|
||||
SAMPLEINFO none;
|
||||
none.bufferIndex = -1;
|
||||
none.implemented = false;
|
||||
none.file = "";
|
||||
none.chan = SampleChannel::CHANNEL_CENTER;
|
||||
none.loop = SampleLoop::LoopAtWill;
|
||||
none.keyLo = 0;
|
||||
none.keyHi = 127;
|
||||
none.loopStart = 0;
|
||||
none.loopEnd = 0;
|
||||
none.releaseSec = 0.0f;
|
||||
return none;
|
||||
}
|
||||
|
||||
return allPresets[bank-1][preset].samples[sampleInd];
|
||||
@@ -44,4 +49,4 @@ SAMPLEINFO PRESET_getSampleInfo(int bank, int preset, int sampleInd)
|
||||
void PRESET_setBufferIndex(int bank, int preset, int sampleInd, int index)
|
||||
{
|
||||
allPresets[bank-1][preset].samples[sampleInd].bufferIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user