/// # Threading Contract for Browser WASM Exports
///
/// All exported functions assume **single-threaded access** per graph variant.
/// The `@ref.Ref` globals below carry mutable graph state across JavaScript
/// calls and provide no synchronization primitives.
///
/// ## Expected call pattern
///
/// 1. **`init_*_graph(sample_rate, block_size)`** -- call once from the main
/// thread before audio starts. This allocates the graph, context, and
/// output buffers.
/// 2. **`process_*_block(...)`** -- call from the AudioWorklet `process()`
/// callback (audio thread). Must not overlap with any other call on the
/// same graph variant.
/// 3. **`queue_*` / `set_*`** -- call from the main thread only between
/// process calls. Use AudioWorklet `port.postMessage` to synchronize.
/// 4. **`*_sample(index)`** -- call only after `process_*_block` returns
/// `true` and before the next process call, to read output samples.
///
/// ## No concurrent access guarantee
///
/// If `init_*` is called while `process_*` is executing, behavior is
/// undefined (potential use-after-free of graph state). The same applies to
/// any pair of exported functions that touch the same `@ref.Ref` globals
/// concurrently.
///
/// ## Re-initialization
///
/// Calling `init_*` with different `sample_rate`/`block_size` rebuilds the
/// graph from scratch; the old graph is discarded. This must never happen
/// during a `process_*` call.
///|
let demo : @ref.Ref[@moondsp.DemoSource] = @ref.new(@moondsp.DemoSource::new())
///|
/// Browser-export wrapper package that preserves the original Phase 0/1 wasm ABI.
fn main {
()
}
// ---------------------------------------------------------------------------
// Shared constants
// ---------------------------------------------------------------------------
///|
const BROWSER_COMPILED_DEFAULT_FREQ : Double = 440.0
///|
const BROWSER_COMPILED_DEFAULT_GAIN : Double = 0.3
///|
const BROWSER_COMPILED_CLIP_THRESHOLD : Double = 0.95
///|
const BROWSER_COMPILED_DEFAULT_PAN : Double = 0.0
///|
const BROWSER_COMPILED_DEFAULT_CUTOFF : Double = 1800.0
///|
const BROWSER_COMPILED_FILTER_Q : Double = 0.707
///|
const BROWSER_COMPILED_DELAY_CAPACITY : Int = 128
///|
const BROWSER_COMPILED_DELAY_SAMPLES : Int = 0
///|
const BROWSER_COMPILED_OSC_GAIN : Double = 0.2
///|
const BROWSER_COMPILED_MAX_FEEDBACK_GAIN : Double = 0.95
///|
const BROWSER_HOT_SWAP_OLD_CONSTANT : Double = 0.25
///|
const BROWSER_HOT_SWAP_NEW_CONSTANT : Double = 0.75
///|
const BROWSER_HOT_SWAP_CROSSFADE_SAMPLES : Int = 128
///|
const BROWSER_TOPOLOGY_EDIT_INSERTED_GAIN : Double = 0.5
///|
const BROWSER_TOPOLOGY_EDIT_CROSSFADE_SAMPLES : Int = 1024
///|
const BROWSER_STEREO_HOT_SWAP_OLD_CONSTANT : Double = 0.25
///|
const BROWSER_STEREO_HOT_SWAP_NEW_CONSTANT : Double = 0.75
///|
const BROWSER_STEREO_HOT_SWAP_CROSSFADE_SAMPLES : Int = 128
///|
const BROWSER_STEREO_TOPOLOGY_EDIT_OLD_PAN : Double = 0.0
///|
const BROWSER_STEREO_TOPOLOGY_EDIT_NEW_PAN : Double = 1.0
///|
const BROWSER_STEREO_TOPOLOGY_EDIT_CROSSFADE_SAMPLES : Int = 128
// ---------------------------------------------------------------------------
// Demo tick passthrough
// ---------------------------------------------------------------------------
///|
/// Reset browser demo state.
pub fn reset_phase() -> Unit {
demo.val.reset()
reset_compiled_graph()
reset_compiled_hot_swap_graph()
reset_compiled_topology_edit_graph()
reset_compiled_stereo_graph()
reset_compiled_stereo_hot_swap_graph()
reset_compiled_stereo_topology_edit_graph()
reset_exit_deliverable_graph()
reset_scheduler_graph()
}
///|
/// Generate one sine sample for the browser demo.
pub fn tick(freq_hz~ : Double, sample_rate~ : Double) -> Double {
demo.val.tick_source(source_id=0, freq_hz~, sample_rate~)
}
///|
/// Generate one sample from the selected browser demo source.
pub fn tick_source(
source_id~ : Int,
freq_hz~ : Double,
sample_rate~ : Double,
) -> Double {
demo.val.tick_source(source_id~, freq_hz~, sample_rate~)
}
// ---------------------------------------------------------------------------
// Shared helpers
// ---------------------------------------------------------------------------
///|
/// Clamp gain to safe feedback range, replacing NaN/Inf with default.
fn bounded_feedback_gain(gain : Double) -> Double {
if gain.is_nan() || gain.is_inf() {
BROWSER_COMPILED_DEFAULT_GAIN
} else if gain < 0.0 {
0.0
} else if gain > BROWSER_COMPILED_MAX_FEEDBACK_GAIN {
BROWSER_COMPILED_MAX_FEEDBACK_GAIN
} else {
gain
}
}