1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
use std::{cell::Cell, rc::Rc};
use cpal::{
traits::{DeviceTrait, HostTrait, StreamTrait},
Stream,
};
use wasm_bindgen::prelude::*;
use web_sys::console;
// This is like the `main` function, except for JavaScript.
#[wasm_bindgen(start)]
pub fn main_js() -> Result<(), JsValue> {
// This provides better error messages in debug mode.
// It's disabled in release mode, so it doesn't bloat up the file size.
#[cfg(debug_assertions)]
console_error_panic_hook::set_once();
let document = gloo::utils::document();
let play_button = document.get_element_by_id("play").unwrap();
let stop_button = document.get_element_by_id("stop").unwrap();
// stream needs to be referenced from the "play" and "stop" closures
let stream = Rc::new(Cell::new(None));
// set up play button
{
let stream = stream.clone();
let closure = Closure::<dyn FnMut(_)>::new(move |_event: web_sys::MouseEvent| {
stream.set(Some(beep()));
});
play_button
.add_event_listener_with_callback("mousedown", closure.as_ref().unchecked_ref())?;
closure.forget();
}
// set up stop button
{
let closure = Closure::<dyn FnMut(_)>::new(move |_event: web_sys::MouseEvent| {
// stop the stream by dropping it
stream.take();
});
stop_button
.add_event_listener_with_callback("mousedown", closure.as_ref().unchecked_ref())?;
closure.forget();
}
Ok(())
}
fn beep() -> Stream {
let host = cpal::default_host();
let device = host
.default_output_device()
.expect("failed to find a default output device");
let config = device.default_output_config().unwrap();
match config.sample_format() {
cpal::SampleFormat::F32 => run::<f32>(&device, config.into()),
cpal::SampleFormat::I16 => run::<i16>(&device, config.into()),
cpal::SampleFormat::U16 => run::<u16>(&device, config.into()),
_ => panic!("unsupported sample format"),
}
}
fn run<T>(device: &cpal::Device, config: cpal::StreamConfig) -> Stream
where
T: cpal::Sample + cpal::SizedSample + cpal::FromSample<f32>,
{
let sample_rate = config.sample_rate as f32;
let channels = config.channels as usize;
// Produce a sinusoid of maximum amplitude.
let mut sample_clock = 0f32;
let mut next_value = move || {
sample_clock = (sample_clock + 1.0) % sample_rate;
(sample_clock * 440.0 * 2.0 * 3.141592 / sample_rate).sin()
};
let err_fn = |err| console::error_1(&format!("an error occurred on stream: {}", err).into());
let stream = device
.build_output_stream(
config,
move |data: &mut [T], _| write_data(data, channels, &mut next_value),
err_fn,
None,
)
.unwrap();
stream.play().unwrap();
stream
}
fn write_data<T>(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> f32)
where
T: cpal::Sample + cpal::FromSample<f32>,
{
for frame in output.chunks_mut(channels) {
let sample = next_sample();
let value = T::from_sample::<f32>(sample);
for sample in frame.iter_mut() {
*sample = value;
}
}
}
|