use crate::prelude::*; pub struct Mixer { name: String, jack: Jack, tracks: Vec, selected_track: usize, selected_column: usize, } impl Mixer { pub fn new (name: &str) -> Result, Box> { let (client, status) = Client::new( name, ClientOptions::NO_START_SERVER )?; let jack = client.activate_async( Notifications, ClosureProcessHandler::new(Box::new( move |_: &Client, _: &ProcessScope| -> Control { Control::Continue }) as BoxControl + Send> ) )?; Ok(DynamicDevice::new(render, handle, |_|{}, Self { name: name.into(), selected_column: 0, selected_track: 1, tracks: vec![ Track::new(&jack.as_client(), 1, "Mono 1")?, Track::new(&jack.as_client(), 1, "Mono 2")?, Track::new(&jack.as_client(), 2, "Stereo 1")?, Track::new(&jack.as_client(), 2, "Stereo 2")?, Track::new(&jack.as_client(), 2, "Stereo 3")?, Track::new(&jack.as_client(), 2, "Bus 1")?, Track::new(&jack.as_client(), 2, "Bus 2")?, Track::new(&jack.as_client(), 2, "Mix")?, ], jack, })) } } pub fn render (state: &Mixer, buf: &mut Buffer, mut area: Rect) -> Usually<(u16, u16)> { if area.height < 2 { return Ok((0, 0)) } area.x = area.width.saturating_sub(80) / 2; area.width = area.width.min(80); area.height = state.tracks.len() as u16 + 2; draw_box(buf, area); let x = area.x + 1; let y = area.y + 1; let h = area.height - 2; for (i, track) in state.tracks.iter().enumerate() { //buf.set_string( //x, y + index as u16, //&track.name, Style::default().bold().not_dim() //); for (j, (column, field)) in [ (0, format!(" {:10} ", track.name)), (12, format!(" {:.1}dB ", track.gain)), (22, format!(" [ ] ")), (30, format!(" C ")), (35, format!(" {:.1}dB ", track.level)), (45, format!(" [ ] ")), (51, format!(" {:7} ", track.route)), ].into_iter().enumerate() { buf.set_string( x + column as u16, y + i as u16, field, if state.selected_track == i && state.selected_column == j { Style::default().white().bold().not_dim() } else { Style::default().not_dim() } ); //stdout.queue(move_to(column, row))?; //if state.selected_track == i && state.selected_column == j { //stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?; //} else { //stdout.queue(PrintStyledContent(field.to_string().bold()))?; //} //fn render_meters ( //state: &mut Mixer, //stdout: &mut Stdout, //offset: (u16, u16) //) -> Result<(), Box> { //let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row); //for (i, track) in state.tracks.iter().enumerate() { //let row = (i + 1) as u16; //stdout //.queue(move_to(10, row))?.queue(PrintStyledContent("▁".green()))? //.queue(move_to(20, row))?.queue(PrintStyledContent("▁".green()))? //.queue(move_to(28, row))?.queue(PrintStyledContent("▁".green()))? //.queue(move_to(43, row))?.queue(PrintStyledContent("▁".green()))?; //} //Ok(()) //} } } Ok((20,10)) } pub fn handle (state: &mut Mixer, event: &EngineEvent) -> Result<(), Box> { if let EngineEvent::Input(crossterm::event::Event::Key(event)) = event { match event.code { //KeyCode::Char('c') => { //if event.modifiers == KeyModifiers::CONTROL { //state.exit(); //} //}, KeyCode::Down => { state.selected_track = (state.selected_track + 1) % state.tracks.len(); println!("{}", state.selected_track); }, KeyCode::Up => { if state.selected_track == 0 { state.selected_track = state.tracks.len() - 1; } else { state.selected_track = state.selected_track - 1; } println!("{}", state.selected_track); }, KeyCode::Left => { if state.selected_column == 0 { state.selected_column = 6 } else { state.selected_column = state.selected_column - 1; } }, KeyCode::Right => { if state.selected_column == 6 { state.selected_column = 0 } else { state.selected_column = state.selected_column + 1; } }, _ => { println!("\n{event:?}"); } } } Ok(()) } // TODO: // - Meters: propagate clipping: // - If one stage clips, all stages after it are marked red // - If one track clips, all tracks that feed from it are marked red? pub const ACTIONS: [(&'static str, &'static str);2] = [ ("+/-", "Adjust"), ("Ins/Del", "Add/remove track"), ]; pub struct Track { name: String, channels: u8, input_ports: Vec>, pre_gain_meter: f64, gain: f64, insert_ports: Vec>, return_ports: Vec>, post_gain_meter: f64, post_insert_meter: f64, level: f64, pan: f64, output_ports: Vec>, post_fader_meter: f64, route: String, } impl Track { pub fn new (jack: &Client, channels: u8, name: &str) -> Result> { let mut input_ports = vec![]; let mut insert_ports = vec![]; let mut return_ports = vec![]; let mut output_ports = vec![]; for channel in 1..=channels { input_ports.push(jack.register_port(&format!("{name} [input {channel}]"), AudioIn::default())?); output_ports.push(jack.register_port(&format!("{name} [out {channel}]"), AudioOut::default())?); let insert_port = jack.register_port(&format!("{name} [pre {channel}]"), AudioOut::default())?; let return_port = jack.register_port(&format!("{name} [insert {channel}]"), AudioIn::default())?; jack.connect_ports(&insert_port, &return_port)?; insert_ports.push(insert_port); return_ports.push(return_port); } Ok(Self { name: name.into(), channels, input_ports, pre_gain_meter: 0.0, gain: 0.0, post_gain_meter: 0.0, insert_ports, return_ports, post_insert_meter: 0.0, level: 0.0, pan: 0.0, post_fader_meter: 0.0, route: "---".into(), output_ports, }) } } pub struct Notifications; impl NotificationHandler for Notifications { fn thread_init (&self, _: &Client) { } fn shutdown (&mut self, status: ClientStatus, reason: &str) { } fn freewheel (&mut self, _: &Client, is_enabled: bool) { } fn sample_rate (&mut self, _: &Client, _: Frames) -> Control { Control::Quit } fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) { } fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) { } fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { Control::Continue } fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) { } fn graph_reorder (&mut self, _: &Client) -> Control { Control::Continue } fn xrun (&mut self, _: &Client) -> Control { Control::Continue } } //impl Input, bool> for Mixer { //fn handle (&mut self, engine: &mut TUI) -> Result> { //Ok(None) //} //} //impl Output, [u16;2]> for Mixer { //fn render (&self, envine: &mut TUI) -> Result> { //let tracks_table = Columns::new() //.add(titles) //.add(input_meters) //.add(gains) //.add(gain_meters) //.add(pres) //.add(pre_meters) //.add(levels) //.add(pans) //.add(pan_meters) //.add(posts) //.add(routes) //Rows::new() //.add(Columns::new() //.add(Rows::new() //.add("[Arrows]".bold()) //.add("Navigate")) //.add(Rows::new() //.add("[+/-]".bold()) //.add("Adjust")) //.add(Rows::new() //.add("[Ins/Del]".bold()) //.add("Add/remove track"))) //.add(tracks_table) //.render(engine) //} //}