1use alloc::vec::Vec;
18
19use azul_core::audio::{AudioConfig, AudioFrame};
20
21use super::capture_common::mic_backend;
22use azul_core::callbacks::Update;
23use azul_core::dom::{ComponentEventFilter, DatasetMergeCallbackType, Dom, EventFilter};
24use azul_core::refany::{OptionRefAny, RefAny};
25use azul_core::task::{ThreadId, ThreadReceiver};
26use azul_css::impl_option_inner; use azul_css::F32Vec;
28
29use crate::callbacks::{Callback, CallbackInfo, CallbackType};
30use crate::thread::{
31 Thread, ThreadCallback, ThreadReceiveMsg, ThreadSender, ThreadWriteBackMsg, WriteBackCallback,
32};
33
34pub type OnAudioFrameCallbackType = extern "C" fn(RefAny, CallbackInfo, AudioFrame) -> Update;
42impl_widget_callback!(
43 OnAudioFrame,
44 OptionOnAudioFrame,
45 OnAudioFrameCallback,
46 OnAudioFrameCallbackType
47);
48
49azul_core::impl_managed_callback! {
51 wrapper: OnAudioFrameCallback,
52 info_ty: CallbackInfo,
53 return_ty: Update,
54 default_ret: Update::DoNothing,
55 invoker_static: ON_AUDIO_FRAME_INVOKER,
56 invoker_ty: AzOnAudioFrameCallbackInvoker,
57 thunk_fn: az_on_audio_frame_callback_thunk,
58 setter_fn: AzApp_setOnAudioFrameCallbackInvoker,
59 from_handle_fn: AzOnAudioFrameCallback_createFromHostHandle,
60 extra_args: [ frame: AudioFrame ],
61}
62
63fn invoke_on_audio_frame(
66 hook: &OptionOnAudioFrame,
67 info: &mut CallbackInfo,
68 frame: AudioFrame,
69) -> Update {
70 match hook {
71 OptionOnAudioFrame::Some(h) => (h.callback.cb)(h.refany.clone(), info.clone(), frame),
72 OptionOnAudioFrame::None => Update::DoNothing,
73 }
74}
75
76struct MicThreadInit {
78 sample_rate: u32,
79 channels: u16,
80}
81
82pub struct MicrophoneWidgetState {
85 pub config: AudioConfig,
87 pub started: bool,
89 pub on_frame: OptionOnAudioFrame,
92}
93
94#[repr(C)]
97pub struct MicrophoneWidget {
98 pub config: AudioConfig,
100 pub on_frame: OptionOnAudioFrame,
102}
103
104impl MicrophoneWidget {
105 pub fn create(config: AudioConfig) -> Self {
107 Self {
108 config,
109 on_frame: OptionOnAudioFrame::None,
110 }
111 }
112
113 pub fn set_on_frame<C: Into<OnAudioFrameCallback>>(&mut self, data: RefAny, on_frame: C) {
117 self.on_frame = Some(OnAudioFrame {
118 refany: data,
119 callback: on_frame.into(),
120 })
121 .into();
122 }
123
124 pub fn with_on_frame<C: Into<OnAudioFrameCallback>>(
126 mut self,
127 data: RefAny,
128 on_frame: C,
129 ) -> Self {
130 self.set_on_frame(data, on_frame);
131 self
132 }
133
134 pub fn dom(self) -> Dom {
138 let state = MicrophoneWidgetState {
139 config: self.config,
140 started: false,
141 on_frame: self.on_frame,
142 };
143 let dataset = RefAny::new(state);
144
145 Dom::create_div()
146 .with_dataset(OptionRefAny::Some(dataset.clone()))
147 .with_merge_callback(merge_microphone_state as DatasetMergeCallbackType)
148 .with_callback(
149 EventFilter::Component(ComponentEventFilter::AfterMount),
150 dataset,
151 Callback::from(mic_on_after_mount as CallbackType),
152 )
153 }
154}
155
156extern "C" fn mic_on_after_mount(mut data: RefAny, mut info: CallbackInfo) -> Update {
158 let (rate, channels) = {
159 let mut s = match data.downcast_mut::<MicrophoneWidgetState>() {
160 Some(s) => s,
161 None => return Update::DoNothing,
162 };
163 if s.started {
164 return Update::DoNothing;
165 }
166 s.started = true;
167 let rate = if s.config.sample_rate > 0 {
168 s.config.sample_rate
169 } else {
170 48_000
171 };
172 let channels = s.config.channels.max(1);
173 (rate, channels)
174 };
175
176 info.add_thread(
177 ThreadId::unique(),
178 Thread::create(
179 RefAny::new(MicThreadInit {
180 sample_rate: rate,
181 channels,
182 }),
183 data.clone(),
184 ThreadCallback::new(mic_worker),
185 ),
186 );
187 Update::DoNothing
188}
189
190extern "C" fn mic_worker(mut init: RefAny, mut sender: ThreadSender, _recv: ThreadReceiver) {
194 let (rate, channels) = init
195 .downcast_ref::<MicThreadInit>()
196 .map(|i| (i.sample_rate, i.channels))
197 .unwrap_or((48_000, 1));
198
199 if let Some(backend) = mic_backend() {
202 let handle = (backend.open)(rate, channels);
203 if handle != 0 {
204 let mut buf: Vec<f32> = Vec::new();
205 loop {
206 let frames = (backend.read)(handle, &mut buf);
207 if frames == 0 {
208 break;
209 }
210 let frame = AudioFrame {
211 sample_rate: rate,
212 channels,
213 samples: F32Vec::from_vec(buf.clone()),
214 };
215 if !sender.send(ThreadReceiveMsg::WriteBack(ThreadWriteBackMsg::new(
216 WriteBackCallback::new(mic_writeback),
217 RefAny::new(frame),
218 ))) {
219 break;
220 }
221 }
222 (backend.close)(handle);
223 return;
224 }
225 }
226
227 let frames_per_chunk = (rate as usize / 50).max(1); let step = 2.0 * core::f32::consts::PI * 440.0 / rate as f32;
229 let mut phase: f32 = 0.0;
230 loop {
231 let mut samples = Vec::with_capacity(frames_per_chunk * channels as usize);
232 for _ in 0..frames_per_chunk {
233 let s = phase.sin() * 0.2;
234 phase += step;
235 if phase > 2.0 * core::f32::consts::PI {
236 phase -= 2.0 * core::f32::consts::PI;
237 }
238 for _ in 0..channels {
239 samples.push(s);
240 }
241 }
242 let frame = AudioFrame {
243 sample_rate: rate,
244 channels,
245 samples: F32Vec::from_vec(samples),
246 };
247 let sent = sender.send(ThreadReceiveMsg::WriteBack(ThreadWriteBackMsg::new(
248 WriteBackCallback::new(mic_writeback),
249 RefAny::new(frame),
250 )));
251 if !sent {
252 break;
253 }
254 std::thread::sleep(std::time::Duration::from_millis(20));
255 }
256}
257
258extern "C" fn mic_writeback(
261 mut writeback_data: RefAny,
262 mut frame_data: RefAny,
263 mut info: CallbackInfo,
264) -> Update {
265 let hook = match writeback_data.downcast_ref::<MicrophoneWidgetState>() {
266 Some(s) => s.on_frame.clone(),
267 None => return Update::DoNothing,
268 };
269 match frame_data.downcast_ref::<AudioFrame>() {
270 Some(frame) => invoke_on_audio_frame(&hook, &mut info, frame.clone()),
271 None => Update::DoNothing,
272 }
273}
274
275extern "C" fn merge_microphone_state(mut new_data: RefAny, mut old_data: RefAny) -> RefAny {
278 {
279 let new_guard = new_data.downcast_mut::<MicrophoneWidgetState>();
280 let old_guard = old_data.downcast_ref::<MicrophoneWidgetState>();
281 if let (Some(mut new_g), Some(old_g)) = (new_guard, old_guard) {
282 new_g.started = old_g.started;
283 }
284 }
285 new_data
286}