use alloc::vec::Vec;
use azul_core::audio::{AudioConfig, AudioFrame};
use super::capture_common::mic_backend;
use azul_core::callbacks::Update;
use azul_core::dom::{ComponentEventFilter, DatasetMergeCallbackType, Dom, EventFilter};
use azul_core::refany::{OptionRefAny, RefAny};
use azul_core::task::{ThreadId, ThreadReceiver};
use azul_css::impl_option_inner; use azul_css::F32Vec;
use crate::callbacks::{Callback, CallbackInfo, CallbackType};
use crate::thread::{
Thread, ThreadCallback, ThreadReceiveMsg, ThreadSender, ThreadWriteBackMsg, WriteBackCallback,
};
pub type OnAudioFrameCallbackType = extern "C" fn(RefAny, CallbackInfo, AudioFrame) -> Update;
impl_widget_callback!(
OnAudioFrame,
OptionOnAudioFrame,
OnAudioFrameCallback,
OnAudioFrameCallbackType
);
azul_core::impl_managed_callback! {
wrapper: OnAudioFrameCallback,
info_ty: CallbackInfo,
return_ty: Update,
default_ret: Update::DoNothing,
invoker_static: ON_AUDIO_FRAME_INVOKER,
invoker_ty: AzOnAudioFrameCallbackInvoker,
thunk_fn: az_on_audio_frame_callback_thunk,
setter_fn: AzApp_setOnAudioFrameCallbackInvoker,
from_handle_fn: AzOnAudioFrameCallback_createFromHostHandle,
extra_args: [ frame: AudioFrame ],
}
fn invoke_on_audio_frame(
hook: &OptionOnAudioFrame,
info: &mut CallbackInfo,
frame: AudioFrame,
) -> Update {
match hook {
OptionOnAudioFrame::Some(h) => (h.callback.cb)(h.refany.clone(), info.clone(), frame),
OptionOnAudioFrame::None => Update::DoNothing,
}
}
struct MicThreadInit {
sample_rate: u32,
channels: u16,
}
pub struct MicrophoneWidgetState {
pub config: AudioConfig,
pub started: bool,
pub on_frame: OptionOnAudioFrame,
}
#[repr(C)]
pub struct MicrophoneWidget {
pub config: AudioConfig,
pub on_frame: OptionOnAudioFrame,
}
impl MicrophoneWidget {
pub fn create(config: AudioConfig) -> Self {
Self {
config,
on_frame: OptionOnAudioFrame::None,
}
}
pub fn set_on_frame<C: Into<OnAudioFrameCallback>>(&mut self, data: RefAny, on_frame: C) {
self.on_frame = Some(OnAudioFrame {
refany: data,
callback: on_frame.into(),
})
.into();
}
pub fn with_on_frame<C: Into<OnAudioFrameCallback>>(
mut self,
data: RefAny,
on_frame: C,
) -> Self {
self.set_on_frame(data, on_frame);
self
}
pub fn dom(self) -> Dom {
let state = MicrophoneWidgetState {
config: self.config,
started: false,
on_frame: self.on_frame,
};
let dataset = RefAny::new(state);
Dom::create_div()
.with_dataset(OptionRefAny::Some(dataset.clone()))
.with_merge_callback(merge_microphone_state as DatasetMergeCallbackType)
.with_callback(
EventFilter::Component(ComponentEventFilter::AfterMount),
dataset,
Callback::from(mic_on_after_mount as CallbackType),
)
}
}
extern "C" fn mic_on_after_mount(mut data: RefAny, mut info: CallbackInfo) -> Update {
let (rate, channels) = {
let mut s = match data.downcast_mut::<MicrophoneWidgetState>() {
Some(s) => s,
None => return Update::DoNothing,
};
if s.started {
return Update::DoNothing;
}
s.started = true;
let rate = if s.config.sample_rate > 0 {
s.config.sample_rate
} else {
48_000
};
let channels = s.config.channels.max(1);
(rate, channels)
};
info.add_thread(
ThreadId::unique(),
Thread::create(
RefAny::new(MicThreadInit {
sample_rate: rate,
channels,
}),
data.clone(),
ThreadCallback::new(mic_worker),
),
);
Update::DoNothing
}
extern "C" fn mic_worker(mut init: RefAny, mut sender: ThreadSender, _recv: ThreadReceiver) {
let (rate, channels) = init
.downcast_ref::<MicThreadInit>()
.map(|i| (i.sample_rate, i.channels))
.unwrap_or((48_000, 1));
if let Some(backend) = mic_backend() {
let handle = (backend.open)(rate, channels);
if handle != 0 {
let mut buf: Vec<f32> = Vec::new();
loop {
let frames = (backend.read)(handle, &mut buf);
if frames == 0 {
break;
}
let frame = AudioFrame {
sample_rate: rate,
channels,
samples: F32Vec::from_vec(buf.clone()),
};
if !sender.send(ThreadReceiveMsg::WriteBack(ThreadWriteBackMsg::new(
WriteBackCallback::new(mic_writeback),
RefAny::new(frame),
))) {
break;
}
}
(backend.close)(handle);
return;
}
}
let frames_per_chunk = (rate as usize / 50).max(1); let step = 2.0 * core::f32::consts::PI * 440.0 / rate as f32;
let mut phase: f32 = 0.0;
loop {
let mut samples = Vec::with_capacity(frames_per_chunk * channels as usize);
for _ in 0..frames_per_chunk {
let s = phase.sin() * 0.2;
phase += step;
if phase > 2.0 * core::f32::consts::PI {
phase -= 2.0 * core::f32::consts::PI;
}
for _ in 0..channels {
samples.push(s);
}
}
let frame = AudioFrame {
sample_rate: rate,
channels,
samples: F32Vec::from_vec(samples),
};
let sent = sender.send(ThreadReceiveMsg::WriteBack(ThreadWriteBackMsg::new(
WriteBackCallback::new(mic_writeback),
RefAny::new(frame),
)));
if !sent {
break;
}
std::thread::sleep(std::time::Duration::from_millis(20));
}
}
extern "C" fn mic_writeback(
mut writeback_data: RefAny,
mut frame_data: RefAny,
mut info: CallbackInfo,
) -> Update {
let hook = match writeback_data.downcast_ref::<MicrophoneWidgetState>() {
Some(s) => s.on_frame.clone(),
None => return Update::DoNothing,
};
match frame_data.downcast_ref::<AudioFrame>() {
Some(frame) => invoke_on_audio_frame(&hook, &mut info, frame.clone()),
None => Update::DoNothing,
}
}
extern "C" fn merge_microphone_state(mut new_data: RefAny, mut old_data: RefAny) -> RefAny {
{
let new_guard = new_data.downcast_mut::<MicrophoneWidgetState>();
let old_guard = old_data.downcast_ref::<MicrophoneWidgetState>();
if let (Some(mut new_g), Some(old_g)) = (new_guard, old_guard) {
new_g.started = old_g.started;
}
}
new_data
}