nandi/jolt-nativepublic Fork 0
fa4ecdfed6a830e6099d5fab9be24ef72ea1923b
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

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