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;
44impl_widget_callback!(
45 OnAudioFrame,
46 OptionOnAudioFrame,
47 OnAudioFrameCallback,
48 OnAudioFrameCallbackType
49);
50
51azul_core::impl_managed_callback! {
53 wrapper: OnAudioFrameCallback,
54 info_ty: CallbackInfo,
55 return_ty: Update,
56 default_ret: Update::DoNothing,
57 invoker_static: ON_AUDIO_FRAME_INVOKER,
58 invoker_ty: AzOnAudioFrameCallbackInvoker,
59 thunk_fn: az_on_audio_frame_callback_thunk,
60 setter_fn: AzApp_setOnAudioFrameCallbackInvoker,
61 from_handle_fn: AzOnAudioFrameCallback_createFromHostHandle,
62 extra_args: [ frame: AudioFrame ],
63}
64
65fn invoke_on_audio_frame(
68 hook: &OptionOnAudioFrame,
69 info: &CallbackInfo,
70 frame: AudioFrame,
71) -> Update {
72 match hook {
73 OptionOnAudioFrame::Some(h) => (h.callback.cb)(h.refany.clone(), *info, frame),
74 OptionOnAudioFrame::None => Update::DoNothing,
75 }
76}
77
78struct MicThreadInit {
80 sample_rate: u32,
81 channels: u16,
82}
83
84#[derive(Debug)]
87pub struct MicrophoneWidgetState {
88 pub config: AudioConfig,
90 pub started: bool,
92 pub on_frame: OptionOnAudioFrame,
95}
96
97#[repr(C)]
100#[derive(Debug)]
101pub struct MicrophoneWidget {
102 pub config: AudioConfig,
104 pub on_frame: OptionOnAudioFrame,
106}
107
108impl MicrophoneWidget {
109 #[must_use] pub const fn create(config: AudioConfig) -> Self {
111 Self {
112 config,
113 on_frame: OptionOnAudioFrame::None,
114 }
115 }
116
117 pub fn set_on_frame<C: Into<OnAudioFrameCallback>>(&mut self, data: RefAny, on_frame: C) {
121 self.on_frame = Some(OnAudioFrame {
122 refany: data,
123 callback: on_frame.into(),
124 })
125 .into();
126 }
127
128 #[must_use]
130 pub fn with_on_frame<C: Into<OnAudioFrameCallback>>(
131 mut self,
132 data: RefAny,
133 on_frame: C,
134 ) -> Self {
135 self.set_on_frame(data, on_frame);
136 self
137 }
138
139 #[must_use] pub fn dom(self) -> Dom {
143 let state = MicrophoneWidgetState {
144 config: self.config,
145 started: false,
146 on_frame: self.on_frame,
147 };
148 let dataset = RefAny::new(state);
149
150 Dom::create_div()
151 .with_dataset(OptionRefAny::Some(dataset.clone()))
152 .with_merge_callback(azul_core::dom::DatasetMergeCallback::from_ptr(merge_microphone_state))
153 .with_callback(
154 EventFilter::Component(ComponentEventFilter::AfterMount),
155 dataset,
156 Callback::from_ptr(mic_on_after_mount),
157 )
158 }
159}
160
161extern "C" fn mic_on_after_mount(mut data: RefAny, mut info: CallbackInfo) -> Update {
163 let (rate, channels) = {
164 let Some(mut s) = data.downcast_mut::<MicrophoneWidgetState>() else {
165 return Update::DoNothing;
166 };
167 if s.started {
168 return Update::DoNothing;
169 }
170 s.started = true;
171 let rate = if s.config.sample_rate > 0 {
172 s.config.sample_rate
173 } else {
174 48_000
175 };
176 let channels = s.config.channels.max(1);
177 (rate, channels)
178 };
179
180 info.add_thread(
181 ThreadId::unique(),
182 Thread::create(
183 RefAny::new(MicThreadInit {
184 sample_rate: rate,
185 channels,
186 }),
187 data.clone(),
188 ThreadCallback::new(mic_worker),
189 ),
190 );
191 Update::DoNothing
192}
193
194#[allow(clippy::cast_precision_loss)] extern "C" fn mic_worker(mut init: RefAny, mut sender: ThreadSender, _recv: ThreadReceiver) {
199 let (rate, channels) = init
200 .downcast_ref::<MicThreadInit>()
201 .map_or((48_000, 1), |i| (i.sample_rate, i.channels));
202
203 if let Some(backend) = mic_backend() {
206 let handle = (backend.open)(rate, channels);
207 if handle != 0 {
208 let mut buf: Vec<f32> = Vec::new();
209 loop {
210 let frames = (backend.read)(handle, &mut buf);
211 if frames == 0 {
212 break;
213 }
214 let frame = AudioFrame {
215 sample_rate: rate,
216 channels,
217 samples: F32Vec::from_vec(buf.clone()),
218 };
219 if !sender.send(ThreadReceiveMsg::WriteBack(ThreadWriteBackMsg::new(
220 WriteBackCallback::new(mic_writeback),
221 RefAny::new(frame),
222 ))) {
223 break;
224 }
225 }
226 (backend.close)(handle);
227 return;
228 }
229 }
230
231 let frames_per_chunk = (rate as usize / 50).max(1); let step = 2.0 * core::f32::consts::PI * 440.0 / rate as f32;
233 let mut phase: f32 = 0.0;
234 loop {
235 let mut samples = Vec::with_capacity(frames_per_chunk * channels as usize);
236 for _ in 0..frames_per_chunk {
237 let s = phase.sin() * 0.2;
238 phase += step;
239 if phase > 2.0 * core::f32::consts::PI {
240 phase -= 2.0 * core::f32::consts::PI;
241 }
242 for _ in 0..channels {
243 samples.push(s);
244 }
245 }
246 let frame = AudioFrame {
247 sample_rate: rate,
248 channels,
249 samples: F32Vec::from_vec(samples),
250 };
251 let sent = sender.send(ThreadReceiveMsg::WriteBack(ThreadWriteBackMsg::new(
252 WriteBackCallback::new(mic_writeback),
253 RefAny::new(frame),
254 )));
255 if !sent {
256 break;
257 }
258 std::thread::sleep(std::time::Duration::from_millis(20));
259 }
260}
261
262extern "C" fn mic_writeback(
265 mut writeback_data: RefAny,
266 mut frame_data: RefAny,
267 info: CallbackInfo,
268) -> Update {
269 let hook = match writeback_data.downcast_ref::<MicrophoneWidgetState>() {
270 Some(s) => s.on_frame.clone(),
271 None => return Update::DoNothing,
272 };
273 frame_data.downcast_ref::<AudioFrame>().map_or(Update::DoNothing, |frame| invoke_on_audio_frame(&hook, &info, frame.clone()))
274}
275
276extern "C" fn merge_microphone_state(mut new_data: RefAny, mut old_data: RefAny) -> RefAny {
279 {
280 let new_guard = new_data.downcast_mut::<MicrophoneWidgetState>();
281 let old_guard = old_data.downcast_ref::<MicrophoneWidgetState>();
282 if let (Some(mut new_g), Some(old_g)) = (new_guard, old_guard) {
283 new_g.started = old_g.started;
284 }
285 }
286 new_data
287}