some fun stuff

This commit is contained in:
Josh Jeppson 2024-11-13 18:39:15 -07:00
parent 6c3cb87fe9
commit b35e84a051
2 changed files with 105 additions and 16 deletions

View file

@ -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"

View file

@ -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::<FslcMix>::new(FslcMix::new(5)))
Ok(Box::<FslcMix>::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<MixChannel>,
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()));