v5 pre-release: multi-track drift fix, more self-tests, logging, CLI, version bump
Multi-track recording drift fix (Ed's question — can the separate tracks drift over an hour?): * Root: FlushPeerTracks skipped a peer that produced no samples in a render block, so a peer that went quiet long enough for its session to be pruned (>4 s idle) would have its track fall behind and desync. Now every peer track is padded to a full render block each cycle (silence when the peer produced nothing), so all peer tracks stay sample-locked to the single render clock — they can't drift apart however long the recording runs, and all end the same length. Same padding for the single-file bypass path. OnRecordBlockComplete now carries the block's float count. (The peer tracks are already resampled to the render clock per peer, so this makes peer-to-peer sync exact; your own "me" track is capture-clocked — same soundcard for capture+playback = same clock = no drift, different interfaces can drift slightly.) * Self-test: two new steps — "Per-peer shaping DSP" (PeerDspChain unity/master-off/volume/parametric + ParametricToPeaking) and "v5 settings and shaping round-trip" (AppConfig defaults, NamedPeers, MainTabOrder, parametric PeerShaping, recording default = Both). * Logging (gated by the logging checkbox): master shaping switch, EQ-mode change, parametric band add/delete, peer rename/clear/delete, and the applied Appearance settings after Preferences close. * CLI: --list-profiles and --list-named-peers (read-only), in --help. * Version bumped to 5.0; About-box changelog, RELEASE_NOTES.md and README updated for v5. Build clean; --selftest passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
fcc3cdc793
commit
aeca24ae17
@@ -204,16 +204,21 @@ internal sealed class RecordingController
|
||||
t.Len = span.Length;
|
||||
}
|
||||
|
||||
// Audio thread. Once per render, flush each peer's summed block to their file and reset.
|
||||
private void FlushPeerTracks()
|
||||
// Audio thread. Once per render, flush each peer's summed block to their file. Every peer track gets
|
||||
// EXACTLY one render block per callback: a peer that produced nothing this block — because it went
|
||||
// quiet and its session was dropped, or it disconnected — is padded with silence to the same length.
|
||||
// That keeps every track sample-locked to the single render clock, so the tracks can never drift
|
||||
// apart from each other however long the recording runs, and they all end up the same length.
|
||||
private void FlushPeerTracks(int floats)
|
||||
{
|
||||
var tracks = peerTracks;
|
||||
if (tracks is null) return;
|
||||
if (tracks is null || floats <= 0) return;
|
||||
foreach (var t in tracks.Values)
|
||||
{
|
||||
if (t.Len <= 0) continue;
|
||||
t.Recorder.WriteReceived(t.Accum.AsMemory(0, t.Len), RenderRoute.Mixed);
|
||||
Array.Clear(t.Accum, 0, t.Len);
|
||||
if (t.Accum.Length < floats) { var a = t.Accum; Array.Resize(ref a, floats); t.Accum = a; }
|
||||
for (int i = t.Len; i < floats; i++) t.Accum[i] = 0f; // pad an empty/short block with silence
|
||||
t.Recorder.WriteReceived(t.Accum.AsMemory(0, floats), RenderRoute.Mixed);
|
||||
Array.Clear(t.Accum, 0, floats);
|
||||
t.Len = 0;
|
||||
}
|
||||
}
|
||||
@@ -227,13 +232,17 @@ internal sealed class RecordingController
|
||||
rawMixLen = span.Length;
|
||||
}
|
||||
|
||||
// Audio thread. Flush the summed raw mix for this render to the single recorder, then reset.
|
||||
private void FlushRawMix()
|
||||
// Audio thread. Flush the summed raw mix for this render to the single recorder, padding a silent
|
||||
// block so the file stays aligned to real time even through total silence, then reset.
|
||||
private void FlushRawMix(int floats)
|
||||
{
|
||||
var rec = active;
|
||||
int n = rawMixLen;
|
||||
if (rec is not null && n > 0) rec.WriteReceived(rawMixAccum.AsMemory(0, n), RenderRoute.Mixed);
|
||||
if (n > 0) { Array.Clear(rawMixAccum, 0, n); rawMixLen = 0; }
|
||||
if (rec is null || floats <= 0) return;
|
||||
if (rawMixAccum.Length < floats) { var a = rawMixAccum; Array.Resize(ref a, floats); rawMixAccum = a; }
|
||||
for (int i = rawMixLen; i < floats; i++) rawMixAccum[i] = 0f;
|
||||
rec.WriteReceived(rawMixAccum.AsMemory(0, floats), RenderRoute.Mixed);
|
||||
Array.Clear(rawMixAccum, 0, floats);
|
||||
rawMixLen = 0;
|
||||
}
|
||||
|
||||
private void StopRecorder(AudioRecorder? r)
|
||||
|
||||
Reference in New Issue
Block a user