diff --git a/.DS_Store b/.DS_Store index 346ea5c..45267d7 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000..e69de29 diff --git a/Cargo.lock b/Cargo.lock index c48aa75..271928d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1694,6 +1694,7 @@ dependencies = [ "puffin", "puffin_http", "rayon", + "realfft", "ruhear", "rustfft", "serde", @@ -2152,6 +2153,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "realfft" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953d9f7e5cdd80963547b456251296efc2626ed4e3cbf36c869d9564e0220571" +dependencies = [ + "rustfft", +] + [[package]] name = "redox_syscall" version = "0.3.5" diff --git a/Cargo.toml b/Cargo.toml index 5431671..b9c9e6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,21 +9,22 @@ rust-version = "1.78" [dependencies] egui = {version = "0.27.2", features = ["rayon"]} eframe = { version = "0.27.2", default-features = false, features = [ - "default_fonts", + "default_fonts", "glow", "wgpu", "persistence", "puffin", ] } serde = { version = "1", features = ["derive"] } -cpal = {version = "0.15.3", exclude = ["js-sys"]} interprocess = "1.2.1" env_logger = { version = "0.11", default-features = false, features = [ "auto-color", "humantime", ] } +cpal = {version = "0.15.3", exclude = ["js-sys"]} anyhow = "1.0" rustfft = "6.2.0" +realfft = "3.3.0" ruhear = "0.1.0" log = "0.4" rayon = "1.10" diff --git a/assets/favicon.ico b/assets/favicon.ico old mode 100755 new mode 100644 index 61ad031..2f568e4 Binary files a/assets/favicon.ico and b/assets/favicon.ico differ diff --git a/assets/icon-1024.png b/assets/icon-1024.png index 6c4eaa3..860ad67 100644 Binary files a/assets/icon-1024.png and b/assets/icon-1024.png differ diff --git a/assets/icon-256.png b/assets/icon-256.png index 1776e0f..872e862 100644 Binary files a/assets/icon-256.png and b/assets/icon-256.png differ diff --git a/assets/icon_ios_touch_192.png b/assets/icon_ios_touch_192.png index 8472802..297ea09 100644 Binary files a/assets/icon_ios_touch_192.png and b/assets/icon_ios_touch_192.png differ diff --git a/assets/maskable_icon_x512.png b/assets/maskable_icon_x512.png index db8df3e..5194856 100644 Binary files a/assets/maskable_icon_x512.png and b/assets/maskable_icon_x512.png differ diff --git a/rust-toolchain b/rust-toolchain index 42100f1..78ddd95 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -5,5 +5,5 @@ # to the user in the error, instead of "error: invalid channel name '[toolchain]'". [toolchain] -channel = "1.78.0" +channel = "nightly" components = [ "rustfmt", "clippy" ] diff --git a/src/app.rs b/src/app.rs index a17db0f..aa1ea38 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3,12 +3,11 @@ use crate::audio::*; use crate::frame::*; use crate::setting::*; use crate::utils::*; -use crate::AudioSource; -use crate::RingBuffer; use crossbeam_channel::unbounded; use crossbeam_channel::{Receiver, Sender}; use eframe::egui::{self, ViewportCommand}; +use eframe::wgpu::core::storage; use eframe::wgpu::rwh::HasWindowHandle; use egui::*; use rayon::prelude::*; @@ -26,10 +25,15 @@ pub struct NanometersApp { pub(crate) frame_history: FrameHistory, #[serde(skip)] - pub(crate) tx_lrms: Option>, + pub(crate) tx: Option>, #[serde(skip)] - pub(crate) rx_lrms: Option>, + pub(crate) rx: Option>, + + #[serde(skip)] + pub(crate) audio_source_buffer: Arc>, + + pub(crate) color_lut_129: Vec, pub(crate) setting: Setting, @@ -44,31 +48,20 @@ pub struct NanometersApp { impl Default for NanometersApp { fn default() -> Self { - let (tx_lrms, rx_lrms) = unbounded(); - let tx_lrms_save = Some(tx_lrms.clone()); - let rx_lrms_save = Some(rx_lrms.clone()); - let callback = Box::new(move |data: Vec>| { - #[cfg(feature = "puffin")] - puffin::profile_scope!("callback"); - let mut send_data = RawData::new(); - data[0].iter().zip(&data[1]).for_each(|(l, r)| { - send_data.push_l(*l); - send_data.push_r(*r); - send_data.push_m((l + r) / 2.0); - send_data.push_s((l - r) / 2.0); - }); - tx_lrms.send(send_data).unwrap(); - }); - - let mut system_capture = SystemCapture::new(callback); + let (tx, rx) = unbounded(); + let audio_source_buffer = Arc::new(Mutex::new(AudioSourceBuffer::new())); + let mut system_capture = + SystemCapture::new(get_callback(tx.clone(), audio_source_buffer.clone())); system_capture.start(); let audio_source = Some(Box::new(system_capture) as Box); Self { audio_source, frame_history: Default::default(), - tx_lrms: tx_lrms_save, - rx_lrms: rx_lrms_save, + tx: Some(tx), + rx: Some(rx), + audio_source_buffer, + color_lut_129: color_lut_129(), setting: Default::default(), setting_switch: false, allways_on_top: false, @@ -87,7 +80,12 @@ impl NanometersApp { // `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`. if let Some(storage) = cc.storage { - return eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default(); + let mut app: NanometersApp = + eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default(); + cc.egui_ctx.set_visuals(set_theme(&mut app)); + // match app.setting.audio_device.device {} + + return app; } Default::default() } diff --git a/src/audio.rs b/src/audio.rs index 458c3cf..9f3e2e3 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -1,7 +1,11 @@ +pub(crate) mod audio_source; +pub(crate) mod callback; pub(crate) mod plugin_client; pub(crate) mod system_capture; pub(crate) mod system_input; +pub use audio_source::*; +pub use callback::*; pub use plugin_client::*; pub use system_capture::*; pub use system_input::*; diff --git a/src/audio/audio_source.rs b/src/audio/audio_source.rs new file mode 100644 index 0000000..ffb42e8 --- /dev/null +++ b/src/audio/audio_source.rs @@ -0,0 +1,5 @@ +pub trait AudioSource { + fn get_name(&self) -> String; + fn start(&mut self); + fn stop(&mut self); +} diff --git a/src/audio/callback.rs b/src/audio/callback.rs new file mode 100644 index 0000000..808fbc1 --- /dev/null +++ b/src/audio/callback.rs @@ -0,0 +1,103 @@ +use realfft::RealFftPlanner; + +use std::sync::{Arc, Mutex}; + +use crate::utils::*; +use crossbeam_channel::Sender; +// use std::collections::VecDeque; +// use std::sync::{Arc, Mutex}; + +pub fn get_callback( + tx_lrms: Sender, + buffer: Arc>, +) -> Box>) + Send + Sync> { + Box::new(move |data: Vec>| { + #[cfg(feature = "puffin")] + puffin::profile_scope!("callback"); + + let mut buffer = buffer.lock().unwrap(); + let block_length = 256; + + let mut send_data = SendData::new(); + let len = data[0].len(); + let mut amp_l = 0.0; + let mut amp_r = 0.0; + for i in 0..len { + // Waveform + buffer.waveform.update_l(data[0][i]); + buffer.waveform.update_r(data[1][i]); + buffer.waveform.update_m((data[0][i] + data[1][i]) / 2.0); + buffer.waveform.update_s((data[0][i] - data[1][i]) / 2.0); + buffer.waveform.index += 1; + if buffer.waveform.index >= block_length { + let mut waveform_buffer = buffer.waveform.clone(); + buffer.waveform.reset(); + send_data.waveform_data.l.push(waveform_buffer.l); + send_data.waveform_data.r.push(waveform_buffer.r); + send_data.waveform_data.m.push(waveform_buffer.m); + send_data.waveform_data.s.push(waveform_buffer.s); + + let mut real_planner = RealFftPlanner::::new(); + let r2c = real_planner.plan_fft_forward(block_length); + let mut spectrum = r2c.make_output_vec(); + r2c.process(&mut waveform_buffer.raw.l, &mut spectrum) + .unwrap(); + send_data + .waveform_data + .l_freq + .push(max_index(spectrum.iter().map(|x| x.norm()).collect())); + r2c.process(&mut waveform_buffer.raw.r, &mut spectrum) + .unwrap(); + send_data + .waveform_data + .r_freq + .push(max_index(spectrum.iter().map(|x| x.norm()).collect())); + r2c.process(&mut waveform_buffer.raw.m, &mut spectrum) + .unwrap(); + send_data + .waveform_data + .m_freq + .push(max_index(spectrum.iter().map(|x| x.norm()).collect())); + r2c.process(&mut waveform_buffer.raw.s, &mut spectrum) + .unwrap(); + send_data + .waveform_data + .s_freq + .push(max_index(spectrum.iter().map(|x| x.norm()).collect())); + } + + // Peak + // DB + amp_l += data[0][i].abs(); + amp_r += data[1][i].abs(); + //IIR + let iir_l = combined_filter(data[0][i], &mut buffer.peak.iir_l); + let iir_r = combined_filter(data[1][i], &mut buffer.peak.iir_r); + buffer.peak.sum_l += iir_l * iir_l; + buffer.peak.sum_r += iir_r * iir_r; + buffer.peak.index += 1; + if buffer.peak.index >= 4800 { + let peak_buffer = buffer.peak.clone(); + buffer.peak.reset_sum(); + send_data.iir_data.l.push(peak_buffer.sum_l); + send_data.iir_data.r.push(peak_buffer.sum_r); + } + } + + //DB + amp_l /= len as f32; + amp_r /= len as f32; + send_data.db_data.l = gain_to_db(amp_l); + send_data.db_data.r = gain_to_db(amp_r); + + tx_lrms.send(send_data).unwrap(); + }) +} + +fn max_index(data: Vec) -> usize { + data.iter() + .enumerate() + .max_by(|&(_, a), &(_, b)| a.partial_cmp(b).unwrap()) + .map(|(index, _)| index) + .unwrap_or(0) +} diff --git a/src/audio/plugin_client.rs b/src/audio/plugin_client.rs index e61daeb..90ad382 100644 --- a/src/audio/plugin_client.rs +++ b/src/audio/plugin_client.rs @@ -1,4 +1,4 @@ -use crate::AudioSource; +use crate::audio::AudioSource; use interprocess::local_socket::{LocalSocketStream, NameTypeSupport}; use std::io::Read; use std::sync::mpsc; @@ -69,7 +69,7 @@ impl AudioSource for PluginClient { continue; } }; - let mut update_buf: Vec = Vec::new(); + let mut update_buf; match conn.read(&mut buf) { Ok(_) => { let buffer = buf diff --git a/src/audio/system_capture.rs b/src/audio/system_capture.rs index c9288ed..4e84eaf 100644 --- a/src/audio/system_capture.rs +++ b/src/audio/system_capture.rs @@ -1,11 +1,10 @@ -use crate::AudioSource; +use crate::audio::AudioSource; use ruhear::RUHear; use std::sync::{Arc, Mutex}; pub struct SystemCapture { name: String, ruhear: RUHear, - callback: Arc>) + Send>>, } impl SystemCapture { @@ -13,11 +12,7 @@ impl SystemCapture { let name = "System Default Output".to_string(); let callback = Arc::new(Mutex::new(callback)); let ruhear = RUHear::new(callback.clone()); - Self { - name, - ruhear, - callback, - } + Self { name, ruhear } } } diff --git a/src/audio/system_input.rs b/src/audio/system_input.rs index f898db2..ce9f8a3 100644 --- a/src/audio/system_input.rs +++ b/src/audio/system_input.rs @@ -1,17 +1,79 @@ #![allow(unused)] -// use crate::AudioSource; +use crate::audio::AudioSource; // use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use std::sync::{Arc, Mutex}; pub struct SystemInput { name: String, callback: Arc>) + Send>>, - stream: cpal::Stream, + host: cpal::Host, + device: cpal::Device, + format: cpal::SupportedStreamConfig, + stream: Option, } -// impl SystemInput { -// pub fn new(callback: Box>) + Send>) -> Self { -// let name = "System Microphone".to_string(); +impl SystemInput { + pub fn new(callback: Box>) + Send>) -> Self { + let name = "System Default Microphone".to_string(); + let host = cpal::default_host(); + let device = host.default_input_device().unwrap(); + let format = device.default_input_config().unwrap(); + Self { + name, + callback: Arc::new(Mutex::new(callback)), + host, + device, + format, + stream: None, + } + } +} + +impl AudioSource for SystemInput { + fn get_name(&self) -> String { + self.name.clone() + } -// } -// } + fn start(&mut self) { + if self.stream.is_none() { + let callback = self.callback.clone(); + let channels = &self.format.channels().clone(); + let channels = *channels as usize; + let stream = match self.format.sample_format() { + cpal::SampleFormat::F32 => self.device.build_input_stream( + &self.format.config(), + move |data: &[f32], &_| { + let mut bufs = vec![vec![]; channels]; + for (i, sample) in data.chunks(channels).enumerate() { + for (j, &channel) in sample.iter().enumerate() { + bufs[j].push(channel as f32); + } + } + if let Ok(mut callback) = callback.lock() { + (*callback)(bufs); + } + }, + |err| { + eprintln!("an error occurred on stream: {}", err); + }, + None, + ), + sample_format => { + panic!("unsupported format {:?}", sample_format); + } + } + .unwrap(); + self.stream = Some(stream); + } + if let Some(stream) = &self.stream { + stream.play(); + } + } + + fn stop(&mut self) { + if let Some(stream) = &self.stream { + stream.pause().unwrap(); + } + } +} diff --git a/src/frame/main_frame.rs b/src/frame/main_frame.rs index 11a4780..ebb9bba 100644 --- a/src/frame/main_frame.rs +++ b/src/frame/main_frame.rs @@ -67,27 +67,30 @@ impl NanometersApp { ); } - let mut update_data = RawData::new(); - self.rx_lrms.as_mut().unwrap().try_iter().for_each(|data| { - update_data.extend_l(data.l.as_slice()); - update_data.extend_r(data.r.as_slice()); - update_data.extend_m(data.m.as_slice()); - update_data.extend_s(data.s.as_slice()); + let mut update_waveform_data = WaveformSendData::new(); + let mut update_iir_data = IIRData::new(); + let mut update_db_data = DBData::new(); + + self.rx.as_mut().unwrap().try_iter().for_each(|data| { + update_iir_data.concat(&data.iir_data); + update_db_data.l = data.db_data.l; + update_db_data.r = data.db_data.r; + update_waveform_data.concat(&data.waveform_data); }); - // println!("{:?}", update_data.l); + ui.ctx().request_repaint(); for (i, meter) in self.setting.sequence[1].clone().iter().enumerate() { let mut meter_rect = self.meters_rects[i]; match meter { ModuleList::Waveform => { - self.waveform_frame(update_data.clone(), meter_rect, ui); + self.waveform_frame(&update_waveform_data, meter_rect, ui); } ModuleList::Spectrogram => { self.spectrogram_frame(meter_rect, ui); } ModuleList::Peak => { - self.peak_frame(&update_data, meter_rect, ui); + self.peak_frame(&update_iir_data, &update_db_data, meter_rect, ui); } ModuleList::Oscilloscope => { self.oscilloscope_frame(meter_rect, ui); diff --git a/src/frame/peak_frame.rs b/src/frame/peak_frame.rs index 4ad4b32..40910ac 100644 --- a/src/frame/peak_frame.rs +++ b/src/frame/peak_frame.rs @@ -2,14 +2,11 @@ use crate::setting::*; use crate::utils::*; use crate::NanometersApp; use egui::*; -use rayon::iter::IntoParallelRefIterator; -use rayon::iter::ParallelIterator; -use rustfft::num_traits::Pow; impl NanometersApp { - pub fn peak_frame(&mut self, data: &RawData, rect: Rect, ui: &mut Ui) { + pub fn peak_frame(&mut self, iir_data: &IIRData, db_data: &DBData, rect: Rect, ui: &mut Ui) { ui.painter().rect_filled(rect, 0.0, self.setting.theme.bg); - if data.l.is_empty() || data.r.is_empty() { + if iir_data.l.is_empty() || iir_data.r.is_empty() { let l_rect = Rect::from_two_pos( pos2(rect.center().x - 12.0, self.peak.plot_l), pos2(rect.center().x - 7.0, rect.max.y), @@ -23,36 +20,22 @@ impl NanometersApp { ui.painter() .rect_filled(r_rect, 0.0, self.setting.theme.main); } else { - let average_l = (data - .l - .par_iter() - .fold(|| 0f32, |a, b| a + b.pow(2.0)) - .sum::() - / data.l.len() as f32) - .sqrt(); - let average_r = (data - .r - .par_iter() - .fold(|| 0f32, |a, b| a + b.pow(2.0)) - .sum::() - / data.r.len() as f32) - .sqrt(); - - if average_l > self.peak.l || self.peak.l.is_nan() { - self.peak.l = average_l; + if db_data.l > self.peak.l || self.peak.l.is_nan() { + self.peak.l = db_data.l; } else { self.peak.l = self.peak.l * self.setting.peak.decay - + (1.0 - self.setting.peak.decay) * average_l; + + (1.0 - self.setting.peak.decay) * db_data.l; } - if average_r > self.peak.r || self.peak.r.is_nan() { - self.peak.r = average_r; + if db_data.r > self.peak.r || self.peak.r.is_nan() { + self.peak.r = db_data.r; } else { self.peak.r = self.peak.r * self.setting.peak.decay - + (1.0 - self.setting.peak.decay) * average_r; + + (1.0 - self.setting.peak.decay) * db_data.r; } - let l_height = -rect.height() * (gain_to_db(self.peak.l) / 60.0); - let r_height = -rect.height() * (gain_to_db(self.peak.r) / 60.0); + + let l_height = -rect.height() * (db_data.l / 60.0); + let r_height = -rect.height() * (db_data.r / 60.0); self.peak.plot_l = l_height; self.peak.plot_r = r_height; @@ -69,5 +52,45 @@ impl NanometersApp { ui.painter() .rect_filled(r_rect, 0.0, self.setting.theme.main); } + + // LUFS + if !iir_data.l.is_empty() && !iir_data.r.is_empty() { + let len = iir_data.l.len(); + for i in 0..len { + self.peak.data_buffer_l.push_back(iir_data.l[i]); + self.peak.data_buffer_r.push_back(iir_data.r[i]); + if self.peak.data_buffer_l.len() >= 4 { + let sigma = (self.peak.data_buffer_l.iter().sum::() + + self.peak.data_buffer_r.iter().sum::()) + / 19200.0; + if sigma.log10() * 10.0 - 0.691 > -70.0 { + self.peak.past_3s.push_back(sigma); + } else { + self.peak.past_3s.push_back(0.0); + } + self.peak.past_3s.pop_front(); + self.peak.data_buffer_l.pop_front(); + self.peak.data_buffer_r.pop_front(); + } + } + } + self.peak.lufs = self + .peak + .past_3s + .clone() + .into_iter() + .filter(|x| *x != 0.0) + .sum::() + .log10() + * 10.0 + - 10.691; + // println!("{}", self.peak.lufs); + // ui.painter().text( + // rect.center(), + // Align2::CENTER_CENTER, + // format!("{}", self.peak.lufs), + // FontId::proportional(20.0), + // Color32::WHITE, + // ); } } diff --git a/src/frame/setting_frame.rs b/src/frame/setting_frame.rs index d06d144..0dfbae7 100644 --- a/src/frame/setting_frame.rs +++ b/src/frame/setting_frame.rs @@ -1,7 +1,6 @@ -use crate::audio::{PluginClient, SystemCapture}; -use crate::setting; +use crate::audio::*; +use crate::setting::{self, set_theme}; use crate::utils::*; -use crate::AudioSource; use crate::NanometersApp; use egui::style::{Selection, WidgetVisuals, Widgets}; use egui::*; @@ -206,17 +205,8 @@ impl NanometersApp { .changed() { self.audio_source.as_mut().unwrap().stop(); - let tx_lrms = self.tx_lrms.clone().unwrap(); - let callback = Box::new(move |data: Vec>| { - let mut send_data = RawData::new(); - data[0].iter().zip(data[1].iter()).for_each(|(l, r)| { - send_data.push_l(*l); - send_data.push_r(*r); - send_data.push_m((l + r) / 2.0); - send_data.push_s((l - r) / 2.0); - }); - tx_lrms.send(send_data).unwrap(); - }); + let tx_lrms = self.tx.clone().unwrap(); + let callback = get_callback(tx_lrms, self.audio_source_buffer.clone()); let mut system_capture = SystemCapture::new(callback); system_capture.start(); self.audio_source = Some(Box::new(system_capture) as Box); @@ -230,17 +220,8 @@ impl NanometersApp { .changed() { self.audio_source.as_mut().unwrap().stop(); - let tx_lrms = self.tx_lrms.clone().unwrap(); - let callback = Box::new(move |data: Vec>| { - let mut send_data = RawData::new(); - data[0].iter().zip(data[1].iter()).for_each(|(l, r)| { - send_data.push_l(*l); - send_data.push_r(*r); - send_data.push_m((l + r) / 2.0); - send_data.push_s((l - r) / 2.0); - }); - tx_lrms.send(send_data).unwrap(); - }); + let tx_lrms = self.tx.clone().unwrap(); + let callback = get_callback(tx_lrms, self.audio_source_buffer.clone()); let mut plugin_client = PluginClient::new(callback); plugin_client.start(); self.audio_source = Some(Box::new(plugin_client) as Box); @@ -597,171 +578,21 @@ impl NanometersApp { .changed() { self.setting.theme = setting::DARK_THEME; - ui.ctx().set_visuals(Visuals { - dark_mode: true, - override_text_color: Some(self.setting.theme.text), - selection: Selection { - bg_fill: self.setting.theme.selection, - stroke: Stroke::NONE, - }, - widgets: Widgets { - noninteractive: WidgetVisuals { - bg_fill: self.setting.theme.bgaccent, - weak_bg_fill: self.setting.theme.bgaccent, - bg_stroke: Stroke::new(1.0, self.setting.theme.frame), - rounding: 0.0.into(), - fg_stroke: Stroke::NONE, - expansion: 0.0, - }, - inactive: WidgetVisuals { - bg_fill: self.setting.theme.bgaccent, - weak_bg_fill: self.setting.theme.bgaccent, - bg_stroke: Stroke::NONE, - rounding: 0.0.into(), - fg_stroke: Stroke::NONE, - expansion: 0.0, - }, - active: WidgetVisuals { - bg_fill: self.setting.theme.selection, - weak_bg_fill: self.setting.theme.selection, - bg_stroke: Stroke::NONE, - rounding: 0.0.into(), - fg_stroke: Stroke::NONE, - expansion: 0.0, - }, - hovered: WidgetVisuals { - bg_fill: self.setting.theme.selection, - weak_bg_fill: self.setting.theme.selection, - bg_stroke: Stroke::NONE, - rounding: 0.0.into(), - fg_stroke: Stroke::new(1.0, self.setting.theme.text), - expansion: 0.0, - }, - open: WidgetVisuals { - bg_fill: self.setting.theme.bg, - weak_bg_fill: self.setting.theme.bgaccent, - bg_stroke: Stroke::NONE, - rounding: 0.0.into(), - fg_stroke: Stroke::NONE, - expansion: 0.0, - }, - }, - ..Default::default() - }); + ui.ctx().set_visuals(set_theme(self)); }; if ui .selectable_value(&mut self.setting.theme, setting::LIGHT_THEME, "Light") .changed() { self.setting.theme = setting::LIGHT_THEME; - ui.ctx().set_visuals(Visuals { - dark_mode: false, - override_text_color: Some(self.setting.theme.text), - selection: Selection { - bg_fill: self.setting.theme.selection, - stroke: Stroke::NONE, - }, - widgets: Widgets { - noninteractive: WidgetVisuals { - bg_fill: self.setting.theme.bgaccent, - weak_bg_fill: self.setting.theme.bgaccent, - bg_stroke: Stroke::new(1.0, self.setting.theme.frame), - rounding: 0.0.into(), - fg_stroke: Stroke::NONE, - expansion: 0.0, - }, - inactive: WidgetVisuals { - bg_fill: self.setting.theme.bgaccent, - weak_bg_fill: self.setting.theme.bgaccent, - bg_stroke: Stroke::NONE, - rounding: 0.0.into(), - fg_stroke: Stroke::NONE, - expansion: 0.0, - }, - active: WidgetVisuals { - bg_fill: self.setting.theme.selection, - weak_bg_fill: self.setting.theme.selection, - bg_stroke: Stroke::NONE, - rounding: 0.0.into(), - fg_stroke: Stroke::NONE, - expansion: 0.0, - }, - hovered: WidgetVisuals { - bg_fill: self.setting.theme.selection, - weak_bg_fill: self.setting.theme.selection, - bg_stroke: Stroke::NONE, - rounding: 0.0.into(), - fg_stroke: Stroke::new(1.0, self.setting.theme.text), - expansion: 0.0, - }, - open: WidgetVisuals { - bg_fill: self.setting.theme.bg, - weak_bg_fill: self.setting.theme.bgaccent, - bg_stroke: Stroke::NONE, - rounding: 0.0.into(), - fg_stroke: Stroke::NONE, - expansion: 0.0, - }, - }, - ..Default::default() - }); + ui.ctx().set_visuals(set_theme(self)); }; if ui .selectable_value(&mut self.setting.theme, setting::PINK_THEME, "Pink") .changed() { self.setting.theme = setting::PINK_THEME; - ui.ctx().set_visuals(Visuals { - dark_mode: false, - override_text_color: Some(self.setting.theme.text), - selection: Selection { - bg_fill: self.setting.theme.selection, - stroke: Stroke::NONE, - }, - widgets: Widgets { - noninteractive: WidgetVisuals { - bg_fill: self.setting.theme.bgaccent, - weak_bg_fill: self.setting.theme.bgaccent, - bg_stroke: Stroke::new(1.0, self.setting.theme.frame), - rounding: 0.0.into(), - fg_stroke: Stroke::NONE, - expansion: 0.0, - }, - inactive: WidgetVisuals { - bg_fill: self.setting.theme.bgaccent, - weak_bg_fill: self.setting.theme.bgaccent, - bg_stroke: Stroke::NONE, - rounding: 0.0.into(), - fg_stroke: Stroke::NONE, - expansion: 0.0, - }, - active: WidgetVisuals { - bg_fill: self.setting.theme.selection, - weak_bg_fill: self.setting.theme.selection, - bg_stroke: Stroke::NONE, - rounding: 0.0.into(), - fg_stroke: Stroke::NONE, - expansion: 0.0, - }, - hovered: WidgetVisuals { - bg_fill: self.setting.theme.selection, - weak_bg_fill: self.setting.theme.selection, - bg_stroke: Stroke::NONE, - rounding: 0.0.into(), - fg_stroke: Stroke::new(1.0, self.setting.theme.text), - expansion: 0.0, - }, - open: WidgetVisuals { - bg_fill: self.setting.theme.bg, - weak_bg_fill: self.setting.theme.bgaccent, - bg_stroke: Stroke::NONE, - rounding: 0.0.into(), - fg_stroke: Stroke::NONE, - expansion: 0.0, - }, - }, - ..Default::default() - }); + ui.ctx().set_visuals(set_theme(self)); } }); }); diff --git a/src/frame/waveform_frame.rs b/src/frame/waveform_frame.rs index b0a71d4..0521367 100644 --- a/src/frame/waveform_frame.rs +++ b/src/frame/waveform_frame.rs @@ -5,92 +5,93 @@ use egui::*; use rayon::prelude::*; impl NanometersApp { - pub fn waveform_frame(&mut self, mut data: RawData, rect: Rect, ui: &mut Ui) { + pub fn waveform_frame(&mut self, data: &WaveformSendData, rect: Rect, ui: &mut Ui) { ui.painter().rect_filled(rect, 0.0, self.setting.theme.bg); - - let last_data = self.waveform.data_buffer.clone(); - data.concat_front(last_data); - let rest = data.l.len() % self.waveform.update_speed; - let len = data.l.len() / self.waveform.update_speed; - self.waveform.data_buffer = data.split_index(data.l.len() - rest); let upper_rect = Rect::from_two_pos(rect.min, pos2(rect.max.x, rect.center().y)); let lower_rect = Rect::from_two_pos(pos2(rect.min.x, rect.center().y), rect.max); match self.setting.waveform.channel_1 { WaveformChannel::None => {} WaveformChannel::Left => { - self.waveform_upper_channel_frame(&data.l, len, upper_rect, ui); + self.waveform_upper_channel_frame(&data.l, &data.l_freq, upper_rect, ui); } WaveformChannel::Right => { - self.waveform_upper_channel_frame(&data.r, len, upper_rect, ui); + self.waveform_upper_channel_frame(&data.r, &data.r_freq, upper_rect, ui); } WaveformChannel::Mid => { - self.waveform_upper_channel_frame(&data.m, len, upper_rect, ui); + self.waveform_upper_channel_frame(&data.m, &data.m_freq, upper_rect, ui); } WaveformChannel::Side => { - self.waveform_upper_channel_frame(&data.s, len, upper_rect, ui); + self.waveform_upper_channel_frame(&data.s, &data.s_freq, upper_rect, ui); } } match self.setting.waveform.channel_2 { WaveformChannel::None => {} WaveformChannel::Left => { - self.waveform_lower_channel_frame(&data.l, len, lower_rect, ui); + self.waveform_lower_channel_frame(&data.l, &data.l_freq, lower_rect, ui); } WaveformChannel::Right => { - self.waveform_lower_channel_frame(&data.r, len, lower_rect, ui); + self.waveform_lower_channel_frame(&data.r, &data.r_freq, lower_rect, ui); } WaveformChannel::Mid => { - self.waveform_lower_channel_frame(&data.m, len, lower_rect, ui); + self.waveform_lower_channel_frame(&data.m, &data.m_freq, lower_rect, ui); } WaveformChannel::Side => { - self.waveform_lower_channel_frame(&data.s, len, lower_rect, ui); + self.waveform_lower_channel_frame(&data.s, &data.s_freq, lower_rect, ui); } } } - fn waveform_upper_channel_frame(&mut self, data: &[f32], len: usize, rect: Rect, ui: &mut Ui) { - for i in 0..len { - let max = data - .par_iter() - .skip(i * self.waveform.update_speed) - .take(self.waveform.update_speed) - .max_by(|x, y| x.total_cmp(*y)) - .unwrap(); - let min = data - .par_iter() - .skip(i * self.waveform.update_speed) - .take(self.waveform.update_speed) - .min_by(|x, y| x.total_cmp(*y)) - .unwrap(); - // println!("max: {}, min: {}", max, min); - self.waveform - .plot_point - .uu - .push(rect.center().y - rect.height() * max / 2.0); - self.waveform - .plot_point - .ud - .push(rect.center().y - rect.height() * min / 2.0); + fn waveform_upper_channel_frame( + &mut self, + data: &[MAXMIN], + color: &[usize], + rect: Rect, + ui: &mut Ui, + ) { + if !data.is_empty() { + data.iter().zip(color).for_each(|(v, c)| { + if self.waveform.plot_point.uu.len() >= self.waveform.history_length { + self.waveform.plot_point.uu.pop_front(); + self.waveform.plot_point.ud.pop_front(); + self.waveform.plot_point.ucolor.pop_front(); + } + self.waveform + .plot_point + .uu + .push_back(rect.center().y - rect.height() * v.max / 2.0); + self.waveform + .plot_point + .ud + .push_back(rect.center().y - rect.height() * v.min / 2.0); + self.waveform + .plot_point + .ucolor + .push_back(self.color_lut_129[*c]); + }); } + let len = self.waveform.plot_point.uu.len(); let shapes: Vec = (0..rect.width() as usize) .into_iter() .map(|i| { epaint::Shape::vline( rect.max.x - i as f32, Rangef::new( - self.waveform - .plot_point - .uu - .get(self.waveform.plot_point.len - i), - self.waveform - .plot_point - .ud - .get(self.waveform.plot_point.len - i), + *self.waveform.plot_point.uu.get(len - i).unwrap_or(&0.0), + *self.waveform.plot_point.ud.get(len - i).unwrap_or(&0.0), ), match self.setting.waveform.mode { WaveformMode::Static => Stroke::new(1.0, self.setting.theme.main), - WaveformMode::MultiBand => Stroke::new(1.0, self.setting.theme.main), + WaveformMode::MultiBand => Stroke::new( + 1.0, + self.waveform + .plot_point + .ucolor + .get(len - i) + .unwrap_or(&self.setting.theme.main) + .clone(), + ), }, ) }) @@ -98,47 +99,55 @@ impl NanometersApp { ui.painter().extend(shapes); } - fn waveform_lower_channel_frame(&mut self, data: &[f32], len: usize, rect: Rect, ui: &mut Ui) { - for i in 0..len { - let max = data - .par_iter() - .skip(i * self.waveform.update_speed) - .take(self.waveform.update_speed) - .max_by(|x, y| x.total_cmp(*y)) - .unwrap(); - let min = data - .par_iter() - .skip(i * self.waveform.update_speed) - .take(self.waveform.update_speed) - .min_by(|x, y| x.total_cmp(*y)) - .unwrap(); - self.waveform - .plot_point - .du - .push(rect.center().y - rect.height() * max / 2.0); - self.waveform - .plot_point - .dd - .push(rect.center().y - rect.height() * min / 2.0); + fn waveform_lower_channel_frame( + &mut self, + data: &[MAXMIN], + color: &[usize], + rect: Rect, + ui: &mut Ui, + ) { + if !data.is_empty() { + data.iter().zip(color).for_each(|(v, c)| { + if self.waveform.plot_point.du.len() >= self.waveform.history_length { + self.waveform.plot_point.du.pop_front(); + self.waveform.plot_point.dd.pop_front(); + self.waveform.plot_point.dcolor.pop_front(); + } + self.waveform + .plot_point + .du + .push_back(rect.center().y - rect.height() * v.max / 2.0); + self.waveform + .plot_point + .dd + .push_back(rect.center().y - rect.height() * v.min / 2.0); + self.waveform + .plot_point + .dcolor + .push_back(self.color_lut_129[*c]); + }); } + let len = self.waveform.plot_point.du.len(); let shapes: Vec = (0..rect.width() as usize) .into_iter() .map(|i| { epaint::Shape::vline( rect.max.x - i as f32, Rangef::new( - self.waveform - .plot_point - .du - .get(self.waveform.plot_point.len - i), - self.waveform - .plot_point - .dd - .get(self.waveform.plot_point.len - i), + *self.waveform.plot_point.du.get(len - i).unwrap_or(&0.0), + *self.waveform.plot_point.dd.get(len - i).unwrap_or(&0.0), ), match self.setting.waveform.mode { WaveformMode::Static => Stroke::new(1.0, self.setting.theme.main), - WaveformMode::MultiBand => Stroke::new(1.0, self.setting.theme.main), + WaveformMode::MultiBand => Stroke::new( + 1.0, + self.waveform + .plot_point + .dcolor + .get(len - i) + .unwrap_or(&self.setting.theme.main) + .clone(), + ), }, ) }) diff --git a/src/lib.rs b/src/lib.rs index f20fb6f..6c3a620 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![warn(clippy::all, rust_2018_idioms)] +// #![warn(clippy::all, rust_2018_idioms)] #[allow(unused)] mod app; pub mod audio; @@ -6,12 +6,4 @@ pub mod frame; pub mod setting; pub mod utils; -use crate::audio::SystemCapture; -pub use crate::utils::ringbuffer::*; pub use app::NanometersApp; - -pub trait AudioSource { - fn get_name(&self) -> String; - fn start(&mut self); - fn stop(&mut self); -} diff --git a/src/setting/peak.rs b/src/setting/peak.rs index 30d4776..7edbda1 100644 --- a/src/setting/peak.rs +++ b/src/setting/peak.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use std::default; +use std::collections::VecDeque; #[derive(Default, Clone, Copy, Debug, Serialize, Deserialize, PartialEq)] pub enum PeakOrientation { @@ -20,6 +20,13 @@ pub struct Peak { pub(crate) r: f32, pub(crate) plot_l: f32, pub(crate) plot_r: f32, + pub(crate) lufs: f32, + #[serde(skip)] + pub(crate) past_3s: VecDeque, + #[serde(skip)] + pub(crate) data_buffer_l: VecDeque, + #[serde(skip)] + pub(crate) data_buffer_r: VecDeque, } impl Default for Peak { @@ -29,6 +36,10 @@ impl Default for Peak { r: f32::NEG_INFINITY, plot_l: 0.0, plot_r: 0.0, + lufs: f32::NEG_INFINITY, + past_3s: vec![f32::NEG_INFINITY; 27].into(), //3000ms, 400ms per block, overlap 75% + data_buffer_l: VecDeque::new(), + data_buffer_r: VecDeque::new(), } } } diff --git a/src/setting/theme.rs b/src/setting/theme.rs index 82acb37..9c6133b 100644 --- a/src/setting/theme.rs +++ b/src/setting/theme.rs @@ -1,4 +1,6 @@ -use egui::Color32; +use crate::NanometersApp; +use egui::style::*; +use egui::*; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)] pub struct Theme { @@ -48,3 +50,57 @@ pub enum ThemeType { Pink, Custom, } + +pub fn set_theme(app: &mut NanometersApp) -> Visuals { + Visuals { + dark_mode: false, + override_text_color: Some(app.setting.theme.text), + selection: Selection { + bg_fill: app.setting.theme.selection, + stroke: Stroke::NONE, + }, + widgets: Widgets { + noninteractive: WidgetVisuals { + bg_fill: app.setting.theme.bgaccent, + weak_bg_fill: app.setting.theme.bgaccent, + bg_stroke: Stroke::new(1.0, app.setting.theme.frame), + rounding: 0.0.into(), + fg_stroke: Stroke::NONE, + expansion: 0.0, + }, + inactive: WidgetVisuals { + bg_fill: app.setting.theme.bgaccent, + weak_bg_fill: app.setting.theme.bgaccent, + bg_stroke: Stroke::NONE, + rounding: 0.0.into(), + fg_stroke: Stroke::NONE, + expansion: 0.0, + }, + active: WidgetVisuals { + bg_fill: app.setting.theme.selection, + weak_bg_fill: app.setting.theme.selection, + bg_stroke: Stroke::NONE, + rounding: 0.0.into(), + fg_stroke: Stroke::NONE, + expansion: 0.0, + }, + hovered: WidgetVisuals { + bg_fill: app.setting.theme.selection, + weak_bg_fill: app.setting.theme.selection, + bg_stroke: Stroke::NONE, + rounding: 0.0.into(), + fg_stroke: Stroke::new(1.0, app.setting.theme.text), + expansion: 0.0, + }, + open: WidgetVisuals { + bg_fill: app.setting.theme.bg, + weak_bg_fill: app.setting.theme.bgaccent, + bg_stroke: Stroke::NONE, + rounding: 0.0.into(), + fg_stroke: Stroke::NONE, + expansion: 0.0, + }, + }, + ..Default::default() + } +} diff --git a/src/setting/waveform.rs b/src/setting/waveform.rs index e0b54b2..ed21a4f 100644 --- a/src/setting/waveform.rs +++ b/src/setting/waveform.rs @@ -1,7 +1,6 @@ use std::collections::VecDeque; use crate::utils::*; -use egui::Pos2; use serde::{Deserialize, Serialize}; #[derive(Default, Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -40,21 +39,23 @@ pub struct WaveformSetting { #[derive(Debug)] pub struct WaveformPlotPoint { - pub(crate) len: usize, - pub(crate) uu: RingBuffer, - pub(crate) ud: RingBuffer, - pub(crate) du: RingBuffer, - pub(crate) dd: RingBuffer, + pub(crate) uu: VecDeque, + pub(crate) ud: VecDeque, + pub(crate) ucolor: VecDeque, + pub(crate) du: VecDeque, + pub(crate) dd: VecDeque, + pub(crate) dcolor: VecDeque, } impl WaveformPlotPoint { pub fn new(size: usize) -> Self { Self { - len: size, - uu: RingBuffer::new_with_default(size, 0.0), - ud: RingBuffer::new_with_default(size, 0.0), - du: RingBuffer::new_with_default(size, 0.0), - dd: RingBuffer::new_with_default(size, 0.0), + uu: VecDeque::with_capacity(size), + ud: VecDeque::with_capacity(size), + ucolor: VecDeque::with_capacity(size), + du: VecDeque::with_capacity(size), + dd: VecDeque::with_capacity(size), + dcolor: VecDeque::with_capacity(size), } } } @@ -67,19 +68,106 @@ impl Default for WaveformPlotPoint { #[derive(Debug, Serialize, Deserialize)] pub struct Waveform { + pub(crate) history_length: usize, #[serde(skip)] pub(crate) plot_point: WaveformPlotPoint, - #[serde(skip)] - pub(crate) data_buffer: RawData, pub(crate) update_speed: usize, } impl Default for Waveform { fn default() -> Self { Self { + history_length: 3840, plot_point: WaveformPlotPoint::new(3840), - data_buffer: RawData::new(), - update_speed: 400, + update_speed: 256, } } } + +#[derive(Debug, Clone, Default)] +pub struct WaveformCalcBuffer { + pub index: usize, + pub raw: RawData, + pub l: MAXMIN, + pub r: MAXMIN, + pub m: MAXMIN, + pub s: MAXMIN, +} + +impl WaveformCalcBuffer { + pub fn new() -> Self { + Self { + index: 0, + raw: RawData::new(), + l: MAXMIN::new(), + r: MAXMIN::new(), + m: MAXMIN::new(), + s: MAXMIN::new(), + } + } + pub fn update_l(&mut self, val: f32) { + self.raw.l.push(val); + self.l.max = self.l.max.max(val); + self.l.min = self.l.min.min(val); + } + pub fn update_r(&mut self, val: f32) { + self.raw.r.push(val); + self.r.max = self.r.max.max(val); + self.r.min = self.r.min.min(val); + } + pub fn update_m(&mut self, val: f32) { + self.raw.m.push(val); + self.m.max = self.m.max.max(val); + self.m.min = self.m.min.min(val); + } + pub fn update_s(&mut self, val: f32) { + self.raw.s.push(val); + self.s.max = self.s.max.max(val); + self.s.min = self.s.min.min(val); + } + pub fn reset(&mut self) { + self.index = 0; + self.raw.clear(); + self.l = MAXMIN::new(); + self.r = MAXMIN::new(); + self.m = MAXMIN::new(); + self.s = MAXMIN::new(); + } +} + +#[derive(Debug, Clone, Default)] +pub struct WaveformSendData { + pub l: Vec, + pub r: Vec, + pub m: Vec, + pub s: Vec, + pub l_freq: Vec, + pub r_freq: Vec, + pub m_freq: Vec, + pub s_freq: Vec, +} + +impl WaveformSendData { + pub fn new() -> Self { + Self { + l: vec![], + r: vec![], + m: vec![], + s: vec![], + l_freq: vec![], + r_freq: vec![], + m_freq: vec![], + s_freq: vec![], + } + } + pub fn concat(&mut self, data: &WaveformSendData) { + self.l.extend_from_slice(&data.l); + self.r.extend_from_slice(&data.r); + self.m.extend_from_slice(&data.m); + self.s.extend_from_slice(&data.s); + self.l_freq.extend_from_slice(&data.l_freq); + self.r_freq.extend_from_slice(&data.r_freq); + self.m_freq.extend_from_slice(&data.m_freq); + self.s_freq.extend_from_slice(&data.s_freq); + } +} diff --git a/src/utils.rs b/src/utils.rs index da7bcbf..64d1cca 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,16 +1,20 @@ #![allow(unused)] +pub(crate) mod calc_check; +pub(crate) mod color; pub(crate) mod data_struct; pub(crate) mod db; pub(crate) mod frame_history; pub(crate) mod hann; -pub(crate) mod irrfilter; +pub(crate) mod iirfilter; pub(crate) mod rect_alloc; pub(crate) mod ringbuffer; +pub use calc_check::*; +pub use color::*; pub use data_struct::*; pub use db::*; pub use frame_history::*; pub use hann::*; -pub use irrfilter::*; +pub use iirfilter::*; pub use rect_alloc::*; pub use ringbuffer::*; diff --git a/src/utils/calc_check.rs b/src/utils/calc_check.rs new file mode 100644 index 0000000..aa77ae7 --- /dev/null +++ b/src/utils/calc_check.rs @@ -0,0 +1,5 @@ +pub struct Calculation { + block_max_min: bool, + block_fft: bool, + block_rms: bool, +} diff --git a/src/utils/color.rs b/src/utils/color.rs new file mode 100644 index 0000000..1322caf --- /dev/null +++ b/src/utils/color.rs @@ -0,0 +1,220 @@ +pub fn color_lut_129() -> Vec { + (0..129) + .into_iter() + .map(|i| { + let h = i as f64 * 360.0 / 129.0; + let s = 1.0; + let v = 1.0; + hsv_to_rgb(h as u16, s, v).unwrap() + }) + .collect() +} + +pub fn hsl_to_rgb(h: u16, s: f64, l: f64) -> Option { + match h { + 0..=60 => { + let c = color_hsl_c(l, s); + let x = color_hsl_x(c, h as f64); + let m = color_hsl_m(l, c); + Some(egui::Color32::from_rgb( + ((c + m) * 255.0) as u8, + ((x + m) * 255.0) as u8, + (m * 255.0) as u8, + )) + } + 61..=120 => { + let c = color_hsl_c(l, s); + let x = color_hsl_x(c, h as f64); + let m = color_hsl_m(l, c); + Some(egui::Color32::from_rgb( + ((x + m) * 255.0) as u8, + ((c + m) * 255.0) as u8, + (m * 255.0) as u8, + )) + } + 121..=180 => { + let c = color_hsl_c(l, s); + let x = color_hsl_x(c, h as f64); + let m = color_hsl_m(l, c); + Some(egui::Color32::from_rgb( + (m * 255.0) as u8, + ((c + m) * 255.0) as u8, + ((x + m) * 255.0) as u8, + )) + } + 181..=240 => { + let c = color_hsl_c(l, s); + let x = color_hsl_x(c, h as f64); + let m = color_hsl_m(l, c); + Some(egui::Color32::from_rgb( + (m * 255.0) as u8, + ((x + m) * 255.0) as u8, + ((c + m) * 255.0) as u8, + )) + } + 241..=300 => { + let c = color_hsl_c(l, s); + let x = color_hsl_x(c, h as f64); + let m = color_hsl_m(l, c); + Some(egui::Color32::from_rgb( + ((x + m) * 255.0) as u8, + (m * 255.0) as u8, + ((c + m) * 255.0) as u8, + )) + } + 301..=360 => { + let c = color_hsl_c(l, s); + let x = color_hsl_x(c, h as f64); + let m = color_hsl_m(l, c); + Some(egui::Color32::from_rgb( + ((c + m) * 255.0) as u8, + (m * 255.0) as u8, + ((x + m) * 255.0) as u8, + )) + } + _ => None, + } +} + +fn color_hsl_c(l: f64, s: f64) -> f64 { + (1.0 - (2.0 * l - 1.0).abs()) * s +} + +fn color_hsl_x(c: f64, h: f64) -> f64 { + c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs()) +} + +fn color_hsl_m(l: f64, c: f64) -> f64 { + l - c / 2.0 +} + +pub fn hsv_to_rgb(h: u16, s: f64, v: f64) -> Option { + match h { + 0..=60 => { + let c = color_hsv_c(v, s); + let x = color_hsv_x(c, h as f64); + let m = color_hsv_m(v, c); + Some(egui::Color32::from_rgb( + ((c + m) * 255.0) as u8, + ((x + m) * 255.0) as u8, + (m * 255.0) as u8, + )) + } + 61..=120 => { + let c = color_hsv_c(v, s); + let x = color_hsv_x(c, h as f64); + let m = color_hsv_m(v, c); + Some(egui::Color32::from_rgb( + ((x + m) * 255.0) as u8, + ((c + m) * 255.0) as u8, + (m * 255.0) as u8, + )) + } + 121..=180 => { + let c = color_hsv_c(v, s); + let x = color_hsv_x(c, h as f64); + let m = color_hsv_m(v, c); + Some(egui::Color32::from_rgb( + (m * 255.0) as u8, + ((c + m) * 255.0) as u8, + ((x + m) * 255.0) as u8, + )) + } + 181..=240 => { + let c = color_hsv_c(v, s); + let x = color_hsv_x(c, h as f64); + let m = color_hsv_m(v, c); + Some(egui::Color32::from_rgb( + (m * 255.0) as u8, + ((x + m) * 255.0) as u8, + ((c + m) * 255.0) as u8, + )) + } + 241..=300 => { + let c = color_hsv_c(v, s); + let x = color_hsv_x(c, h as f64); + let m = color_hsv_m(v, c); + Some(egui::Color32::from_rgb( + ((x + m) * 255.0) as u8, + (m * 255.0) as u8, + ((c + m) * 255.0) as u8, + )) + } + 301..=360 => { + let c = color_hsv_c(v, s); + let x = color_hsv_x(c, h as f64); + let m = color_hsv_m(v, c); + Some(egui::Color32::from_rgb( + ((c + m) * 255.0) as u8, + (m * 255.0) as u8, + ((x + m) * 255.0) as u8, + )) + } + _ => None, + } +} + +fn color_hsv_c(v: f64, s: f64) -> f64 { + v * s +} + +fn color_hsv_x(c: f64, h: f64) -> f64 { + c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs()) +} + +fn color_hsv_m(v: f64, c: f64) -> f64 { + v - c +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hsl_to_rgb() { + assert_eq!( + hsl_to_rgb(0, 0.0, 0.0), + Some(egui::Color32::from_rgb(0, 0, 0)) + ); + assert_eq!( + hsl_to_rgb(0, 0.0, 1.0), + Some(egui::Color32::from_rgb(255, 255, 255)) + ); + assert_eq!( + hsl_to_rgb(0, 1.0, 0.5), + Some(egui::Color32::from_rgb(255, 0, 0)) + ); + assert_eq!( + hsl_to_rgb(120, 1.0, 0.5), + Some(egui::Color32::from_rgb(0, 255, 0)) + ); + assert_eq!( + hsl_to_rgb(240, 1.0, 0.5), + Some(egui::Color32::from_rgb(0, 0, 255)) + ); + } + + #[test] + fn test_hsv_to_rgb() { + assert_eq!( + hsv_to_rgb(0, 0.0, 0.0), + Some(egui::Color32::from_rgb(0, 0, 0)) + ); + assert_eq!( + hsv_to_rgb(0, 0.0, 1.0), + Some(egui::Color32::from_rgb(255, 255, 255)) + ); + assert_eq!( + hsv_to_rgb(0, 1.0, 0.5), + Some(egui::Color32::from_rgb(127, 0, 0)) + ); + assert_eq!( + hsv_to_rgb(120, 1.0, 0.5), + Some(egui::Color32::from_rgb(0, 127, 0)) + ); + assert_eq!( + hsv_to_rgb(240, 1.0, 0.5), + Some(egui::Color32::from_rgb(0, 0, 127)) + ); + } +} diff --git a/src/utils/data_struct.rs b/src/utils/data_struct.rs index d3bc760..441fd7f 100644 --- a/src/utils/data_struct.rs +++ b/src/utils/data_struct.rs @@ -1,3 +1,6 @@ +use crate::setting::*; +use std::fmt::Display; + #[derive(Debug, Clone, Default)] pub struct RawData { pub l: Vec, @@ -5,7 +8,6 @@ pub struct RawData { pub m: Vec, pub s: Vec, } - impl RawData { pub fn new() -> Self { Self { @@ -44,36 +46,138 @@ impl RawData { self.m.clear(); self.s.clear(); } +} - pub fn push_l(&mut self, value: f32) { - self.l.push(value); +#[derive(Debug, Clone, Default)] +pub struct IIRBuffer { + pub x_1: f32, + pub x_2: f32, + pub y_1: f32, + pub y_2: f32, + pub z_1: f32, + pub z_2: f32, +} +impl IIRBuffer { + pub fn new() -> Self { + Self { + x_1: 0.0, + x_2: 0.0, + y_1: 0.0, + y_2: 0.0, + z_1: 0.0, + z_2: 0.0, + } } +} + +#[derive(Debug, Clone, Default)] +pub struct MAXMIN { + pub max: f32, + pub min: f32, +} - pub fn push_r(&mut self, value: f32) { - self.r.push(value); +impl Display for MAXMIN { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "max: {}, min: {}", self.max, self.min) } +} - pub fn push_m(&mut self, value: f32) { - self.m.push(value); +impl MAXMIN { + pub fn new() -> Self { + Self { + max: f32::NEG_INFINITY, + min: f32::INFINITY, + } } +} - pub fn push_s(&mut self, value: f32) { - self.s.push(value); +#[derive(Debug, Clone, Default)] +pub struct PeakCalcBuffer { + pub index: usize, + pub iir_l: IIRBuffer, + pub iir_r: IIRBuffer, + pub sum_l: f32, + pub sum_r: f32, +} + +impl PeakCalcBuffer { + pub fn new() -> Self { + Self { + index: 0, + iir_l: IIRBuffer::new(), + iir_r: IIRBuffer::new(), + sum_l: 0.0, + sum_r: 0.0, + } } - pub fn extend_l(&mut self, value: &[f32]) { - self.l.extend_from_slice(value); + pub fn reset_sum(&mut self) { + self.index = 0; + self.sum_l = 0.0; + self.sum_r = 0.0; } +} - pub fn extend_r(&mut self, value: &[f32]) { - self.r.extend_from_slice(value); +#[derive(Debug, Clone, Default)] +pub struct AudioSourceBuffer { + pub peak: PeakCalcBuffer, + pub waveform: WaveformCalcBuffer, +} + +impl AudioSourceBuffer { + pub fn new() -> Self { + Self { + peak: PeakCalcBuffer::new(), + waveform: WaveformCalcBuffer::new(), + } + } +} + +#[derive(Debug, Clone, Default)] +pub struct IIRData { + pub l: Vec, + pub r: Vec, +} + +impl IIRData { + pub fn new() -> Self { + Self { + l: vec![], + r: vec![], + } } - pub fn extend_m(&mut self, value: &[f32]) { - self.m.extend_from_slice(value); + pub fn concat(&mut self, data: &IIRData) { + self.l.extend_from_slice(&data.l); + self.r.extend_from_slice(&data.r); } +} + +#[derive(Debug, Clone, Default)] +pub struct DBData { + pub l: f32, + pub r: f32, +} + +impl DBData { + pub fn new() -> Self { + Self { l: 0.0, r: 0.0 } + } +} + +#[derive(Debug, Clone, Default)] +pub struct SendData { + pub waveform_data: WaveformSendData, + pub iir_data: IIRData, + pub db_data: DBData, +} - pub fn extend_s(&mut self, value: &[f32]) { - self.s.extend_from_slice(value); +impl SendData { + pub fn new() -> Self { + Self { + waveform_data: WaveformSendData::new(), + iir_data: IIRData::new(), + db_data: DBData::new(), + } } } diff --git a/src/utils/iirfilter.rs b/src/utils/iirfilter.rs new file mode 100644 index 0000000..7591799 --- /dev/null +++ b/src/utils/iirfilter.rs @@ -0,0 +1,32 @@ +use super::*; + +pub const SHELVING_A1: f32 = -1.69065929318241; +pub const SHELVING_A2: f32 = 0.73248077421585; +pub const SHELVING_B0: f32 = 0.73248077421585; +pub const SHELVING_B1: f32 = -2.69169618940638; +pub const SHELVING_B2: f32 = 1.19839281085285; +pub const HIGHPASS_A1: f32 = -1.99004745483398; +pub const HIGHPASS_A2: f32 = 0.99007225036621; +pub const HIGHPASS_B0: f32 = 1.0; +pub const HIGHPASS_B1: f32 = -2.0; +pub const HIGHPASS_B2: f32 = 1.0; + +pub fn combined_filter(x_0: f32, buffer: &mut IIRBuffer) -> f32 { + let y_0 = SHELVING_B0 * x_0 + + SHELVING_B1 * buffer.x_1 + + SHELVING_B2 * buffer.x_2 + + SHELVING_A1 * buffer.y_1 + + SHELVING_A2 * buffer.y_2; + let z_0 = HIGHPASS_B0 * y_0 + + HIGHPASS_B1 * buffer.y_1 + + HIGHPASS_B2 * buffer.y_2 + + HIGHPASS_A1 * buffer.z_1 + + HIGHPASS_A2 * buffer.z_2; + buffer.x_2 = buffer.x_1; + buffer.x_1 = x_0; + buffer.y_2 = buffer.y_1; + buffer.y_1 = y_0; + buffer.z_2 = buffer.z_1; + buffer.z_1 = z_0; + z_0 +} diff --git a/src/utils/irrfilter.rs b/src/utils/irrfilter.rs deleted file mode 100644 index b59c4f3..0000000 --- a/src/utils/irrfilter.rs +++ /dev/null @@ -1,29 +0,0 @@ -pub fn shelving_filter(x: &Vec, y: &mut Vec) { - let a1 = -1.69065929318241; - let a2 = 0.73248077421585; - let b0 = 0.73248077421585; - let b1 = -2.69169618940638; - let b2 = 1.19839281085285; - (0..x.len()).for_each(|i| { - if i < 2 { - y[i] = 0.0; - } else { - y[i] = b0 * x[i] + b1 * x[i - 1] + b2 * x[i - 2] + a1 * y[i - 1] + a2 * y[i - 2]; - } - }); -} - -pub fn highpass_filter(x: &Vec, y: &mut Vec) { - let a1 = -1.99004745483398; - let a2 = 0.99007225036621; - let b0 = 1.0; - let b1 = -2.0; - let b2 = 1.0; - (0..x.len()).for_each(|i| { - if i < 2 { - y[i] = 0.0; - } else { - y[i] = b0 * x[i] + b1 * x[i - 1] + b2 * x[i - 2] + a1 * y[i - 1] + a2 * y[i - 2]; - } - }); -} diff --git a/src/utils/ringbuffer.rs b/src/utils/ringbuffer.rs index 84196b2..27e3a1a 100644 --- a/src/utils/ringbuffer.rs +++ b/src/utils/ringbuffer.rs @@ -1,13 +1,13 @@ use std::{collections::VecDeque, marker::PhantomData}; -#[derive(Debug)] -pub struct RingBuffer { +#[derive(Debug, Default)] +pub struct RingBufferF32 { buffer: VecDeque, capacity: usize, _marker: PhantomData<*const ()>, } -impl RingBuffer { +impl RingBufferF32 { pub fn new(size: usize) -> Self { Self { buffer: VecDeque::with_capacity(size), @@ -50,5 +50,64 @@ impl RingBuffer { } } -unsafe impl Send for RingBuffer {} -unsafe impl Sync for RingBuffer {} +unsafe impl Send for RingBufferF32 {} +unsafe impl Sync for RingBufferF32 {} + +#[derive(Debug, Default)] +pub struct RingBuffer { + buffer: VecDeque, + capacity: usize, + _marker: PhantomData<*const ()>, +} + +impl RingBuffer { + pub fn new(size: usize) -> Self { + Self { + buffer: VecDeque::with_capacity(size), + capacity: size, + _marker: PhantomData, + } + } + + pub fn new_with_default(size: usize, default: T) -> Self + where + T: Clone, + { + Self { + buffer: VecDeque::from(vec![default; size]), + capacity: size, + _marker: PhantomData, + } + } + + pub fn push(&mut self, value: T) { + if self.buffer.len() == self.capacity { + self.buffer.pop_front(); + } + self.buffer.push_back(value); + } + + pub fn push_slice(&mut self, slice: &[T]) + where + T: Clone, + { + if slice.len() + self.buffer.len() > self.capacity { + let diff = slice.len() + self.buffer.len() - self.capacity; + for i in 0..diff { + self.buffer.pop_front(); + self.buffer.push_back(slice[i].clone()); + } + } + } + + pub fn get(&self, index: usize) -> Option<&T> + where + T: Clone, + { + self.buffer.get(index) + } + + pub fn len(&self) -> usize { + self.buffer.len() + } +}