# telemetry _(no submodules)_ ## Purpose OpenTelemetry instrument bridge for the SpessaSynth plugin. Exposes three instruments under the meter `@suaveplan/audio-fx-plugin-spessasynth`: a monotonic counter `spessasynth_voices_triggered_total`, a histogram `spessasynth_process_duration_us`, and an observable gauge `spessasynth_voices_active`. The instruments are lazily created on first use and cached so the underlying OTel SDK sees a stable identity across the process lifetime. The plugin core, voice manager, and MIDI router call into this module — none of those modules import `@opentelemetry/api` directly — so telemetry can be swapped or stubbed by mutating this single file. ## Features - Lazy instrument creation: instruments are not registered with the SDK until first call. - `recordVoicesTriggered(n?)` — counter bump on every `noteOn`. - `recordProcessDuration(durationUs)` — histogram sample per `process()` block. - `setActiveVoices(n)` — latched value the observable gauge reads on each collection. - `getActiveVoicesValue()` — read-back for diagnostics and unit tests. - `resetForTesting()` — clears the singletons so a test suite that swaps the meter provider can re-register from scratch. - All instrument labels are cardinality-stable: no per-channel dimensions, no per-voice tags — chosen so a long-running synth session does not blow up the metric backend. ## Usage: Basic ```ts import { recordVoicesTriggered, recordProcessDuration, setActiveVoices, } from "@suaveplan/audio-fx-plugin-spessasynth/telemetry"; recordVoicesTriggered(1); recordProcessDuration(412); // microseconds setActiveVoices(processor.voiceCount); ``` ## Usage: Advanced ```ts import { metrics } from "@opentelemetry/api"; import { MeterProvider } from "@opentelemetry/sdk-metrics"; import { InMemoryMetricExporter } from "@opentelemetry/sdk-metrics"; import { resetForTesting, setActiveVoices, } from "@suaveplan/audio-fx-plugin-spessasynth/telemetry"; const exporter = new InMemoryMetricExporter(0); const provider = new MeterProvider({ readers: [/* periodic reader */] }); metrics.setGlobalMeterProvider(provider); resetForTesting(); setActiveVoices(3); const collected = await exporter.collect(); // `spessasynth_voices_active` gauge in collected.scopeMetrics → 3 ``` ## API Reference - `recordVoicesTriggered(n?: number): void` — counter add, default 1. - `recordProcessDuration(durationUs: number): void` — histogram record. - `setActiveVoices(count: number): void` — latch the gauge value. - `getActiveVoicesValue(): number` — read the latched value. - `resetForTesting(): void` — clears singletons (test-only). ## Implementation Notes The observable gauge is a one-shot construction with a callback that reads the latched `_activeVoicesValue` module-level variable. The latched approach (rather than passing the processor in to read `voiceCount`) decouples the telemetry module from the upstream library and lets `setActiveVoices` be called from a high-frequency path (every `process()` block) without per-call overhead. `resetForTesting` deliberately drops references to the cached instruments — the OTel API gracefully re-creates them on the next call, but the gauge callback registration is lost; tests that need to re-arm the gauge should call `setActiveVoices` at least once after resetting.