11use std:: { cell:: Cell , rc:: Rc } ;
22
33use cpal:: {
4- traits:: { DeviceTrait , HostTrait , StreamTrait } ,
54 Device , Error , ErrorKind , FromSample , HostId , Sample , SampleFormat , SizedSample , Stream ,
65 StreamConfig ,
6+ traits:: { DeviceTrait , HostTrait , StreamTrait } ,
7+ } ;
8+ use ringbuf:: {
9+ HeapCons , HeapProd , HeapRb ,
10+ traits:: { Consumer , Producer , Split } ,
711} ;
812use wasm_bindgen:: prelude:: * ;
913use web_sys:: console;
@@ -19,6 +23,8 @@ pub fn main_js() -> Result<(), JsValue> {
1923 let document = gloo:: utils:: document ( ) ;
2024 let play_button = document. get_element_by_id ( "play" ) . unwrap ( ) ;
2125 let stop_button = document. get_element_by_id ( "stop" ) . unwrap ( ) ;
26+ let record_button = document. get_element_by_id ( "record" ) . unwrap ( ) ;
27+ let stop_record_button = document. get_element_by_id ( "stop-record" ) . unwrap ( ) ;
2228
2329 // stream needs to be referenced from the "play" and "stop" closures
2430 let stream = Rc :: new ( Cell :: new ( None ) ) ;
@@ -45,12 +51,36 @@ pub fn main_js() -> Result<(), JsValue> {
4551 closure. forget ( ) ;
4652 }
4753
54+ // input stream needs its own slot; recording and playback run independently
55+ let record_stream = Rc :: new ( Cell :: new ( None ) ) ;
56+
57+ // set up record button
58+ {
59+ let record_stream = record_stream. clone ( ) ;
60+ let closure = Closure :: < dyn FnMut ( _) > :: new ( move |_event : web_sys:: MouseEvent | {
61+ record_stream. set ( Some ( record ( ) ) ) ;
62+ } ) ;
63+ record_button
64+ . add_event_listener_with_callback ( "mousedown" , closure. as_ref ( ) . unchecked_ref ( ) ) ?;
65+ closure. forget ( ) ;
66+ }
67+
68+ // set up stop-record button
69+ {
70+ let closure = Closure :: < dyn FnMut ( _) > :: new ( move |_event : web_sys:: MouseEvent | {
71+ // stop the stream by dropping it; releases the microphone
72+ record_stream. take ( ) ;
73+ } ) ;
74+ stop_record_button
75+ . add_event_listener_with_callback ( "mousedown" , closure. as_ref ( ) . unchecked_ref ( ) ) ?;
76+ closure. forget ( ) ;
77+ }
78+
4879 Ok ( ( ) )
4980}
5081
5182fn beep ( ) -> Stream {
52- let host =
53- cpal:: host_from_id ( HostId :: AudioWorklet ) . expect ( "AudioWorklet host not available" ) ;
83+ let host = cpal:: host_from_id ( HostId :: AudioWorklet ) . expect ( "AudioWorklet host not available" ) ;
5484
5585 let device = host
5686 . default_output_device ( )
@@ -98,6 +128,98 @@ where
98128 stream
99129}
100130
131+ /// Captures microphone input into a ring buffer and immediately plays it back, so you can hear
132+ /// your own voice. Wear headphones: routing a live mic to speakers risks feedback howl.
133+ fn record ( ) -> ( Stream , Stream ) {
134+ let host = cpal:: host_from_id ( HostId :: AudioWorklet ) . expect ( "AudioWorklet host not available" ) ;
135+
136+ let input_device = host
137+ . default_input_device ( )
138+ . expect ( "failed to find a default input device" ) ;
139+ let output_device = host
140+ . default_output_device ( )
141+ . expect ( "failed to find a default output device" ) ;
142+
143+ let input_config = input_device. default_input_config ( ) . unwrap ( ) ;
144+ let output_config = output_device. default_output_config ( ) . unwrap ( ) ;
145+
146+ // Bound end-to-end latency; once full, the producer drops the newest samples instead of
147+ // blocking.
148+ let max_buffered_samples =
149+ output_config. sample_rate ( ) as usize * output_config. channels ( ) as usize / 2 ;
150+ let ring = HeapRb :: < f32 > :: new ( max_buffered_samples) ;
151+ let ( producer, consumer) = ring. split ( ) ;
152+
153+ let input_stream = match input_config. sample_format ( ) {
154+ SampleFormat :: F32 => build_input :: < f32 > ( & input_device, input_config. into ( ) , producer) ,
155+ SampleFormat :: I16 => build_input :: < i16 > ( & input_device, input_config. into ( ) , producer) ,
156+ SampleFormat :: U16 => build_input :: < u16 > ( & input_device, input_config. into ( ) , producer) ,
157+ _ => panic ! ( "unsupported sample format" ) ,
158+ } ;
159+ let output_stream = match output_config. sample_format ( ) {
160+ SampleFormat :: F32 => build_output :: < f32 > ( & output_device, output_config. into ( ) , consumer) ,
161+ SampleFormat :: I16 => build_output :: < i16 > ( & output_device, output_config. into ( ) , consumer) ,
162+ SampleFormat :: U16 => build_output :: < u16 > ( & output_device, output_config. into ( ) , consumer) ,
163+ _ => panic ! ( "unsupported sample format" ) ,
164+ } ;
165+
166+ ( input_stream, output_stream)
167+ }
168+
169+ fn build_input < T > ( device : & Device , config : StreamConfig , mut producer : HeapProd < f32 > ) -> Stream
170+ where
171+ T : Sample + SizedSample ,
172+ f32 : FromSample < T > ,
173+ {
174+ let err_fn = |err : Error | match err. kind ( ) {
175+ ErrorKind :: DeviceChanged | ErrorKind :: RealtimeDenied => {
176+ console:: log_1 ( & format ! ( "{err}" ) . into ( ) )
177+ }
178+ _ => console:: error_1 ( & format ! ( "Stream error: {err}" ) . into ( ) ) ,
179+ } ;
180+
181+ let stream = device
182+ . build_input_stream (
183+ config,
184+ move |data : & [ T ] , _| {
185+ producer. push_iter ( data. iter ( ) . map ( |& s| f32:: from_sample ( s) ) ) ;
186+ } ,
187+ err_fn,
188+ None ,
189+ )
190+ . unwrap ( ) ;
191+ stream. start ( ) . unwrap ( ) ;
192+ stream
193+ }
194+
195+ fn build_output < T > ( device : & Device , config : StreamConfig , mut consumer : HeapCons < f32 > ) -> Stream
196+ where
197+ T : Sample + SizedSample + FromSample < f32 > ,
198+ {
199+ let err_fn = |err : Error | match err. kind ( ) {
200+ ErrorKind :: DeviceChanged | ErrorKind :: RealtimeDenied => {
201+ console:: log_1 ( & format ! ( "{err}" ) . into ( ) )
202+ }
203+ _ => console:: error_1 ( & format ! ( "Stream error: {err}" ) . into ( ) ) ,
204+ } ;
205+
206+ let stream = device
207+ . build_output_stream (
208+ config,
209+ move |data : & mut [ T ] , _| {
210+ for sample in data. iter_mut ( ) {
211+ let value = consumer. try_pop ( ) . unwrap_or ( f32:: EQUILIBRIUM ) ;
212+ * sample = T :: from_sample ( value) ;
213+ }
214+ } ,
215+ err_fn,
216+ None ,
217+ )
218+ . unwrap ( ) ;
219+ stream. start ( ) . unwrap ( ) ;
220+ stream
221+ }
222+
101223fn write_data < T > ( output : & mut [ T ] , channels : usize , next_sample : & mut dyn FnMut ( ) -> f32 )
102224where
103225 T : Sample + FromSample < f32 > ,
0 commit comments