web-sys: WebAudio - The wasm-bindgen Guide (original) (raw)

  1. Introduction
  2. 1. Examples
    1. 1.1. Hello, World!
    2. 1.2. Using console.log
    3. 1.3. Small Wasm files
    4. 1.4. Without a Bundler
    5. 1.5. Synchronous Instantiation
    6. 1.6. Importing functions from JS
    7. 1.7. Working with char
    8. 1.8. js-sys: WebAssembly in WebAssembly
    9. 1.9. web-sys: DOM hello world
    10. 1.10. web-sys: Closures
    11. 1.11. web-sys: performance.now
    12. 1.12. web-sys: using fetch
    13. 1.13. web-sys: Weather report
    14. 1.14. web-sys: canvas hello world
    15. 1.15. web-sys: canvas Julia set
    16. 1.16. web-sys: WebAudio
    17. 1.17. web-sys: WebGL
    18. 1.18. web-sys: WebSockets
    19. 1.19. web-sys: WebRTC DataChannel
    20. 1.20. web-sys: requestAnimationFrame
    21. 1.21. web-sys: A Simple Paint Program
    22. 1.22. web-sys: Wasm in Web Worker
    23. 1.23. Parallel Raytracing
    24. 1.24. Wasm Audio Worklet
    25. 1.25. web-sys: A TODO MVC App
  3. 2. Reference
    1. 2.1. Deployment
    2. 2.2. JS snippets
    3. 2.3. Static JS Objects
    4. 2.4. Passing Rust Closures to JS
    5. 2.5. Receiving JS Closures in Rust
    6. 2.6. Promises and Futures
    7. 2.7. Iterating over JS Values
    8. 2.8. Arbitrary Data with Serde
    9. 2.9. Accessing Properties of Untyped JS Values
    10. 2.10. Working with Duck-Typed Interfaces
    11. 2.11. Command Line Interface
    12. 2.12. Optimizing for Size
    13. 2.13. Supported Rust Targets
    14. 2.14. Supported Browsers
    15. 2.15. Support for Weak References
    16. 2.16. Support for Reference Types
    17. 2.17. Supported Types
      1. 2.17.1. Imported JavaScript Types
      2. 2.17.2. Exported Rust Types
      3. 2.17.3. JsValue
      4. 2.17.4. Box<[T]> and Vec
      5. 2.17.5. *const T and *mut T
      6. 2.17.6. NonNull
      7. 2.17.7. Numbers
      8. 2.17.8. bool
      9. 2.17.9. char
      10. 2.17.10. str
      11. 2.17.11. String
      12. 2.17.12. Number Slices
      13. 2.17.13. Boxed Number Slices
      14. 2.17.14. Result<T, E>
    18. 2.18. #[wasm_bindgen] Attributes
      1. 2.18.1. On JavaScript Imports
        1. 2.18.1.1. catch
          1. 2.18.1.2. constructor
          2. 2.18.1.3. extends
          3. 2.18.1.4. getter and setter
          4. 2.18.1.5. final
          5. 2.18.1.6. indexing_getter, indexing_setter, and indexing_deleter
          6. 2.18.1.7. js_class = "Blah"
          7. 2.18.1.8. js_name
          8. 2.18.1.9. js_namespace
          9. 2.18.1.10. method
          10. 2.18.1.11. module = "blah"
          11. 2.18.1.12. raw_module = "blah"
          12. 2.18.1.13. no_deref
          13. 2.18.1.14. static_method_of = Blah
          14. 2.18.1.15. structural
          15. 2.18.1.16. typescript_type
          16. 2.18.1.17. variadic
          17. 2.18.1.18. vendor_prefix
      2. 2.18.2. On Rust Exports
        1. 2.18.2.1. constructor
          1. 2.18.2.2. js_name = Blah
          2. 2.18.2.3. js_class = Blah
          3. 2.18.2.4. readonly
          4. 2.18.2.5. skip
          5. 2.18.2.6. skip_jsdoc
          6. 2.18.2.7. start
          7. 2.18.2.8. main
          8. 2.18.2.9. typescript_custom_section
          9. 2.18.2.10. getter and setter
          10. 2.18.2.11. inspectable
          11. 2.18.2.12. skip_typescript
          12. 2.18.2.13. getter_with_clone
          13. 2.18.2.14. unchecked_return_type and unchecked_param_type
          14. 2.18.2.15. return_description and param_description
  4. 3. web-sys
    1. 3.1. Using web-sys
    2. 3.2. Cargo Features
    3. 3.3. Function Overloads
    4. 3.4. Type Translations
    5. 3.5. Inheritance
    6. 3.6. Unstable APIs
  5. 4. Testing with wasm-bindgen-test
    1. 4.1. Usage
    2. 4.2. Writing Asynchronous Tests
    3. 4.3. Testing in Headless Browsers
    4. 4.4. Continuous Integration
    5. 4.5. Coverage (Experimental)
  6. 5. Contributing to wasm-bindgen
    1. 5.1. Testing
  7. 5.2. Internal Design
    1. 5.2.1. JS Objects in Rust
      1. 5.2.2. Exporting a function to JS
      2. 5.2.3. Exporting a struct to JS
      3. 5.2.4. Importing a function from JS
      4. 5.2.5. Importing a class from JS
      5. 5.2.6. Rust Type conversions
      6. 5.2.7. Types in wasm-bindgen
  8. 5.3. js-sys
    1. 5.3.1. Testing
      1. 5.3.2. Adding More APIs
  9. 5.4. web-sys
    1. 5.4.1. Overview
      1. 5.4.2. Testing
      2. 5.4.3. Logging
      3. 5.4.4. Supporting More Web APIs
  10. 5.5. Publishing
  11. 5.6. Team

The `wasm-bindgen` Guide

WebAudio

View full source code or view the compiled example online

This example creates an FM oscillator using the WebAudio API andweb-sys.

Cargo.toml

The Cargo.toml enables the types needed to use the relevant bits of the WebAudio API.

[package]
authors = ["The wasm-bindgen Developers"]
edition = "2021"
name = "webaudio"
publish = false
version = "0.0.0"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = { path = "../../" }

[dependencies.web-sys]
features = [
  'AudioContext',
  'AudioDestinationNode',
  'AudioNode',
  'AudioParam',
  'GainNode',
  'OscillatorNode',
  'OscillatorType',
]
path = "../../crates/web-sys"

[lints]
workspace = true

src/lib.rs

The Rust code implements the FM oscillator.


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;
use web_sys::{AudioContext, OscillatorType};

/// Converts a midi note to frequency
///
/// A midi note is an integer, generally in the range of 21 to 108
pub fn midi_to_freq(note: u8) -> f32 {
    27.5 * 2f32.powf((note as f32 - 21.0) / 12.0)
}

#[wasm_bindgen]
pub struct FmOsc {
    ctx: AudioContext,
    /// The primary oscillator.  This will be the fundamental frequency
    primary: web_sys::OscillatorNode,

    /// Overall gain (volume) control
    gain: web_sys::GainNode,

    /// Amount of frequency modulation
    fm_gain: web_sys::GainNode,

    /// The oscillator that will modulate the primary oscillator's frequency
    fm_osc: web_sys::OscillatorNode,

    /// The ratio between the primary frequency and the fm_osc frequency.
    ///
    /// Generally fractional values like 1/2 or 1/4 sound best
    fm_freq_ratio: f32,

    fm_gain_ratio: f32,
}

impl Drop for FmOsc {
    fn drop(&mut self) {
        let _ = self.ctx.close();
    }
}

#[wasm_bindgen]
impl FmOsc {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Result<FmOsc, JsValue> {
        let ctx = web_sys::AudioContext::new()?;

        // Create our web audio objects.
        let primary = ctx.create_oscillator()?;
        let fm_osc = ctx.create_oscillator()?;
        let gain = ctx.create_gain()?;
        let fm_gain = ctx.create_gain()?;

        // Some initial settings:
        primary.set_type(OscillatorType::Sine);
        primary.frequency().set_value(440.0); // A4 note
        gain.gain().set_value(0.0); // starts muted
        fm_gain.gain().set_value(0.0); // no initial frequency modulation
        fm_osc.set_type(OscillatorType::Sine);
        fm_osc.frequency().set_value(0.0);

        // Connect the nodes up!

        // The primary oscillator is routed through the gain node, so that
        // it can control the overall output volume.
        primary.connect_with_audio_node(&gain)?;

        // Then connect the gain node to the AudioContext destination (aka
        // your speakers).
        gain.connect_with_audio_node(&ctx.destination())?;

        // The FM oscillator is connected to its own gain node, so it can
        // control the amount of modulation.
        fm_osc.connect_with_audio_node(&fm_gain)?;

        // Connect the FM oscillator to the frequency parameter of the main
        // oscillator, so that the FM node can modulate its frequency.
        fm_gain.connect_with_audio_param(&primary.frequency())?;

        // Start the oscillators!
        primary.start()?;
        fm_osc.start()?;

        Ok(FmOsc {
            ctx,
            primary,
            gain,
            fm_gain,
            fm_osc,
            fm_freq_ratio: 0.0,
            fm_gain_ratio: 0.0,
        })
    }

    /// Sets the gain for this oscillator, between 0.0 and 1.0.
    #[wasm_bindgen]
    pub fn set_gain(&self, mut gain: f32) {
        gain = gain.clamp(0.0, 1.0);
        self.gain.gain().set_value(gain);
    }

    #[wasm_bindgen]
    pub fn set_primary_frequency(&self, freq: f32) {
        self.primary.frequency().set_value(freq);

        // The frequency of the FM oscillator depends on the frequency of the
        // primary oscillator, so we update the frequency of both in this method.
        self.fm_osc.frequency().set_value(self.fm_freq_ratio * freq);
        self.fm_gain.gain().set_value(self.fm_gain_ratio * freq);
    }

    #[wasm_bindgen]
    pub fn set_note(&self, note: u8) {
        let freq = midi_to_freq(note);
        self.set_primary_frequency(freq);
    }

    /// This should be between 0 and 1, though higher values are accepted.
    #[wasm_bindgen]
    pub fn set_fm_amount(&mut self, amt: f32) {
        self.fm_gain_ratio = amt;

        self.fm_gain
            .gain()
            .set_value(self.fm_gain_ratio * self.primary.frequency().value());
    }

    /// This should be between 0 and 1, though higher values are accepted.
    #[wasm_bindgen]
    pub fn set_fm_frequency(&mut self, amt: f32) {
        self.fm_freq_ratio = amt;
        self.fm_osc
            .frequency()
            .set_value(self.fm_freq_ratio * self.primary.frequency().value());
    }
}

#}

index.js

A small bit of JavaScript glues the rust module to input widgets and translates events into calls into Wasm code.

import('./pkg')
  .then(rust_module => {
    let fm = null;

    const play_button = document.getElementById("play");
    play_button.addEventListener("click", event => {
      if (fm === null) {
        fm = new rust_module.FmOsc();
        fm.set_note(50);
        fm.set_fm_frequency(0);
        fm.set_fm_amount(0);
        fm.set_gain(0.8);
      } else {
        fm.free();
        fm = null;
      }
    });

    const primary_slider = document.getElementById("primary_input");
    primary_slider.addEventListener("input", event => {
      if (fm) {
        fm.set_note(parseInt(event.target.value));
      }
    });

    const fm_freq = document.getElementById("fm_freq");
    fm_freq.addEventListener("input", event => {
      if (fm) {
        fm.set_fm_frequency(parseFloat(event.target.value));
      }
    });

    const fm_amount = document.getElementById("fm_amount");
    fm_amount.addEventListener("input", event => {
      if (fm) {
        fm.set_fm_amount(parseFloat(event.target.value));
      }
    });
  })
  .catch(console.error);