use crate::agents::audio_agent::{AudioAgent, AudioData}; use crate::utils; use std::sync::Arc; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use web_sys::HtmlCanvasElement; use yew::prelude::*; use yew::services::ConsoleService; use yew::Bridge; pub struct Canvas { props: Props, _audio_agent: Box>, canvas_node: NodeRef, audio_data: Option>, cb: Option>, } #[derive(Debug)] pub enum Msg { Reset, AudioAgentMessage(Result, String>), } #[derive(Properties, Clone, PartialEq, Debug)] pub struct Props { #[prop_or(1)] pub zoom_factor: i32, } impl Component for Canvas { type Message = Msg; type Properties = Props; fn create(props: Self::Properties, link: ComponentLink) -> Self { Self { props, _audio_agent: AudioAgent::bridge(link.callback(Msg::AudioAgentMessage)), canvas_node: NodeRef::default(), audio_data: None, cb: None, } } fn rendered(&mut self, first_render: bool) { if first_render { let cb = Closure::wrap(Box::new(|| { // ConsoleService::log(&format!("In setInterval callback")); }) as Box); // TODO: request_animation_frame() ? let window = web_sys::window().unwrap(); window .set_interval_with_callback_and_timeout_and_arguments_0( cb.as_ref().unchecked_ref(), 1000, ) .unwrap(); self.cb = Some(cb); } } fn update(&mut self, msg: Self::Message) -> ShouldRender { match msg { Msg::AudioAgentMessage(Ok(audio_data)) => self.handle_samples_loaded(audio_data), Msg::AudioAgentMessage(Err(err)) => self.handle_samples_loaded_error(&err), Msg::Reset => self.reset(), }; false } fn change(&mut self, props: Self::Properties) -> ShouldRender { if self.props.zoom_factor != props.zoom_factor { ConsoleService::log(&format!("Switch to Zoom factor: {:?}", props)); self.props.zoom_factor = props.zoom_factor; self.redraw_canvas(); } if self.props != props { self.props = props; } false } fn view(&self) -> Html { html! { } } } impl Canvas { fn reset(&mut self) { self.audio_data = None; self.redraw_canvas(); } fn handle_samples_loaded(&mut self, audio_data: Arc) { self.audio_data = Some(audio_data); self.redraw_canvas(); } fn handle_samples_loaded_error(&mut self, err: &str) { ConsoleService::error(&format!("Error loading samples: {}", err)); self.audio_data = None; self.redraw_canvas(); } fn redraw_canvas(&mut self) { let canvas_element = self.canvas_node.cast::().unwrap(); let width = canvas_element.width() as f64; let height = canvas_element.height() as f64; let context = canvas_element .get_context("2d") .unwrap() .unwrap() .dyn_into::() .unwrap(); context.set_fill_style(&"#000000".into()); context.set_stroke_style(&"#00ff00".into()); context.fill_rect(0.0, 0.0, width, height); if self.audio_data.is_some() { let audio_data = self.audio_data.as_ref().unwrap(); let num_channels = audio_data.num_channels; for chan in 0..num_channels { let channel_data = &audio_data.buffers[chan as usize]; let chunks = utils::chunks_fixed(&channel_data, canvas_element.width() as usize); chunks.enumerate().for_each(|(i, chunk)| { let max = chunk .iter() .map(|v| (v * 32767.0) as i32) .map(|v| v.abs()) .max() .unwrap(); let pc = (max as f64 / 32767.0) * 100.0; let max_height = (height / num_channels as f64).floor(); let len = (max_height / 100.0 * pc).floor(); let mid = max_height * (chan + 1) as f64 - (max_height / 2.0); context.begin_path(); context.move_to(i as f64, mid - (len / 2.0)); context.line_to(i as f64, mid + (len / 2.0)); context.stroke(); }); } } } }