From b35e84a051c22de1d54d441e01af0b5edfb2b92b Mon Sep 17 00:00:00 2001 From: Josh Jeppson Date: Wed, 13 Nov 2024 18:39:15 -0700 Subject: [PATCH] some fun stuff --- Cargo.toml | 2 +- src/main.rs | 119 +++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 105 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 480e85e..5fd5e43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -clap = "4.5.20" +clap = { version = "4.5.20", features = ["derive"] } eframe = "0.29.1" egui = "0.29.1" jack = "0.13.0" diff --git a/src/main.rs b/src/main.rs index 8b5ca0a..f702517 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,10 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +use clap::Parser; use eframe::egui::*; fn main() -> eframe::Result { + let args = Args::parse(); let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default(), ..Default::default() @@ -15,33 +17,104 @@ fn main() -> eframe::Result { cc.egui_ctx.set_theme(egui::Theme::Dark); // egui_extras::install_image_loaders(&cc.egui_ctx); - Ok(Box::::new(FslcMix::new(5))) + Ok(Box::::new(FslcMix::new(args.channels))) }), ) } +/// A mixer that supports an arbitrary number of channels. +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Args { + /// Number of channels + #[arg(short, long, default_value_t = 5)] + channels: u8, +} + struct FslcMix { channels: Vec, master: MixChannel, + normalize: bool, + ui_size: egui::Vec2, } impl FslcMix { fn new(num_channels: u8) -> Self { Self { - channels: (0..num_channels).map(|_| MixChannel::default()).collect(), - master: MixChannel { channel_name: "Master".to_owned(), ..Default::default() } + channels: (1..=num_channels).map(|i| + MixChannel { + channel_name: format!("Channel {}", i), + ..Default::default() + } + ).collect(), + master: MixChannel { + channel_name: "Master".to_owned(), + ..Default::default() + }, + normalize: false, + ui_size: egui::Vec2::new(400.0, 200.0), // This size doesn't matter since it's + // overritten } } + + fn mix(&mut self, inputs : Vec<&[f32]>, output : &mut [f32]) { + // Sanity check + assert!(inputs.len() == self.channels.len()); + // Initialize to zeros + for i in 0..output.len() { + output[i] = 0.0; + } + if self.master.mute { + return; + } + // TODO: short circuit + let any_solo = self.channels.iter() + .fold(false, |any_so_far, channel| { + channel.solo || any_so_far + }); + // Mix each channel together + for channel_index in 0..inputs.len() { + let channel = &mut self.channels[channel_index]; + let input = inputs[channel_index]; + channel.mix(input, output, any_solo); + } + // Apply the master channel's mix and normalize if necessary + let norm_factor = inputs.len() as f32; + // Bypass its mix() function since we do it slightly different here + for i in 0..output.len() { + if self.normalize { + output[i] /= norm_factor; + } + let sample = output[i] * self.master.gain; + let out_sample = if self.master.limit && sample >= 1.0 { + 1.0 + } else if self.master.limit && sample <= -1.0 { + -1.0 + } else { + sample + }; + if out_sample > self.master.max { + self.master.max = out_sample; + } + output[i] = out_sample; + + } + self.master.last = output[output.len() - 1]; + } } impl eframe::App for FslcMix { fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { - egui::Window::new("Mixer (FSLCMix)") - .default_pos([100.0, 100.0]) - .title_bar(true) + //egui::Window::new("Mixer (FSLCMix)") + // .default_pos([100.0, 100.0]) + // .title_bar(true) + egui::CentralPanel::default() .show(ctx, |ui|{ ui.vertical(|ui| { - ui.label("Licensed under the GPLv3."); + ui.horizontal(|ui| { + // ui.label("Licensed under the GPLv3."); + ui.toggle_value(&mut self.normalize, "Normalize") + }); }); ui.horizontal(|ui| { self.master.ui(ui); @@ -50,7 +123,10 @@ impl eframe::App for FslcMix { channel.ui(ui); } }); + self.ui_size = ui.min_size(); }); + let window_size = self.ui_size + egui::vec2(20.0, 40.0); + // frame.set_window_size(window_size); } } @@ -65,19 +141,27 @@ struct MixChannel { } impl MixChannel { - fn mix(&mut self, input : &[f32], output : &mut [f32]) { + + fn mix(&mut self, input : &[f32], output : &mut [f32], any_solo : bool) { + if self.mute || (any_solo && !self.solo) { + self.last = 0.0; + return; + } // Sanity check assert!(input.len() == output.len()); for i in 0..input.len() { let sample = input[i] * self.gain; - if self.limit && sample >= 1.0 { - output[i] = 1.0; + let out_sample = if self.limit && sample >= 1.0 { + 1.0 + } else if self.limit && sample <= -1.0 { + -1.0 } else { - output[i] = sample; - } - if output[i] > self.max { - self.max = output[i]; + sample + }; + if out_sample > self.max { + self.max = out_sample; } + output[i] += out_sample; } self.last = output[output.len() - 1]; } @@ -91,6 +175,11 @@ impl MixChannel { ui.add(egui::Slider::new(&mut self.gain, 0.0..=1.2).text("Gain").vertical()); self.levels_bar(ui, self.last); }); + + let btn = ui.button("Reset Gain"); + if btn.clicked() { + self.gain = 1.0; + } ui.horizontal(|ui| { ui.toggle_value(&mut self.mute, "M"); ui.toggle_value(&mut self.solo, "S"); @@ -107,7 +196,7 @@ impl MixChannel { let filled_rect = Rect::from_min_max(rect.min, rect.min + vec2(rect.width(), filled_height)); let remaining_rect = Rect::from_min_max(filled_rect.max, rect.max); painter.rect_filled(filled_rect, 0.0, Color32::from_rgb(0, 200, 0)); - painter.rect_filled(remaining_rect, 0.0, Color32::from_rgb(200, 0, 0)); +painter.rect_filled(remaining_rect, 0.0, Color32::from_rgb(200, 0, 0)); painter.rect_stroke(rect, 0.0, (1.0, Color32::WHITE)); response.on_hover_cursor(egui::CursorIcon::PointingHand) .on_hover_text(format!("{:.1} db", value.log10()));