Voxis Realtime Guide
Description: Learn the Voxis realtime browser API from scratch: HTML, JavaScript, file input, sliders, microphone, crossfade, convolution IR, and GitHub Pages usage.

Realtime Browser Guide

# Build realtime audio tools with Voxis in the browser.

This guide explains how the browser API works, how HTML elements connect to the player, and how to control realtime effects with file inputs, sliders, microphone input, crossfade, and convolution IR.

    import { createLockedControl, createVoxisRealtimePlayer, effects }
      from "https://spiralRbx.github.io/voxis/api/voxis-realtime.js";

    const player = await createVoxisRealtimePlayer({ container: "#player-host" });

## 1. What the realtime API is

The public browser API lives in the root `api/` folder. It ships:

- `voxis-realtime.js`: public entry point
- `voxis-realtime-player.js`: player and Web Audio graph wrapper
- `voxis-realtime-effects.js`: effect builder helpers
- processor files plus `voxis-realtime-dynamics.wasm`

## 2. The most important concept

Voxis does not auto-detect HTML IDs by magic. Your JavaScript connects DOM elements to the API.

Example:

- You create `<input id="editor-file-input" type="file">` in HTML.
- Your script reads it with `document.querySelector("#editor-file-input")`.
- When the user picks a file, your script calls `player.loadFile(file)`.

So the API handles audio and effects, while your script handles the page wiring.

## 3. Organize the DOM bridge

A simple pattern is to collect all the page elements in one object. That makes it obvious which HTML nodes your editor depends on.

    const $ = (selector) => document.querySelector(selector);

    const elements = {
      fileInput: $("#editor-file-input"),
      host: $("#editor-player-host"),
      gainRange: $("#gain-range"),
      chorusMixRange: $("#chorus-mix-range"),
    };

Voxis still does not read those IDs by itself. Your script reads them, then calls the player methods with the selected file and slider values.

## 4. Minimal HTML structure

    <div id="player-host"></div>
    <input id="editor-file-input" type="file" accept="audio/*">
    <script type="module" src="./main.js"></script>

`#player-host` is where Voxis will create and attach the standard `<audio>` element when you pass that container to the player.

## 5. Minimal JavaScript setup

    import { createLockedControl, createVoxisRealtimePlayer, effects }
      from "https://spiralRbx.github.io/voxis/api/voxis-realtime.js";

    const $ = (selector) => document.querySelector(selector);

    const fileInput = $("#editor-file-input");
    const playerHost = $("#player-host");

    const gainControl = createLockedControl({
      input: "#gain-range",
      output: "#gain-output",
      min: 0.0,
      max: 2.0,
      step: 0.01,
      format: (value) => `${value.toFixed(2)}x`,
    });

    const player = await createVoxisRealtimePlayer({ container: playerHost });

    fileInput.addEventListener("change", async (event) => {
      const [file] = event.target.files || [];
      if (!file) {
        return;
      }
      await player.loadFile(file);
    });

## 6. What `createVoxisRealtimePlayer()` does

The helper starts the audio engine, loads the processors, loads the shipped WASM module, and creates a standard `<audio>` element when you pass a container.

The public constructor options are:

- `container`: selector or DOM element that receives the standard audio player
- `audio`: your own existing `<audio>` element if you do not want Voxis to create one
- `appendAudio`: whether Voxis should append the audio elements to the container
- `processorBaseUrl`: override where the processor files are loaded from
- `wasmUrl`: override where `voxis-realtime-dynamics.wasm` is loaded from

## 7. Apply effects in realtime

Use `player.setEffects([...])` with builders from the `effects` object.

    player.setEffects([
      effects.gain({ value: 1.1 }),
      effects.compressor({ thresholdDb: -18, ratio: 3.0, makeupDb: 2.0 }),
      effects.chorus({ mix: 0.35, rateHz: 0.9 }),
      effects.hall_reverb({ decaySeconds: 1.8, mix: 0.2 }),
    ]);

The builder names are the public browser-side API. They mirror the offline effect names as closely as possible, for example `effects.gain(...)`, `effects.compressor(...)`, `effects.output_level(...)`, `effects.noise_reduction(...)`, and `effects.hrtf_simulation(...)`.

If you want the API itself to enforce stricter app-side limits, pass a `limits` object inside the effect options. That protects against users changing the DOM range in DevTools and sending a larger value than your page intended.

    player.setEffects([
      effects.gain({
        value: gainControl.read(),
        limits: { min: 0.0, max: 2.0 },
      }),
      effects.compressor({
        thresholdDb: thresholdControl.read(),
        ratio: 3.0,
        limits: {
          thresholdDb: { min: -48.0, max: -1.0 },
          ratio: { min: 1.0, max: 12.0 },
        },
      }),
    ]);

`createLockedControl(...)` locks the app-side min, max, and step in JavaScript. So even if the HTML is changed in DevTools, the control is normalized again before your effect reads the value.

That is still app-side protection, not total browser security. The project that embeds Voxis is still responsible for the final policy, because the browser runs in the user's own environment.

The shipped WASM module is now also the native source of truth for realtime guardrail metadata. EQ/filter/dynamics parameters are clamped inside the native bridge itself, and the other realtime sections read their public limits from that same WASM metadata before configuration reaches the JavaScript processors.

    player.onWarning((warning) => {
      console.warn("[Voxis warning]", warning.message);
    });

    console.log(player.getNativeRealtimeLimits());

## 8. Player methods you will use most

- `await player.loadFile(file)`: load a file from an `<input type="file">`
- `await player.loadUrl(url)`: load a file from a URL
- `player.getAudioElement()`: get the standard audio element that Voxis created
- `player.setEffects([...])`: replace the active realtime effect chain
- `player.clearEffects()`: remove all active realtime effects
- `await player.useMicrophone()`: switch the source to microphone monitoring
- `player.stopMicrophone()`: stop microphone monitoring
- `await player.loadCrossfadePartnerFile(file)`: load the second file used by `effects.crossfade(...)`
- `await player.loadConvolutionIrFile(file, true)`: load an impulse response for convolution reverb
- `player.clearConvolutionIr()`: remove the current impulse response
- `player.onWarning((warning) => { ... })`: listen for JavaScript and native realtime guardrail warnings
- `player.getNativeRealtimeLimits()`: inspect the native WASM limits metadata used by the realtime API
- `player.getNativeDynamicsLimits()`: compatibility alias for the same native metadata object
- `await player.destroy()`: shut down the audio engine and release browser resources

## 9. Realtime sliders and range inputs

This is the pattern you use when you want a real editor instead of fixed presets.

    <input id="gain-range" type="range" min="0" max="5" step="0.01" value="1">
    <input id="chorus-mix-range" type="range" min="0" max="1" step="0.01" value="0.35">

    const gainControl = createLockedControl({
      input: "#gain-range",
      output: "#gain-output",
      min: 0.0,
      max: 2.0,
      step: 0.01,
      format: (value) => `${value.toFixed(2)}x`,
    });

    const chorusMixControl = createLockedControl({
      input: "#chorus-mix-range",
      min: 0.0,
      max: 1.0,
      step: 0.01,
    });

    function applyRealtimeEffects() {
      player.setEffects([
        effects.gain({ value: gainControl.read(), limits: { min: 0.0, max: 2.0 } }),
        effects.chorus({ mix: chorusMixControl.read(), rateHz: 0.9 }),
      ]);
    }

    gainControl.element.addEventListener("input", applyRealtimeEffects);
    chorusMixControl.element.addEventListener("input", applyRealtimeEffects);
    applyRealtimeEffects();

This is exactly how the API test editor works: the script reads the current range values, rebuilds the effect list, and sends the new state to the player.

## 10. A complete starter example

    <div id="player-host"></div>
    <input id="editor-file-input" type="file" accept="audio/*">
    <input id="gain-range" type="range" min="0" max="4" step="0.01" value="1">
    <output id="gain-output">1.00x</output>
    <input id="fft-low-range" type="range" min="20" max="4000" step="1" value="90">
    <script type="module">
      import { createLockedControl, createVoxisRealtimePlayer, effects }
        from "https://spiralRbx.github.io/voxis/api/voxis-realtime.js";

      const $ = (selector) => document.querySelector(selector);

      const elements = {
        host: $("#player-host"),
        fileInput: $("#editor-file-input"),
        gainRange: $("#gain-range"),
        fftLowRange: $("#fft-low-range"),
      };

      const player = await createVoxisRealtimePlayer({ container: elements.host });
      const gainControl = createLockedControl({
        input: "#gain-range",
        output: "#gain-output",
        min: 0.0,
        max: 2.0,
        step: 0.01,
        format: (value) => `${value.toFixed(2)}x`,
      });

      function applyEffects() {
        player.setEffects([
          effects.gain({
            value: gainControl.read(),
            limits: { min: 0.0, max: 2.0 },
          }),
          effects.fft_filter({
            lowHz: Number(elements.fftLowRange.value),
            highHz: 9000,
            mix: 1.0,
          }),
        ]);
      }

      elements.fileInput.addEventListener("change", async (event) => {
        const [file] = event.target.files || [];
        if (!file) {
          return;
        }
        await player.loadFile(file);
      });

      gainControl.element.addEventListener("input", applyEffects);
      elements.fftLowRange.addEventListener("input", applyEffects);

      applyEffects();
    </script>

If you want a live starter instead of only a code snippet, open realtime-example.html. It ships a working page plus a `View code` button that reveals the HTML and JavaScript used by the example itself.

## 11. Use microphone input

    await player.useMicrophone();

Stop microphone monitoring with:

    player.stopMicrophone();

## 12. Use a crossfade partner

    <input id="crossfade-file-input" type="file" accept="audio/*">

    const crossfadeInput = $("#crossfade-file-input");

    crossfadeInput.addEventListener("change", async (event) => {
      const [file] = event.target.files || [];
      if (!file) {
        return;
      }
      await player.loadCrossfadePartnerFile(file);
      player.setEffects([
        effects.crossfade({ durationMs: 1200 }),
      ]);
    });

## 13. Use convolution IR

    <input id="ir-file-input" type="file" accept="audio/*">

    const irInput = $("#ir-file-input");

    irInput.addEventListener("change", async (event) => {
      const [file] = event.target.files || [];
      if (!file) {
        player.clearConvolutionIr();
        return;
      }
      await player.loadConvolutionIrFile(file, true);
      player.setEffects([
        effects.convolution_reverb({ mix: 0.28, normalizeIr: true }),
      ]);
    });

## 14. Local files versus GitHub Pages

If you are building inside this repository, you can import from a relative path:

    import { createVoxisRealtimePlayer, effects } from "./api/voxis-realtime.js";

If you are using the published GitHub Pages version from another project, use the full URL:

    import { createVoxisRealtimePlayer, effects }
      from "https://spiralRbx.github.io/voxis/api/voxis-realtime.js";

## 15. What the API already covers

The realtime browser API currently exposes builder helpers for checklist sections 1 through 11, including:

- basic clip preview operations
- dynamics and EQ/filter work
- modulation, ambience, distortion, pitch/time
- modern/AI-like and creative effects
- spectral and stereo/spatial processing

## 16. Where to inspect a working example

The repository also ships a complete live editor in:

- `web-test/api-test/templates/index.html`
- `web-test/api-test/static/editor.js`

That app is a practical reference for wiring file inputs, range inputs, status labels, crossfade, IR loading, and realtime effect updates. For the smallest public starter, use realtime-example.html.