From 3bae9053ce8ed19c0eaf147b48c37c206a19513a Mon Sep 17 00:00:00 2001 From: Josh Jeppson Date: Wed, 13 Nov 2024 13:40:16 -0700 Subject: [PATCH] initial commit --- .gitignore | 1 + Cargo.toml | 10 ++++ src/main.rs | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..480e85e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "fslcmix" +version = "0.1.0" +edition = "2021" + +[dependencies] +clap = "4.5.20" +eframe = "0.29.1" +egui = "0.29.1" +jack = "0.13.0" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..903501f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,129 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +use eframe::egui::*; + +fn main() -> eframe::Result { + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default(), + ..Default::default() + }; + eframe::run_native( + "FSLCMix", + options, + Box::new(|cc| { + // Dark theme + cc.egui_ctx.set_theme(egui::Theme::Dark); + // egui_extras::install_image_loaders(&cc.egui_ctx); + + Ok(Box::::new(FslcMix::new(10))) + }), + ) +} + +struct FslcMix { + channels: Vec, + master: MixChannel, +} + +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() } + } + } +} + +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) + .show(ctx, |ui|{ + ui.vertical(|ui| { + ui.label("Licensed under the GPLv3."); + }); + ui.horizontal(|ui| { + self.master.ui(ui); + ui.separator(); + for channel in &mut self.channels { + channel.ui(ui); + } + }); + }); + } +} + +struct MixChannel { + gain: f32, + last: f32, + max: f32, + channel_name: String, + limit: bool, + mute: bool, + solo: bool, +} + +impl MixChannel { + fn mix(&mut self, input : &[f32], output : &mut [f32]) { + // 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; + } else { + output[i] = sample; + } + if output[i] > self.max { + self.max = output[i]; + } + } + self.last = output[output.len() - 1]; + } + + fn ui(&mut self, ui : &mut egui::Ui) { + ui.vertical(|ui| { + ui.horizontal(|ui| { + ui.label(format!("Peak: {} dB", self.max.log10())); + }); + ui.horizontal(|ui| { + ui.add(egui::Slider::new(&mut self.gain, 0.0..=1.2).text("Gain").vertical()); + self.levels_bar(ui, self.last); + }); + ui.horizontal(|ui| { + ui.toggle_value(&mut self.mute, "M"); + ui.toggle_value(&mut self.solo, "S"); + ui.toggle_value(&mut self.limit, "Limit"); + }); + ui.add(egui::TextEdit::singleline(&mut self.channel_name).desired_width(75.0)); + }); + } + + fn levels_bar(&self, ui: &mut Ui, value: f32) { + let (rect, response) = ui.allocate_exact_size(vec2(20.0, 200.0), egui::Sense::hover()); + let painter = ui.painter(); + let filled_height = rect.height() * value / 1.2; // Show a bit over max amplitude + 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_stroke(rect, 0.0, (1.0, Color32::WHITE)); + response.on_hover_cursor(egui::CursorIcon::PointingHand) + .on_hover_text(format!("{:.1} db", value.log10())); + } +} + +impl Default for MixChannel { + fn default() -> Self { + Self { + gain: 1.0, + last: 0.0, + max: 0.0, + channel_name: "Channel".to_owned(), + limit: false, + mute: false, + solo: false, + } + } +}