Delay runtime sources until scene ready
This commit is contained in:
parent
6e370ff2ac
commit
38fa830211
4 changed files with 72 additions and 32 deletions
|
|
@ -254,7 +254,7 @@ using var configuration = new DisposableConfiguration(MimirRuntimeConfiguration.
|
|||
using var hub = new MimirSynchronizationHub(configuration.Value.Settings);
|
||||
var syncMode = configuration.Value.Settings.Audio.Mode;
|
||||
|
||||
foreach (var source in configuration.Value.Sources)
|
||||
foreach (var source in configuration.Sources.ToArray())
|
||||
{
|
||||
configuration.Detach(source);
|
||||
hub.AddSource(source);
|
||||
|
|
@ -4997,11 +4997,13 @@ internal sealed class DisposableConfiguration : IDisposable
|
|||
public DisposableConfiguration(MimirRuntimeConfiguration value)
|
||||
{
|
||||
Value = value;
|
||||
ownedSources = value.Sources.ToList();
|
||||
ownedSources = value.CreateSources().ToList();
|
||||
}
|
||||
|
||||
public MimirRuntimeConfiguration Value { get; }
|
||||
|
||||
public IReadOnlyList<IMimirStreamSource> Sources => ownedSources;
|
||||
|
||||
public void Detach(IMimirStreamSource source)
|
||||
{
|
||||
ownedSources.Remove(source);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ public sealed class MimirRuntime : IAquariumRuntime
|
|||
private const float DefaultSpectrumUpdateIntervalSeconds = 0.2f;
|
||||
private readonly MimirSynchronizationHub synchronization;
|
||||
private readonly MimirAudioSpectrumAnalyzer spectrumAnalyzer;
|
||||
private readonly IReadOnlyList<MimirStreamSourceFactory> sourceFactories;
|
||||
private readonly AquariumUiDocument ui;
|
||||
private readonly AquariumAudioDocument audio = new();
|
||||
private readonly MimirAudioSynchronizationSettings audioSyncSettings;
|
||||
|
|
@ -49,12 +50,12 @@ public sealed class MimirRuntime : IAquariumRuntime
|
|||
}
|
||||
|
||||
public MimirRuntime(AquariumRuntimeOptions options, MimirRuntimeConfiguration configuration)
|
||||
: this(options, configuration.Settings, configuration.Sources)
|
||||
: this(options, configuration.Settings, configuration.SourceFactories)
|
||||
{
|
||||
}
|
||||
|
||||
public MimirRuntime(AquariumRuntimeOptions options, MimirSynchronizationSettings settings)
|
||||
: this(options, settings, [])
|
||||
: this(options, settings, Array.Empty<IMimirStreamSource>())
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -62,10 +63,23 @@ public sealed class MimirRuntime : IAquariumRuntime
|
|||
AquariumRuntimeOptions options,
|
||||
MimirSynchronizationSettings settings,
|
||||
IEnumerable<IMimirStreamSource> streamSources)
|
||||
: this(options, settings, Array.Empty<MimirStreamSourceFactory>())
|
||||
{
|
||||
foreach (var source in streamSources)
|
||||
{
|
||||
synchronization.AddSource(source);
|
||||
}
|
||||
}
|
||||
|
||||
private MimirRuntime(
|
||||
AquariumRuntimeOptions options,
|
||||
MimirSynchronizationSettings settings,
|
||||
IEnumerable<MimirStreamSourceFactory> sourceFactories)
|
||||
{
|
||||
Options = options;
|
||||
synchronization = new MimirSynchronizationHub(settings);
|
||||
spectrumAnalyzer = new MimirAudioSpectrumAnalyzer(ParseSpectrumFftSize());
|
||||
this.sourceFactories = sourceFactories.ToArray();
|
||||
audioSyncSettings = settings.Audio;
|
||||
telemetryIntervalSeconds = ParseTelemetryIntervalSeconds();
|
||||
audioSyncUpdateIntervalSeconds = ParseAudioSyncIntervalSeconds();
|
||||
|
|
@ -80,11 +94,6 @@ public sealed class MimirRuntime : IAquariumRuntime
|
|||
: null;
|
||||
nextAudioSyncSeconds = audioSyncUpdateIntervalSeconds;
|
||||
nextTelemetrySeconds = telemetryIntervalSeconds;
|
||||
foreach (var source in streamSources)
|
||||
{
|
||||
synchronization.AddSource(source);
|
||||
}
|
||||
|
||||
ui = CreateUi();
|
||||
}
|
||||
|
||||
|
|
@ -124,15 +133,16 @@ public sealed class MimirRuntime : IAquariumRuntime
|
|||
|
||||
public void Update(float deltaSeconds, InputState input)
|
||||
{
|
||||
runtimeSeconds += Math.Max(deltaSeconds, 0.0f);
|
||||
lastPollCount = synchronization.PollSources();
|
||||
if (sceneReady)
|
||||
if (!sceneReady)
|
||||
{
|
||||
QueueCalibrationTimeline();
|
||||
UpdateAudioSpectra();
|
||||
UpdateAudioSynchronization();
|
||||
return;
|
||||
}
|
||||
|
||||
runtimeSeconds += Math.Max(deltaSeconds, 0.0f);
|
||||
lastPollCount = synchronization.PollSources();
|
||||
QueueCalibrationTimeline();
|
||||
UpdateAudioSpectra();
|
||||
UpdateAudioSynchronization();
|
||||
EmitTelemetry();
|
||||
}
|
||||
|
||||
|
|
@ -144,6 +154,7 @@ public sealed class MimirRuntime : IAquariumRuntime
|
|||
}
|
||||
|
||||
sceneReady = true;
|
||||
StartConfiguredSources();
|
||||
nextAudioSyncSeconds = runtimeSeconds + audioSyncUpdateIntervalSeconds;
|
||||
nextSpectrumSeconds = runtimeSeconds;
|
||||
Console.WriteLine($"Mimir Fensalir scene ready at {runtimeSeconds:0.000}s; runtime audio tests and spectra enabled.");
|
||||
|
|
@ -365,6 +376,18 @@ public sealed class MimirRuntime : IAquariumRuntime
|
|||
nextSpectrumSeconds = runtimeSeconds + spectrumUpdateIntervalSeconds;
|
||||
}
|
||||
|
||||
private void StartConfiguredSources()
|
||||
{
|
||||
foreach (var factory in sourceFactories)
|
||||
{
|
||||
var source = factory.Create();
|
||||
if (source != null)
|
||||
{
|
||||
synchronization.AddSource(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EmitTelemetry()
|
||||
{
|
||||
if (telemetryIntervalSeconds <= 0.0f || runtimeSeconds < nextTelemetrySeconds)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,16 @@ public sealed class MimirRuntimeConfiguration
|
|||
{
|
||||
public MimirSynchronizationSettings Settings { get; init; } = new();
|
||||
|
||||
public IReadOnlyList<IMimirStreamSource> Sources { get; init; } = [];
|
||||
public IReadOnlyList<MimirStreamSourceFactory> SourceFactories { get; init; } = [];
|
||||
|
||||
public IReadOnlyList<IMimirStreamSource> CreateSources()
|
||||
{
|
||||
return SourceFactories
|
||||
.Select(factory => factory.Create())
|
||||
.Where(source => source != null)
|
||||
.Cast<IMimirStreamSource>()
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public static MimirRuntimeConfiguration Load()
|
||||
{
|
||||
|
|
@ -39,10 +48,10 @@ public sealed class MimirRuntimeConfiguration
|
|||
var streams = sourceModels
|
||||
.SelectMany(ToDescriptors)
|
||||
.ToArray();
|
||||
var sources = sourceModels
|
||||
.Select(stream => TryCreateSource(stream, configDirectory))
|
||||
.Where(source => source != null)
|
||||
.Cast<IMimirStreamSource>()
|
||||
var sourceFactories = sourceModels
|
||||
.Select(stream => TryCreateSourceFactory(stream, configDirectory))
|
||||
.Where(factory => factory != null)
|
||||
.Cast<MimirStreamSourceFactory>()
|
||||
.ToArray();
|
||||
|
||||
return new MimirRuntimeConfiguration
|
||||
|
|
@ -53,7 +62,7 @@ public sealed class MimirRuntimeConfiguration
|
|||
Audio = audio,
|
||||
Streams = streams,
|
||||
},
|
||||
Sources = sources,
|
||||
SourceFactories = sourceFactories,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -113,11 +122,12 @@ public sealed class MimirRuntimeConfiguration
|
|||
}
|
||||
}
|
||||
|
||||
private static IMimirStreamSource? TryCreateSource(MimirStreamConfig stream, string? configDirectory)
|
||||
private static MimirStreamSourceFactory? TryCreateSourceFactory(MimirStreamConfig stream, string? configDirectory)
|
||||
{
|
||||
var descriptor = ToDescriptor(stream);
|
||||
if (string.Equals(stream.Adapter, "native", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new MimirNativeIngestStreamSource(ToDescriptor(stream));
|
||||
return new MimirStreamSourceFactory(descriptor, () => new MimirNativeIngestStreamSource(descriptor));
|
||||
}
|
||||
|
||||
if (string.Equals(stream.Adapter, "asio", StringComparison.OrdinalIgnoreCase))
|
||||
|
|
@ -128,13 +138,13 @@ public sealed class MimirRuntimeConfiguration
|
|||
}
|
||||
|
||||
#pragma warning disable CA1416
|
||||
return new MimirAsioStreamSource(
|
||||
ToDescriptor(stream),
|
||||
return new MimirStreamSourceFactory(descriptor, () => new MimirAsioStreamSource(
|
||||
descriptor,
|
||||
new MimirAsioStreamSourceOptions(
|
||||
ResolveCommand(stream.Command, configDirectory),
|
||||
stream.DriverClsid,
|
||||
stream.SampleRate,
|
||||
stream.AcceptSourceIds));
|
||||
stream.AcceptSourceIds)));
|
||||
#pragma warning restore CA1416
|
||||
}
|
||||
|
||||
|
|
@ -146,14 +156,14 @@ public sealed class MimirRuntimeConfiguration
|
|||
return null;
|
||||
}
|
||||
|
||||
return new MimirFrameEventProcessStreamSource(
|
||||
ToDescriptor(stream),
|
||||
return new MimirStreamSourceFactory(descriptor, () => new MimirFrameEventProcessStreamSource(
|
||||
descriptor,
|
||||
new MimirFrameEventProcessStreamSourceOptions(
|
||||
ResolveCommand(stream.Command, configDirectory),
|
||||
stream.Arguments,
|
||||
stream.AcceptSourceIds.Length > 0
|
||||
? new HashSet<string>(stream.AcceptSourceIds, StringComparer.Ordinal)
|
||||
: null));
|
||||
: null)));
|
||||
}
|
||||
|
||||
if (!string.Equals(stream.Adapter, "process", StringComparison.OrdinalIgnoreCase))
|
||||
|
|
@ -166,12 +176,12 @@ public sealed class MimirRuntimeConfiguration
|
|||
return null;
|
||||
}
|
||||
|
||||
return new MimirProcessStreamSource(
|
||||
ToDescriptor(stream),
|
||||
return new MimirStreamSourceFactory(descriptor, () => new MimirProcessStreamSource(
|
||||
descriptor,
|
||||
new MimirProcessStreamSourceOptions(
|
||||
ResolveCommand(stream.Command, configDirectory),
|
||||
stream.Arguments,
|
||||
Math.Max(1024, stream.ChunkBytes)));
|
||||
Math.Max(1024, stream.ChunkBytes))));
|
||||
}
|
||||
|
||||
private static string ResolveCommand(string command, string? configDirectory)
|
||||
|
|
@ -201,6 +211,10 @@ public sealed class MimirRuntimeConfiguration
|
|||
}
|
||||
}
|
||||
|
||||
public sealed record MimirStreamSourceFactory(
|
||||
MimirStreamDescriptor Descriptor,
|
||||
Func<IMimirStreamSource?> Create);
|
||||
|
||||
public sealed class MimirRuntimeConfigFile
|
||||
{
|
||||
public double BufferSeconds { get; set; } = 5.0;
|
||||
|
|
|
|||
|
|
@ -110,3 +110,4 @@
|
|||
{"ts": "2026-05-25T06:50:00+01:00", "type": "implementation", "status": "ok", "note": "Corrected `MimirDirectPathTracker` confidence scoring. The old score normalized direct-cluster confidence against all alternate matched-filter lobes, which made a nearly perfect loopback lock report only 0.126 confidence. Confidence now primarily belongs to the selected direct cluster: direct-hit count, residual tightness, phase coherence, and prediction agreement; nearby competing lobes apply only an ambiguity penalty. Reanalysis of `scarlett-canary-packet-live-20260525-063229-f32.raw` leaves delays unchanged but raises Loopback 1 -> Loopback 2 confidence to 0.971, cardioid confidence to 0.488, and the weaker/disconnected-looking channel to 0.335."}
|
||||
{"ts": "2026-05-25T06:59:00+01:00", "type": "correction", "status": "invalidated", "note": "Invalidated the apparent cardioid timing proof in `calibration/bioacoustic/complex-contour-live-20260525-063229.md`. The log-mel spectrogram and channel statistics show `asio-ch0` is not a real acoustic signal in that capture: RMS is almost flat (`0.00208350` to `0.00209815` by 0.1s block), peak is only `0.00334287`, and zero-lag correlation with loopback is `0.000032`. The near-seed +1.547 us contour estimate was a seeded tracker fit against noise, not physical mic evidence. The receipt remains valid as a loopback/interface proof (`asio-ch2` -> `asio-ch3` at -0.014 us, confidence 0.971) plus a weak/non-authoritative input witness."}
|
||||
{"ts": "2026-05-25T07:05:34+01:00", "type": "implementation", "status": "ok", "note": "Added runtime FFT spectra and Fensalir readiness gating. Fensalir now calls IAquariumRuntime.OnSceneReady after the first presented ready frame; Mimir waits for that callback before emitting bioacoustic test audio, running sync analysis, or updating the cached spectrum diagnostics. MimirAudioSpectrumAnalyzer computes bounded Hann-windowed radix-2 FFT snapshots from rolling audio buffers, exposes top peaks/log bands/RMS/peak, and Mimir renders cached spectra in the DirectWrite UI plus telemetry. Verification run with ASIO config showed scene-ready before bioacoustic chunks and live 8192-point spectra for asio-ch0 through asio-ch3; asio-ch0 remains flat low-level noise while loopback/mic-bearing channels show song-band peaks."}
|
||||
{"ts": "2026-05-25T07:11:48+01:00", "type": "correction", "status": "ok", "note": "Tightened the Fensalir scene-ready gate after runtime spectra testing showed the app still created sources and emitted telemetry before the render path was ready. Runtime config now stores lazy stream-source factories; Mimir.App creates/registers ASIO/process/native sources only inside OnSceneReady, MimirRuntime.Update returns without polling, spectra, sync, telemetry, or audio emission until that callback, and Fensalir constructs the WASAPI/AquaSynth host only after the first presented ready frame. Verification log artifacts/runtime/mimir-ready-gate-20260525-071121.out.log shows no ASIO buffer polling, spectrum telemetry, or bioacoustic batch before D3D12 shader pipelines ready and Mimir Fensalir scene ready."}
|
||||
|
|
|
|||
Loading…
Reference in a new issue