web-sys: WebAudio - The wasm-bindgen
Guide (original) (raw)
- Introduction
- 1. Examples
- 1.1. Hello, World!
- 1.2. Using console.log
- 1.3. Small Wasm files
- 1.4. Without a Bundler
- 1.5. Synchronous Instantiation
- 1.6. Importing functions from JS
- 1.7. Working with char
- 1.8. js-sys: WebAssembly in WebAssembly
- 1.9. web-sys: DOM hello world
- 1.10. web-sys: Closures
- 1.11. web-sys: performance.now
- 1.12. web-sys: using fetch
- 1.13. web-sys: Weather report
- 1.14. web-sys: canvas hello world
- 1.15. web-sys: canvas Julia set
- 1.16. web-sys: WebAudio
- 1.17. web-sys: WebGL
- 1.18. web-sys: WebSockets
- 1.19. web-sys: WebRTC DataChannel
- 1.20. web-sys: requestAnimationFrame
- 1.21. web-sys: A Simple Paint Program
- 1.22. web-sys: Wasm in Web Worker
- 1.23. Parallel Raytracing
- 1.24. Wasm Audio Worklet
- 1.25. web-sys: A TODO MVC App
- 2. Reference
- 2.1. Deployment
- 2.2. JS snippets
- 2.3. Static JS Objects
- 2.4. Passing Rust Closures to JS
- 2.5. Receiving JS Closures in Rust
- 2.6. Promises and Futures
- 2.7. Iterating over JS Values
- 2.8. Arbitrary Data with Serde
- 2.9. Accessing Properties of Untyped JS Values
- 2.10. Working with Duck-Typed Interfaces
- 2.11. Command Line Interface
- 2.12. Optimizing for Size
- 2.13. Supported Rust Targets
- 2.14. Supported Browsers
- 2.15. Support for Weak References
- 2.16. Support for Reference Types
- 2.17. Supported Types
- 2.18. #[wasm_bindgen] Attributes
- 2.18.1. On JavaScript Imports
- 2.18.1.1. catch
- 2.18.1.2. constructor
- 2.18.1.3. extends
- 2.18.1.4. getter and setter
- 2.18.1.5. final
- 2.18.1.6. indexing_getter, indexing_setter, and indexing_deleter
- 2.18.1.7. js_class = "Blah"
- 2.18.1.8. js_name
- 2.18.1.9. js_namespace
- 2.18.1.10. method
- 2.18.1.11. module = "blah"
- 2.18.1.12. raw_module = "blah"
- 2.18.1.13. no_deref
- 2.18.1.14. static_method_of = Blah
- 2.18.1.15. structural
- 2.18.1.16. typescript_type
- 2.18.1.17. variadic
- 2.18.1.18. vendor_prefix
- 2.18.1.1. catch
- 2.18.2. On Rust Exports
- 2.18.2.1. constructor
- 2.18.2.2. js_name = Blah
- 2.18.2.3. js_class = Blah
- 2.18.2.4. readonly
- 2.18.2.5. skip
- 2.18.2.6. skip_jsdoc
- 2.18.2.7. start
- 2.18.2.8. main
- 2.18.2.9. typescript_custom_section
- 2.18.2.10. getter and setter
- 2.18.2.11. inspectable
- 2.18.2.12. skip_typescript
- 2.18.2.13. getter_with_clone
- 2.18.2.14. unchecked_return_type and unchecked_param_type
- 2.18.2.15. return_description and param_description
- 2.18.2.1. constructor
- 3. web-sys
- 4. Testing with wasm-bindgen-test
- 5. Contributing to wasm-bindgen
- 5.2. Internal Design
- 5.3. js-sys
- 5.4. web-sys
- 5.5. Publishing
- 5.6. Team
The `wasm-bindgen` Guide
View full source code or view the compiled example online
This example creates an FM oscillator using the WebAudio API andweb-sys
.
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
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());
}
}
#}
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);