nandi/jolt-nativepublic Fork 0
cfd3e3677bed92e80cfe447469a249cfdc0b1401
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 · 106 lines · 3.3 KBRust Blame HistoryRaw
Lift freeq's AV media plane out of sleek 90f8b89 nandi 19d 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 =
52 cpal::host_from_id(cpal::HostId::AudioWorklet).expect("AudioWorklet host not available");
53
54 let device = host
55 .default_output_device()
56 .expect("failed to find a default output device");
57 let config = device.default_output_config().unwrap();
58
59 match config.sample_format() {
60 cpal::SampleFormat::F32 => run::<f32>(&device, config.into()),
61 cpal::SampleFormat::I16 => run::<i16>(&device, config.into()),
62 cpal::SampleFormat::U16 => run::<u16>(&device, config.into()),
63 _ => panic!("unsupported sample format"),
64 }
65}
66
67fn run<T>(device: &cpal::Device, config: cpal::StreamConfig) -> Stream
68where
69 T: cpal::Sample + cpal::SizedSample + cpal::FromSample<f32>,
70{
71 let sample_rate = config.sample_rate as f32;
72 let channels = config.channels as usize;
73
74 // Produce a sinusoid of maximum amplitude.
75 let mut sample_clock = 0f32;
76 let mut next_value = move || {
77 sample_clock = (sample_clock + 1.0) % sample_rate;
78 (sample_clock * 440.0 * 2.0 * std::f32::consts::PI / sample_rate).sin()
79 };
80
81 let err_fn = |err| console::error_1(&format!("an error occurred on stream: {}", err).into());
82
83 let stream = device
84 .build_output_stream(
85 config,
86 move |data: &mut [T], _| write_data(data, channels, &mut next_value),
87 err_fn,
88 None,
89 )
90 .unwrap();
91 stream.play().unwrap();
92 stream
93}
94
95fn write_data<T>(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> f32)
96where
97 T: cpal::Sample + cpal::FromSample<f32>,
98{
99 for frame in output.chunks_mut(channels) {
100 let sample = next_sample();
101 let value = T::from_sample::<f32>(sample);
102 for sample in frame.iter_mut() {
103 *sample = value;
104 }
105 }
106}