mirror of
https://github.com/got-feedBack/feedBack-desktop.git
synced 2026-09-10 22:14:10 +00:00
feat(audio): getLatencyBreakdown — one owner for every latency term
Deep-read §5: latency had three unreconciled truths — getLatencyMs' static half-capacity ring guess (42.7 ms), the verifier's input-delta-only offset, and the renderer bus adding prime+fill+resample that no figure surfaced. New engine API + export: per-term breakdown (deviceBufferMs, input/output driver latency, MEASURED split-ring residency, monitor total) plus the renderer-bus song-audio delay (measured bus fill) as its own term. On the user's split exclusive setup the measured ring sits at ~10 ms — the legacy figure overstated monitor latency by ~33 ms (102.7 reported vs ~70 real). getLatencyMs is unchanged for compatibility; UI adoption of the breakdown is renderer follow-up. Snapshots regenerated (104 exports). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
f0340ed425
commit
9d0963d6d5
@@ -228,6 +228,54 @@ double AudioEngine::getLatencyMs() const
|
||||
return (totalSamples / sr) * 1000.0;
|
||||
}
|
||||
|
||||
AudioEngine::LatencyBreakdown AudioEngine::getLatencyBreakdown() const
|
||||
{
|
||||
LatencyBreakdown b;
|
||||
const double sr = currentSampleRate.load(std::memory_order_relaxed);
|
||||
b.sampleRate = sr;
|
||||
b.duplex = duplexMode.load(std::memory_order_relaxed);
|
||||
if (sr <= 0.0) return b;
|
||||
const auto ms = [sr](double samples) { return (samples / sr) * 1000.0; };
|
||||
|
||||
if (b.duplex)
|
||||
{
|
||||
if (auto* device = inputDeviceManager.getCurrentAudioDevice())
|
||||
{
|
||||
b.deviceBufferMs = ms(device->getCurrentBufferSizeSamples());
|
||||
b.inputLatencyMs = ms(device->getInputLatencyInSamples());
|
||||
b.outputLatencyMs = ms(device->getOutputLatencyInSamples());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto* in = inputDeviceManager.getCurrentAudioDevice())
|
||||
{
|
||||
b.deviceBufferMs += ms(in->getCurrentBufferSizeSamples());
|
||||
b.inputLatencyMs = ms(in->getInputLatencyInSamples());
|
||||
}
|
||||
if (auto* out = outputDeviceManager.getCurrentAudioDevice())
|
||||
{
|
||||
b.deviceBufferMs += ms(out->getCurrentBufferSizeSamples());
|
||||
b.outputLatencyMs = ms(out->getOutputLatencyInSamples());
|
||||
}
|
||||
// MEASURED ring residency — getLatencyMs() still reports the static
|
||||
// half-capacity estimate for compatibility; this is the live figure
|
||||
// getDeviceMetrics already exposes, converted to time.
|
||||
const uint64_t w = outputRing.writeIndex.load(std::memory_order_acquire);
|
||||
const uint64_t r = outputRing.readIndex.load(std::memory_order_acquire);
|
||||
const uint64_t fill = (w >= r) ? (w - r) : 0;
|
||||
b.splitRingMs = ms((double) std::min<uint64_t>(fill, (uint64_t) kOutputRingFrames));
|
||||
}
|
||||
b.monitorTotalMs = b.deviceBufferMs + b.inputLatencyMs + b.outputLatencyMs + b.splitRingMs;
|
||||
|
||||
// Song audio over the renderer bus is delayed by the measured bus fill —
|
||||
// a term no previous latency figure surfaced (deep-read 5).
|
||||
const auto busMetrics = rendererBus.metrics();
|
||||
if (busMetrics.enabled)
|
||||
b.rendererBusMs = ms((double) busMetrics.fillFrames);
|
||||
return b;
|
||||
}
|
||||
|
||||
// ── Device Selection ──────────────────────────────────────────────────────────
|
||||
|
||||
bool AudioEngine::setDeviceType(const juce::String& typeName)
|
||||
|
||||
@@ -280,6 +280,25 @@ public:
|
||||
// Latency
|
||||
double getLatencyMs() const;
|
||||
|
||||
// One owner for every latency term (TLC deep-read §5) — the previous
|
||||
// three unreconciled truths were getLatencyMs' static half-capacity ring
|
||||
// guess, the verifier's input-latency-delta-only offset, and the renderer
|
||||
// bus adding prime+fill+resample that no figure surfaced. All ms.
|
||||
struct LatencyBreakdown
|
||||
{
|
||||
double sampleRate = 0.0;
|
||||
bool duplex = true;
|
||||
double deviceBufferMs = 0.0; // input buffer (+ output buffer when split)
|
||||
double inputLatencyMs = 0.0; // driver-reported capture latency
|
||||
double outputLatencyMs = 0.0; // driver-reported playback latency
|
||||
double splitRingMs = 0.0; // MEASURED primary-ring residency (0 in duplex)
|
||||
double monitorTotalMs = 0.0; // guitar in→out: buffers + in/out + splitRing
|
||||
// Renderer-bus song-audio delay: measured bus fill (includes the
|
||||
// ~10.7 ms prime cushion once flowing). 0 when the bus is off.
|
||||
double rendererBusMs = 0.0;
|
||||
};
|
||||
LatencyBreakdown getLatencyBreakdown() const;
|
||||
|
||||
// Raw input frame snapshot for renderer-side polyphonic chord scoring in
|
||||
// notedetect. Backed by sources[0]'s pre-gate input ring; the rings (and the
|
||||
// power-of-two capacity constants) now live on SourceChain. Default snapshot
|
||||
|
||||
@@ -109,6 +109,7 @@ using slopsmith::addon::SetMonitorMuteSuppressed;
|
||||
using slopsmith::addon::AcquireMonitorMuteHold;
|
||||
using slopsmith::addon::ReleaseMonitorMuteHold;
|
||||
using slopsmith::addon::GetMonitorMuteState;
|
||||
using slopsmith::addon::GetLatencyBreakdown;
|
||||
using slopsmith::addon::SetMultiBypass;
|
||||
using slopsmith::addon::SetNoiseGate;
|
||||
using slopsmith::addon::SetNoteDetectionEnabled;
|
||||
@@ -352,6 +353,7 @@ static Napi::Object InitModule(Napi::Env env, Napi::Object exports)
|
||||
exports.Set("acquireMonitorMuteHold", Napi::Function::New(env, AcquireMonitorMuteHold));
|
||||
exports.Set("releaseMonitorMuteHold", Napi::Function::New(env, ReleaseMonitorMuteHold));
|
||||
exports.Set("getMonitorMuteState", Napi::Function::New(env, GetMonitorMuteState));
|
||||
exports.Set("getLatencyBreakdown", Napi::Function::New(env, GetLatencyBreakdown));
|
||||
exports.Set("isMonitorMuted", Napi::Function::New(env, IsMonitorMuted));
|
||||
exports.Set("setMonitorKill", Napi::Function::New(env, SetMonitorKill));
|
||||
exports.Set("setNoiseGate", Napi::Function::New(env, SetNoiseGate));
|
||||
|
||||
@@ -11,6 +11,8 @@ class SourceChain;
|
||||
|
||||
namespace slopsmith::addon {
|
||||
|
||||
Napi::Value GetLatencyBreakdown(const Napi::CallbackInfo& info);
|
||||
|
||||
// Validate a JS source-id argument and return the live source (nullptr for
|
||||
// missing / non-Number / non-finite / out-of-range). Shared by the
|
||||
// source-indexed bindings across the split files.
|
||||
|
||||
@@ -831,4 +831,22 @@ Napi::Value GetSampleRate(const Napi::CallbackInfo& info)
|
||||
}
|
||||
|
||||
|
||||
Napi::Value GetLatencyBreakdown(const Napi::CallbackInfo& info)
|
||||
{
|
||||
auto env = info.Env();
|
||||
auto obj = Napi::Object::New(env);
|
||||
auto liveEngine = snapshotEngine();
|
||||
if (!liveEngine) return obj;
|
||||
const auto b = liveEngine->getLatencyBreakdown();
|
||||
obj.Set("sampleRate", b.sampleRate);
|
||||
obj.Set("duplex", b.duplex);
|
||||
obj.Set("deviceBufferMs", b.deviceBufferMs);
|
||||
obj.Set("inputLatencyMs", b.inputLatencyMs);
|
||||
obj.Set("outputLatencyMs", b.outputLatencyMs);
|
||||
obj.Set("splitRingMs", b.splitRingMs);
|
||||
obj.Set("monitorTotalMs", b.monitorTotalMs);
|
||||
obj.Set("rendererBusMs", b.rendererBusMs);
|
||||
return obj;
|
||||
}
|
||||
|
||||
} // namespace slopsmith::addon
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"getDeviceMetrics",
|
||||
"getDeviceTypes",
|
||||
"getKnownPlugins",
|
||||
"getLatencyBreakdown",
|
||||
"getLevels",
|
||||
"getMonitorMuteState",
|
||||
"getNoteVerdicts",
|
||||
|
||||
Reference in New Issue
Block a user