forked from rustwasm/wasm-bindgen
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request rustwasm#716 from eminence/webaudio
Initial example of using the WebAudio APIs from web-sys
- Loading branch information
Showing
11 changed files
with
279 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package-lock.json | ||
node_modules/ | ||
webaudio.js | ||
webaudio_bg.wasm |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[package] | ||
name = "webaudio" | ||
version = "0.1.0" | ||
authors = ["Andrew Chin <achin@eminence32.net>"] | ||
|
||
[lib] | ||
crate-type = ["cdylib"] | ||
|
||
[dependencies] | ||
wasm-bindgen = { path = "../.." } | ||
web-sys = { path = "../../crates/web-sys" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Web Audio example | ||
|
||
This directory is an example of how to use the Web Audio APIs from Rust. It creates a very simple | ||
FM (frequency modulation) synth, and let's you control the primary frequency, the modulation amount, | ||
and the modulation frequency. | ||
|
||
To run, first install some utilities via npm: | ||
|
||
> npm install | ||
Then build the project with either `build.bat` or `build.sh`. | ||
|
||
Finally, run a development web server with `npm run serve` and then open | ||
[http://localhost:8080/](http://localhost:8080/) in a browser! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
cargo +nightly build --target wasm32-unknown-unknown | ||
cargo +nightly run --manifest-path ../../crates/cli/Cargo.toml --bin wasm-bindgen -- ../../target/wasm32-unknown-unknown/debug/webaudio.wasm --out-dir . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
#!/bin/sh | ||
|
||
# For more coments about what's going on here, see the `hello_world` example | ||
|
||
set -ex | ||
|
||
cargo +nightly build --target wasm32-unknown-unknown | ||
cargo +nightly run --manifest-path ../../crates/cli/Cargo.toml \ | ||
--bin wasm-bindgen -- \ | ||
../../target/wasm32-unknown-unknown/debug/webaudio.wasm --out-dir . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<html> | ||
<head> | ||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/> | ||
</head> | ||
<body> | ||
<script> | ||
// Global variable, so a user can play with it via the JS console | ||
var fm; | ||
function play() { | ||
console.log("Rust module not loaded yet!"); | ||
} | ||
</script> | ||
<script src='./index.js'></script> | ||
|
||
<input type="button" value="Click me first to turn on audio" onclick="javascript: play();" /> | ||
(headphone users, please make sure your volume is not too loud!) | ||
|
||
<div> | ||
Primary frequency: <input type="range" min="30" max="80" value="50" style="width: 400px" id="primary_input"/> | ||
</div> | ||
|
||
<div> | ||
Modulation frequency: <input type="range" min="0" max="3" value="0" step="0.05" style="width: 400px" id="fm_freq"/> | ||
</div> | ||
|
||
<div> | ||
Modulation amount: <input type="range" min="0" max="3" value="0" step="0.05" style="width: 400px" id="fm_amount"/> | ||
</div> | ||
|
||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
const rust = import('./webaudio'); | ||
|
||
|
||
// Most browsers don't let WebAudio autoplay without some interaction from the user. So once the module is loaded, | ||
// it's passed to this function which will set up the UI elements for the user to interact with | ||
function setup(rust_module) { | ||
play = function() { | ||
console.log("About to create some music!"); | ||
fm = new rust_module.FmOsc(); | ||
|
||
fm.set_note(50); | ||
fm.set_fm_frequency(0); | ||
fm.set_fm_amount(0); | ||
fm.set_gain(0.8); | ||
|
||
}; | ||
|
||
// create some UI elements | ||
const primary_slider = document.getElementById("primary_input"); | ||
primary_slider.oninput = (e) => { | ||
fm.set_note(e.target.value); | ||
}; | ||
|
||
const fm_freq = document.getElementById("fm_freq"); | ||
fm_freq.oninput = (e) => { | ||
fm.set_fm_frequency(e.target.value); | ||
}; | ||
|
||
const fm_amount = document.getElementById("fm_amount"); | ||
fm_amount.oninput = (e) => { | ||
fm.set_fm_amount(e.target.value); | ||
}; | ||
|
||
console.log("Ready! Press the play button!"); | ||
} | ||
|
||
|
||
rust.then(m => { | ||
setup(m); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"scripts": { | ||
"serve": "webpack-serve ./webpack.config.js" | ||
}, | ||
"devDependencies": { | ||
"webpack": "^4.16.5", | ||
"webpack-serve": "^2.0.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
#![feature(use_extern_macros, nll)] | ||
|
||
extern crate wasm_bindgen; | ||
extern crate web_sys; | ||
|
||
use wasm_bindgen::prelude::*; | ||
use web_sys::{AudioContext, BaseAudioContext, AudioNode, AudioScheduledSourceNode, 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, | ||
|
||
|
||
} | ||
|
||
#[wasm_bindgen] | ||
impl FmOsc { | ||
#[wasm_bindgen(constructor)] | ||
pub fn new() -> FmOsc { | ||
// TODO, how to throw from a constructor? | ||
|
||
let ctx = web_sys::AudioContext::new().unwrap(); | ||
let base: &BaseAudioContext = ctx.as_ref(); | ||
|
||
// create our web audio objects | ||
let primary = base.create_oscillator().unwrap(); | ||
let fm_osc = base.create_oscillator().unwrap(); | ||
let gain = base.create_gain().unwrap(); | ||
let fm_gain = base.create_gain().unwrap(); | ||
|
||
// 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); | ||
|
||
|
||
// Create base class references: | ||
let primary_node: &AudioNode = primary.as_ref(); | ||
let gain_node: &AudioNode = gain.as_ref(); | ||
let fm_osc_node: &AudioNode = fm_osc.as_ref(); | ||
let fm_gain_node: &AudioNode = fm_gain.as_ref(); | ||
let destination = base.destination(); | ||
let destination_node: &AudioNode = destination.as_ref(); | ||
|
||
|
||
// connect them up: | ||
|
||
// The primary oscillator is routed through the gain node, so that it can control the overall output volume | ||
primary_node.connect_with_destination_and_output_and_input_using_destination(gain.as_ref()); | ||
// Then connect the gain node to the AudioContext destination (aka your speakers) | ||
gain_node.connect_with_destination_and_output_and_input_using_destination(destination_node); | ||
|
||
// the FM oscillator is connected to its own gain node, so it can control the amount of modulation | ||
fm_osc_node.connect_with_destination_and_output_and_input_using_destination(fm_gain.as_ref()); | ||
|
||
// Connect the FM oscillator to the frequency parameter of the main oscillator, so that the | ||
// FM node can modulate its frequency | ||
fm_gain_node.connect_with_destination_and_output_using_destination(&primary.frequency()); | ||
|
||
|
||
// start the oscillators! | ||
AsRef::<AudioScheduledSourceNode>::as_ref(&primary).start(); | ||
AsRef::<AudioScheduledSourceNode>::as_ref(&fm_osc).start(); | ||
|
||
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) { | ||
if gain > 1.0 { gain = 1.0; } | ||
if gain < 0.0 { gain = 0.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()); | ||
} | ||
|
||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
const path = require('path'); | ||
|
||
module.exports = { | ||
entry: './index.js', | ||
output: { | ||
path: path.resolve(__dirname, 'dist'), | ||
filename: 'index.js', | ||
}, | ||
mode: 'development' | ||
}; |