/* * fee[dB]ack — pane streams. * * The main realm's sampler for high-rate numeric data a pane wants to display: * the playhead, and audio levels. * * Why a sampler rather than letting panes read the sources directly: * * 1. An AnalyserNode cannot cross a window boundary. A popped-out pane can * never hold one. So levels must be reduced to plain numbers HERE, in the * realm that owns the audio graph, and shipped as numbers. Making the * docked path work the same way is what keeps one `mount()` valid in both * realms. * 2. Per the plugin performance rules, playback-tied loops must stop when * nothing is looking at them. One shared rAF loop, reference-counted * against live subscriptions, is strictly cheaper than N plugin loops — * and it stops dead when the last pane closes. * * Exposes `window.__fbPaneStreams` (host-internal; panes reach this through * ctx.subscribe()). */ (function () { 'use strict'; // Sources are sampled every frame; a source that returns `undefined` is // simply unavailable right now (no stems plugin, no song loaded) and its // subscribers are not called at all — better than feeding them zeros they'd // render as a real silent signal. const SOURCES = { // { t, duration, playing } — the transport position. playhead() { const hw = window.highway; if (!hw || typeof hw.getTime !== 'function') return undefined; const t = hw.getTime(); if (!Number.isFinite(t)) return undefined; const info = (typeof hw.getSongInfo === 'function' && hw.getSongInfo()) || {}; const bus = window.feedBack; return { t: t, duration: Number.isFinite(info.duration) ? info.duration : 0, playing: !!(bus && bus.isPlaying), }; }, // { master } — 0..1 RMS of the master bus. // // Read from the stems plugin's analyser when present. It mutes the core //