some fun stuff
This commit is contained in:
parent
6c3cb87fe9
commit
b35e84a051
2 changed files with 105 additions and 16 deletions
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "4.5.20"
|
clap = { version = "4.5.20", features = ["derive"] }
|
||||||
eframe = "0.29.1"
|
eframe = "0.29.1"
|
||||||
egui = "0.29.1"
|
egui = "0.29.1"
|
||||||
jack = "0.13.0"
|
jack = "0.13.0"
|
||||||
|
|
|
||||||
119
src/main.rs
119
src/main.rs
|
|
@ -1,8 +1,10 @@
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
use eframe::egui::*;
|
use eframe::egui::*;
|
||||||
|
|
||||||
fn main() -> eframe::Result {
|
fn main() -> eframe::Result {
|
||||||
|
let args = Args::parse();
|
||||||
let options = eframe::NativeOptions {
|
let options = eframe::NativeOptions {
|
||||||
viewport: egui::ViewportBuilder::default(),
|
viewport: egui::ViewportBuilder::default(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -15,33 +17,104 @@ fn main() -> eframe::Result {
|
||||||
cc.egui_ctx.set_theme(egui::Theme::Dark);
|
cc.egui_ctx.set_theme(egui::Theme::Dark);
|
||||||
// egui_extras::install_image_loaders(&cc.egui_ctx);
|
// 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 {
|
struct FslcMix {
|
||||||
channels: Vec<MixChannel>,
|
channels: Vec<MixChannel>,
|
||||||
master: MixChannel,
|
master: MixChannel,
|
||||||
|
normalize: bool,
|
||||||
|
ui_size: egui::Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FslcMix {
|
impl FslcMix {
|
||||||
fn new(num_channels: u8) -> Self {
|
fn new(num_channels: u8) -> Self {
|
||||||
Self {
|
Self {
|
||||||
channels: (0..num_channels).map(|_| MixChannel::default()).collect(),
|
channels: (1..=num_channels).map(|i|
|
||||||
master: MixChannel { channel_name: "Master".to_owned(), ..Default::default() }
|
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 {
|
impl eframe::App for FslcMix {
|
||||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||||
egui::Window::new("Mixer (FSLCMix)")
|
//egui::Window::new("Mixer (FSLCMix)")
|
||||||
.default_pos([100.0, 100.0])
|
// .default_pos([100.0, 100.0])
|
||||||
.title_bar(true)
|
// .title_bar(true)
|
||||||
|
egui::CentralPanel::default()
|
||||||
.show(ctx, |ui|{
|
.show(ctx, |ui|{
|
||||||
ui.vertical(|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| {
|
ui.horizontal(|ui| {
|
||||||
self.master.ui(ui);
|
self.master.ui(ui);
|
||||||
|
|
@ -50,7 +123,10 @@ impl eframe::App for FslcMix {
|
||||||
channel.ui(ui);
|
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 {
|
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
|
// Sanity check
|
||||||
assert!(input.len() == output.len());
|
assert!(input.len() == output.len());
|
||||||
for i in 0..input.len() {
|
for i in 0..input.len() {
|
||||||
let sample = input[i] * self.gain;
|
let sample = input[i] * self.gain;
|
||||||
if self.limit && sample >= 1.0 {
|
let out_sample = if self.limit && sample >= 1.0 {
|
||||||
output[i] = 1.0;
|
1.0
|
||||||
|
} else if self.limit && sample <= -1.0 {
|
||||||
|
-1.0
|
||||||
} else {
|
} else {
|
||||||
output[i] = sample;
|
sample
|
||||||
}
|
};
|
||||||
if output[i] > self.max {
|
if out_sample > self.max {
|
||||||
self.max = output[i];
|
self.max = out_sample;
|
||||||
}
|
}
|
||||||
|
output[i] += out_sample;
|
||||||
}
|
}
|
||||||
self.last = output[output.len() - 1];
|
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());
|
ui.add(egui::Slider::new(&mut self.gain, 0.0..=1.2).text("Gain").vertical());
|
||||||
self.levels_bar(ui, self.last);
|
self.levels_bar(ui, self.last);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let btn = ui.button("Reset Gain");
|
||||||
|
if btn.clicked() {
|
||||||
|
self.gain = 1.0;
|
||||||
|
}
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.toggle_value(&mut self.mute, "M");
|
ui.toggle_value(&mut self.mute, "M");
|
||||||
ui.toggle_value(&mut self.solo, "S");
|
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 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);
|
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(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));
|
painter.rect_stroke(rect, 0.0, (1.0, Color32::WHITE));
|
||||||
response.on_hover_cursor(egui::CursorIcon::PointingHand)
|
response.on_hover_cursor(egui::CursorIcon::PointingHand)
|
||||||
.on_hover_text(format!("{:.1} db", value.log10()));
|
.on_hover_text(format!("{:.1} db", value.log10()));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue