use playr_core::audio::Cmd;
use crate::dispatch::Frontend;
use crate::message::Message;
pub const RANGE_DB: f32 = 60.0;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Law {
#[default]
Db,
Cubic,
}
impl Law {
pub const NAMES: [(&'static str, Law); 2] = [("db", Law::Db), ("cubic", Law::Cubic)];
pub fn name(self) -> &'static str {
match self {
Law::Db => "db",
Law::Cubic => "cubic",
}
}
pub fn named(name: &str) -> Option<Law> {
Self::NAMES.iter().find(|n| n.0 == name).map(|n| n.1)
}
pub fn gain(self, p: f32) -> f32 {
let p = p.clamp(0.0, 1.0);
match self {
Law::Db if p == 0.0 => 0.0,
Law::Db => 10f32.powf(RANGE_DB * (p - 1.0) / 20.0),
Law::Cubic => p * p * p,
}
}
pub fn position(self, gain: f32) -> f32 {
let g = gain.clamp(0.0, 1.0);
match self {
Law::Db if g == 0.0 => 0.0,
Law::Db => (1.0 + 20.0 * g.log10() / RANGE_DB).max(0.0),
Law::Cubic => g.cbrt(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Strip {
Master,
Player,
Tape,
Decks,
Headphones,
}
impl Strip {
pub const ALL: [Strip; 5] = [
Strip::Master,
Strip::Player,
Strip::Tape,
Strip::Decks,
Strip::Headphones,
];
pub fn name(self) -> &'static str {
match self {
Strip::Master => "master",
Strip::Player => "player",
Strip::Tape => "tape",
Strip::Decks => "decks",
Strip::Headphones => "headphones",
}
}
pub fn named(name: &str) -> Option<Strip> {
Self::ALL.into_iter().find(|s| s.name() == name)
}
fn index(self) -> usize {
self as usize
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Mix {
law: Law,
level: [f32; 5],
muted: [bool; 5],
eq_held: Option<[f32; 3]>,
interp: playr_dsp::Interp,
}
impl Mix {
pub fn new(law: Law, master: f32) -> Mix {
let mut mix = Mix {
law,
level: [1.0; 5],
muted: [false; 5],
eq_held: None,
interp: playr_dsp::Interp::default(),
};
mix.set_level(Strip::Master, master);
mix
}
pub fn law(&self) -> Law {
self.law
}
pub fn set_law(&mut self, law: Law) {
self.law = law;
}
pub fn level(&self, s: Strip) -> f32 {
self.level[s.index()]
}
pub fn set_level(&mut self, s: Strip, p: f32) {
if !p.is_nan() {
self.level[s.index()] = p.clamp(0.0, 1.0);
}
}
pub fn muted(&self, s: Strip) -> bool {
self.muted[s.index()]
}
pub fn set_muted(&mut self, s: Strip, on: bool) {
self.muted[s.index()] = on;
}
pub fn interp(&self) -> playr_dsp::Interp {
self.interp
}
pub fn set_interp(&mut self, interp: playr_dsp::Interp) {
self.interp = interp;
}
pub fn eq_held(&self) -> Option<[f32; 3]> {
self.eq_held
}
pub fn hold_eq(&mut self, bands: Option<[f32; 3]>) {
self.eq_held = bands;
}
pub fn gain(&self, s: Strip) -> f32 {
match s {
Strip::Master | Strip::Headphones => self.fader_gain(s),
Strip::Player | Strip::Tape | Strip::Decks => {
self.fader_gain(Strip::Master) * self.fader_gain(s)
}
}
}
pub fn fader_gain(&self, s: Strip) -> f32 {
match self.muted(s) {
true => 0.0,
false => self.law.gain(self.level(s)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MixAction {
Show,
Set(Strip, f32),
By(Strip, f32),
Mute(Strip, Option<bool>),
Law(Option<Law>),
Record,
}
pub fn act(f: &mut impl Frontend, action: MixAction) {
let mix = f.mix();
match action {
MixAction::Show => {
let shown = mix.clone();
f.notify(Message::Mix(shown));
return;
}
MixAction::Set(s, p) => mix.set_level(s, p),
MixAction::By(s, d) => mix.set_level(s, mix.level(s) + d),
MixAction::Mute(s, on) => {
let on = on.unwrap_or(!mix.muted(s));
mix.set_muted(s, on);
}
MixAction::Record => return record(f),
MixAction::Law(law) => {
let law = law.unwrap_or(match mix.law() {
Law::Db => Law::Cubic,
Law::Cubic => Law::Db,
});
mix.set_law(law);
f.notify(Message::FaderLaw(law));
}
}
send_player(f);
}
fn record(f: &mut impl Frontend) {
if f.session_mut().stop_master() {
return f.notify(Message::MasterStopping);
}
match f.session_mut().start_master() {
Ok(path) => f.notify(Message::MasterRecording(path)),
Err(e) => f.notify(Message::MasterFailed(e)),
}
}
pub fn poll(f: &mut impl Frontend) {
match f.session_mut().master_done() {
Some(Ok(done)) => f.notify(Message::MasterRecorded(done)),
Some(Err(e)) => f.notify(Message::MasterFailed(e)),
None => {}
}
}
pub fn send_player(f: &mut impl Frontend) {
let gain = f.mix().gain(Strip::Player);
f.session().send(Cmd::SetVolume(gain));
}