audioview/frontend-core/src/components/waveform/canvas.rs

155 lines
4.7 KiB
Rust

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<dyn Bridge<AudioAgent>>,
canvas_node: NodeRef,
audio_data: Option<Arc<AudioData>>,
cb: Option<Closure<dyn FnMut()>>,
}
#[derive(Debug)]
pub enum Msg {
Reset,
AudioAgentMessage(Result<Arc<AudioData>, 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 {
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<dyn FnMut()>);
// 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! { <canvas ref=self.canvas_node.clone() width="800" height="300" style="border: 1px solid grey"></canvas> }
}
}
impl Canvas {
fn reset(&mut self) {
self.audio_data = None;
self.redraw_canvas();
}
fn handle_samples_loaded(&mut self, audio_data: Arc<AudioData>) {
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::<HtmlCanvasElement>().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::<web_sys::CanvasRenderingContext2d>()
.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();
});
}
}
}
}