#![feature(trait_alias)] pub use tek_engine; pub(crate) use ConnectName::*; pub(crate) use ConnectScope::*; pub(crate) use ConnectStatus::*; mod device_struct; pub use self::device_struct::*; mod device_trait; pub use self::device_trait::*; mod device_type; pub use self::device_type::*; mod device_impl; pub(crate) use ::{ tek_engine::*, tek_engine::tengri::{ Usually, Perhaps, Has, MaybeHas, has, maybe_has, from, input::*, output::*, tui::*, tui::ratatui, tui::ratatui::widgets::{Widget, canvas::{Canvas, Line}}, tui::ratatui::prelude::{Rect, Style, Stylize, Buffer, Color::{self, *}}, }, std::{ cmp::Ord, ffi::OsString, fmt::{Debug, Formatter}, fs::File, path::PathBuf, sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed}}, } }; #[cfg(feature = "sampler")] pub(crate) use symphonia::{ core::{ formats::Packet, codecs::{Decoder, CODEC_TYPE_NULL}, //errors::Error as SymphoniaError, io::MediaSourceStream, probe::Hint, audio::SampleBuffer, }, default::get_codecs, }; #[cfg(feature = "lv2")] use std::thread::{spawn, JoinHandle}; #[cfg(feature = "lv2_gui")] use ::winit::{ application::ApplicationHandler, event::WindowEvent, event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, window::{Window, WindowId}, platform::x11::EventLoopBuilderExtX11 }; /// Define a type alias for iterators of sized items (columns). macro_rules! def_sizes_iter { ($Type:ident => $($Item:ty),+) => { pub trait $Type<'a> = Iterator + Send + Sync + 'a; } } pub fn view_transport ( play: bool, bpm: Arc>, beat: Arc>, time: Arc>, ) -> impl Content { let theme = ItemTheme::G[96]; Tui::bg(Black, row!(Bsp::a( Fill::XY(Align::w(button_play_pause(play))), Fill::XY(Align::e(row!( FieldH(theme, "BPM", bpm), FieldH(theme, "Beat", beat), FieldH(theme, "Time", time), ))) ))) } pub fn view_status ( sel: Option>, sr: Arc>, buf: Arc>, lat: Arc>, ) -> impl Content { let theme = ItemTheme::G[96]; Tui::bg(Black, row!(Bsp::a( Fill::XY(Align::w(sel.map(|sel|FieldH(theme, "Selected", sel)))), Fill::XY(Align::e(row!( FieldH(theme, "SR", sr), FieldH(theme, "Buf", buf), FieldH(theme, "Lat", lat), ))) ))) } pub fn button_play_pause (playing: bool) -> impl Content { let compact = true;//self.is_editing(); Tui::bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) }, Either::new(compact, Thunk::new(move|to: &mut TuiOut|to.place(&Fixed::X(9, Either::new(playing, Tui::fg(Rgb(0, 255, 0), " PLAYING "), Tui::fg(Rgb(255, 128, 0), " STOPPED "))) )), Thunk::new(move|to: &mut TuiOut|to.place(&Fixed::X(5, Either::new(playing, Tui::fg(Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)), Tui::fg(Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",)))) )) ) ) } pub fn swap_value ( target: &mut T, value: &T, returned: impl Fn(T)->U ) -> Perhaps { if *target == *value { Ok(None) } else { let mut value = value.clone(); std::mem::swap(target, &mut value); Ok(Some(returned(value))) } } pub fn toggle_bool ( target: &mut bool, value: &Option, returned: impl Fn(Option)->U ) -> Perhaps { let mut value = value.unwrap_or(!*target); if value == *target { Ok(None) } else { std::mem::swap(target, &mut value); Ok(Some(returned(Some(value)))) } } pub fn device_kinds () -> &'static [&'static str] { &[ #[cfg(feature = "sampler")] "Sampler", #[cfg(feature = "lv2")] "Plugin (LV2)", ] } impl>> HasDevices for T { fn devices (&self) -> &Vec { self.get() } fn devices_mut (&mut self) -> &mut Vec { self.get_mut() } } impl Device { pub fn name (&self) -> &str { match self { Self::Sampler(sampler) => sampler.name.as_ref(), _ => todo!(), } } pub fn midi_ins (&self) -> &[MidiInput] { match self { //Self::Sampler(Sampler { midi_in, .. }) => &[midi_in], _ => todo!() } } pub fn midi_outs (&self) -> &[MidiOutput] { match self { Self::Sampler(_) => &[], _ => todo!() } } pub fn audio_ins (&self) -> &[AudioInput] { match self { Self::Sampler(Sampler { audio_ins, .. }) => audio_ins.as_slice(), _ => todo!() } } pub fn audio_outs (&self) -> &[AudioOutput] { match self { Self::Sampler(Sampler { audio_outs, .. }) => audio_outs.as_slice(), _ => todo!() } } } audio!(|self: DeviceAudio<'a>, client, scope|{ use Device::*; match self.0 { Mute => { Control::Continue }, Bypass => { /*TODO*/ Control::Continue }, #[cfg(feature = "sampler")] Sampler(sampler) => sampler.process(client, scope), #[cfg(feature = "lv2")] Lv2(lv2) => lv2.process(client, scope), #[cfg(feature = "vst2")] Vst2 => { todo!() }, // TODO #[cfg(feature = "vst3")] Vst3 => { todo!() }, // TODO #[cfg(feature = "clap")] Clap => { todo!() }, // TODO #[cfg(feature = "sf2")] Sf2 => { todo!() }, // TODO } }); def_command!(ClockCommand: |clock: Clock| { SeekUsec { usec: f64 } => { clock.playhead.update_from_usec(*usec); Ok(None) }, SeekSample { sample: f64 } => { clock.playhead.update_from_sample(*sample); Ok(None) }, SeekPulse { pulse: f64 } => { clock.playhead.update_from_pulse(*pulse); Ok(None) }, SetBpm { bpm: f64 } => Ok(Some( Self::SetBpm { bpm: clock.timebase().bpm.set(*bpm) })), SetQuant { quant: f64 } => Ok(Some( Self::SetQuant { quant: clock.quant.set(*quant) })), SetSync { sync: f64 } => Ok(Some( Self::SetSync { sync: clock.sync.set(*sync) })), Play { position: Option } => { clock.play_from(*position)?; Ok(None) /* TODO Some(Pause(previousPosition)) */ }, Pause { position: Option } => { clock.pause_at(*position)?; Ok(None) }, TogglePlayback { position: u32 } => Ok(if clock.is_rolling() { clock.pause_at(Some(*position))?; None } else { clock.play_from(Some(*position))?; None }), }); def_command!(DeviceCommand: |device: Device| {}); def_command!(ClipCommand: |clip: MidiClip| { SetColor { color: Option } => { //(SetColor [t: usize, s: usize, c: ItemTheme] //clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o))))); //("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random()))) todo!() }, SetLoop { looping: Option } => { //(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}")) //("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap()))) todo!() } }); def_command!(BrowseCommand: |browse: Browse| { SetVisible => Ok(None), SetPath { address: PathBuf } => Ok(None), SetSearch { filter: Arc } => Ok(None), SetCursor { cursor: usize } => Ok(None), }); //take!(DeviceCommand|state: Arrangement, iter|state.selected_device().as_ref() //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); #[cfg(feature = "track")] pub fn view_track_row_section ( _theme: ItemTheme, button: impl Content, button_add: impl Content, content: impl Content, ) -> impl Content { Bsp::w(Fill::Y(Fixed::X(4, Align::nw(button_add))), Bsp::e(Fixed::X(20, Fill::Y(Align::nw(button))), Fill::XY(Align::c(content)))) } pub fn wrap (bg: Color, fg: Color, content: impl Content) -> impl Content { let left = Tui::fg_bg(bg, Reset, Fixed::X(1, Repeat::Y("▐"))); let right = Tui::fg_bg(bg, Reset, Fixed::X(1, Repeat::Y("▌"))); Bsp::e(left, Bsp::w(right, Tui::fg_bg(fg, bg, content))) } #[macro_export] macro_rules! has_clip { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? { fn clip (&$self) -> Option>> { $cb } } } } def_command!(MidiEditCommand: |editor: MidiEditor| { Show { clip: Option>> } => { editor.set_clip(clip.as_ref()); editor.redraw(); Ok(None) }, DeleteNote => { editor.redraw(); todo!() }, AppendNote { advance: bool } => { editor.put_note(*advance); editor.redraw(); Ok(None) }, SetNotePos { pos: usize } => { editor.set_note_pos((*pos).min(127)); editor.redraw(); Ok(None) }, SetNoteLen { len: usize } => { editor.set_note_len(*len); editor.redraw(); Ok(None) }, SetNoteScroll { scroll: usize } => { editor.set_note_lo((*scroll).min(127)); editor.redraw(); Ok(None) }, SetTimePos { pos: usize } => { editor.set_time_pos(*pos); editor.redraw(); Ok(None) }, SetTimeScroll { scroll: usize } => { editor.set_time_start(*scroll); editor.redraw(); Ok(None) }, SetTimeZoom { zoom: usize } => { editor.set_time_zoom(*zoom); editor.redraw(); Ok(None) }, SetTimeLock { lock: bool } => { editor.set_time_lock(*lock); editor.redraw(); Ok(None) }, // TODO: 1-9 seek markers that by default start every 8th of the clip }); fn to_key (note: usize) -> &'static str { match note % 12 { 11 | 9 | 7 | 5 | 4 | 2 | 0 => "████▌", 10 | 8 | 6 | 3 | 1 => " ", _ => unreachable!(), } } pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) -> impl Iterator { (note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n)) } fn draw_header (state: &Lv2, to: &mut TuiOut, x: u16, y: u16, w: u16) { let style = Style::default().gray(); let label1 = format!(" {}", state.name); to.blit(&label1, x + 1, y, Some(style.white().bold())); if let Some(ref path) = state.path { let label2 = format!("{}…", &path[..((w as usize - 10).min(path.len()))]); to.blit(&label2, x + 2 + label1.len() as u16, y, Some(style.not_dim())); } //Ok(Rect { x, y, width: w, height: 1 }) } #[cfg(feature = "lv2_gui")] pub fn run_lv2_ui (mut ui: LV2PluginUI) -> Usually> { Ok(spawn(move||{ let event_loop = EventLoop::builder().with_x11().with_any_thread(true).build().unwrap(); event_loop.set_control_flow(ControlFlow::Wait); event_loop.run_app(&mut ui).unwrap() })) } #[cfg(feature = "lv2_gui")] fn lv2_ui_instantiate (kind: &str) { //let host = Suil } pub fn mix_summing ( buffer: &mut [Vec], gain: f32, frames: usize, mut next: impl FnMut()->Option<[f32;N]>, ) -> bool { let channels = buffer.len(); for index in 0..frames { if let Some(frame) = next() { for (channel, sample) in frame.iter().enumerate() { let channel = channel % channels; buffer[channel][index] += sample * gain; } } else { return false } } true } pub fn mix_average ( buffer: &mut [Vec], gain: f32, frames: usize, mut next: impl FnMut()->Option<[f32;N]>, ) -> bool { let channels = buffer.len(); for index in 0..frames { if let Some(frame) = next() { for (channel, sample) in frame.iter().enumerate() { let channel = channel % channels; let value = buffer[channel][index]; buffer[channel][index] = (value + sample * gain) / 2.0; } } else { return false } } true } pub fn to_log10 (samples: &[f32]) -> f32 { let total: f32 = samples.iter().map(|x|x.abs()).sum(); let count = samples.len() as f32; 10. * (total / count).log10() } pub fn to_rms (samples: &[f32]) -> f32 { let sum = samples.iter() .map(|s|*s) .reduce(|sum, sample|sum + sample.abs()) .unwrap_or(0.0); (sum / samples.len() as f32).sqrt() } pub fn view_meter <'a> (label: &'a str, value: f32) -> impl Content + 'a { col!( FieldH(ItemTheme::G[128], label, format!("{:>+9.3}", value)), Fixed::XY(if value >= 0.0 { 13 } else if value >= -1.0 { 12 } else if value >= -2.0 { 11 } else if value >= -3.0 { 10 } else if value >= -4.0 { 9 } else if value >= -6.0 { 8 } else if value >= -9.0 { 7 } else if value >= -12.0 { 6 } else if value >= -15.0 { 5 } else if value >= -20.0 { 4 } else if value >= -25.0 { 3 } else if value >= -30.0 { 2 } else if value >= -40.0 { 1 } else { 0 }, 1, Tui::bg(if value >= 0.0 { Red } else if value >= -3.0 { Yellow } else { Green }, ()))) } pub fn view_meters (values: &[f32;2]) -> impl Content + use<'_> { let left = format!("L/{:>+9.3}", values[0]); let right = format!("R/{:>+9.3}", values[1]); Bsp::s(left, right) } def_command!(PoolCommand: |pool: Pool| { // Toggle visibility of pool Show { visible: bool } => { pool.visible = *visible; Ok(Some(Self::Show { visible: !visible })) }, // Select a clip from the clip pool Select { index: usize } => { pool.set_clip_index(*index); Ok(None) }, // Update the contents of the clip pool Clip { command: PoolClipCommand } => Ok(command.execute(pool)?.map(|command|Self::Clip{command})), // Rename a clip Rename { command: RenameCommand } => Ok(command.delegate(pool, |command|Self::Rename{command})?), // Change the length of a clip Length { command: CropCommand } => Ok(command.delegate(pool, |command|Self::Length{command})?), // Import from file Import { command: BrowseCommand } => Ok(if let Some(browse) = pool.browse.as_mut() { command.delegate(browse, |command|Self::Import{command})? } else { None }), // Export to file Export { command: BrowseCommand } => Ok(if let Some(browse) = pool.browse.as_mut() { command.delegate(browse, |command|Self::Export{command})? } else { None }), }); def_command!(PoolClipCommand: |pool: Pool| { Delete { index: usize } => { let index = *index; let clip = pool.clips_mut().remove(index).read().unwrap().clone(); Ok(Some(Self::Add { index, clip })) }, Swap { index: usize, other: usize } => { let index = *index; let other = *other; pool.clips_mut().swap(index, other); Ok(Some(Self::Swap { index, other })) }, Export { index: usize, path: PathBuf } => { todo!("export clip to midi file"); }, Add { index: usize, clip: MidiClip } => { let index = *index; let mut index = index; let clip = Arc::new(RwLock::new(clip.clone())); let mut clips = pool.clips_mut(); if index >= clips.len() { index = clips.len(); clips.push(clip) } else { clips.insert(index, clip); } Ok(Some(Self::Delete { index })) }, Import { index: usize, path: PathBuf } => { let index = *index; let bytes = std::fs::read(&path)?; let smf = Smf::parse(bytes.as_slice())?; let mut t = 0u32; let mut events = vec![]; for track in smf.tracks.iter() { for event in track.iter() { t += event.delta.as_int(); if let TrackEventKind::Midi { channel, message } = event.kind { events.push((t, channel.as_int(), message)); } } } let mut clip = MidiClip::new("imported", true, t as usize + 1, None, None); for event in events.iter() { clip.notes[event.0 as usize].push(event.2); } Ok(Self::Add { index, clip }.execute(pool)?) }, SetName { index: usize, name: Arc } => { let index = *index; let clip = &mut pool.clips_mut()[index]; let old_name = clip.read().unwrap().name.clone(); clip.write().unwrap().name = name.clone(); Ok(Some(Self::SetName { index, name: old_name })) }, SetLength { index: usize, length: usize } => { let index = *index; let clip = &mut pool.clips_mut()[index]; let old_len = clip.read().unwrap().length; clip.write().unwrap().length = *length; Ok(Some(Self::SetLength { index, length: old_len })) }, SetColor { index: usize, color: ItemColor } => { let index = *index; let mut color = ItemTheme::from(*color); std::mem::swap(&mut color, &mut pool.clips()[index].write().unwrap().color); Ok(Some(Self::SetColor { index, color: color.base })) }, }); def_command!(RenameCommand: |pool: Pool| { Begin => unreachable!(), Cancel => { if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() { pool.clips()[clip].write().unwrap().name = old_name.clone().into(); } Ok(None) }, Confirm => { if let Some(PoolMode::Rename(_clip, ref mut old_name)) = pool.mode_mut().clone() { let old_name = old_name.clone(); *pool.mode_mut() = None; return Ok(Some(Self::Set { value: old_name })) } Ok(None) }, Set { value: Arc } => { if let Some(PoolMode::Rename(clip, ref mut _old_name)) = pool.mode_mut().clone() { pool.clips()[clip].write().unwrap().name = value.clone(); } Ok(None) }, }); def_command!(CropCommand: |pool: Pool| { Begin => unreachable!(), Cancel => { if let Some(PoolMode::Length(..)) = pool.mode_mut().clone() { *pool.mode_mut() = None; } Ok(None) }, Set { length: usize } => { if let Some(PoolMode::Length(clip, ref mut length, ref mut _focus)) = pool.mode_mut().clone() { let old_length; { let clip = pool.clips()[clip].clone();//.write().unwrap(); old_length = Some(clip.read().unwrap().length); clip.write().unwrap().length = *length; } *pool.mode_mut() = None; return Ok(old_length.map(|length|Self::Set { length })) } Ok(None) }, Next => { if let Some(PoolMode::Length(_clip, ref mut _length, ref mut focus)) = pool.mode_mut().clone() { focus.next() }; Ok(None) }, Prev => { if let Some(PoolMode::Length(_clip, ref mut _length, ref mut focus)) = pool.mode_mut().clone() { focus.prev() }; Ok(None) }, Inc => { if let Some(PoolMode::Length(_clip, ref mut length, ref mut focus)) = pool.mode_mut().clone() { match focus { ClipLengthFocus::Bar => { *length += 4 * PPQ }, ClipLengthFocus::Beat => { *length += PPQ }, ClipLengthFocus::Tick => { *length += 1 }, } } Ok(None) }, Dec => { if let Some(PoolMode::Length(_clip, ref mut length, ref mut focus)) = pool.mode_mut().clone() { match focus { ClipLengthFocus::Bar => { *length = length.saturating_sub(4 * PPQ) }, ClipLengthFocus::Beat => { *length = length.saturating_sub(PPQ) }, ClipLengthFocus::Tick => { *length = length.saturating_sub(1) }, } } Ok(None) } }); def_command!(MidiInputCommand: |port: MidiInput| { Close => todo!(), Connect { midi_out: Arc } => todo!(), }); def_command!(MidiOutputCommand: |port: MidiOutput| { Close => todo!(), Connect { midi_in: Arc } => todo!(), }); def_command!(AudioInputCommand: |port: AudioInput| { Close => todo!(), Connect { audio_out: Arc } => todo!(), }); def_command!(AudioOutputCommand: |port: AudioOutput| { Close => todo!(), Connect { audio_in: Arc } => todo!(), }); def_sizes_iter!(InputsSizes => MidiInput); def_sizes_iter!(OutputsSizes => MidiOutput); def_sizes_iter!(PortsSizes => Arc, [Connect]); def_sizes_iter!(ScenesSizes => Scene); fn draw_info (sample: Option<&Arc>>) -> impl Content + use<'_> { When::new(sample.is_some(), Thunk::new(move|to: &mut TuiOut|{ let sample = sample.unwrap().read().unwrap(); let theme = sample.color; to.place(&row!( FieldH(theme, "Name", format!("{:<10}", sample.name.clone())), FieldH(theme, "Length", format!("{:<8}", sample.channels[0].len())), FieldH(theme, "Start", format!("{:<8}", sample.start)), FieldH(theme, "End", format!("{:<8}", sample.end)), FieldH(theme, "Trans", "0"), FieldH(theme, "Gain", format!("{}", sample.gain)), )) })) } fn draw_info_v (sample: Option<&Arc>>) -> impl Content + use<'_> { Either::new(sample.is_some(), Thunk::new(move|to: &mut TuiOut|{ let sample = sample.unwrap().read().unwrap(); let theme = sample.color; to.place(&Fixed::X(20, col!( Fill::X(Align::w(FieldH(theme, "Name ", format!("{:<10}", sample.name.clone())))), Fill::X(Align::w(FieldH(theme, "Length", format!("{:<8}", sample.channels[0].len())))), Fill::X(Align::w(FieldH(theme, "Start ", format!("{:<8}", sample.start)))), Fill::X(Align::w(FieldH(theme, "End ", format!("{:<8}", sample.end)))), Fill::X(Align::w(FieldH(theme, "Trans ", "0"))), Fill::X(Align::w(FieldH(theme, "Gain ", format!("{}", sample.gain)))), ))) }), Thunk::new(|to: &mut TuiOut|to.place(&Tui::fg(Red, col!( Tui::bold(true, "× No sample."), "[r] record", "[Shift-F9] import", ))))) } fn draw_status (sample: Option<&Arc>>) -> impl Content { Tui::bold(true, Tui::fg(Tui::g(224), sample .map(|sample|{ let sample = sample.read().unwrap(); format!("Sample {}-{}", sample.start, sample.end) }) .unwrap_or_else(||"No sample".to_string()))) } fn draw_sample ( to: &mut TuiOut, x: u16, y: u16, note: Option<&u7>, sample: &Sample, focus: bool ) -> Usually { let style = if focus { Style::default().green() } else { Style::default() }; if focus { to.blit(&"🬴", x+1, y, Some(style.bold())); } let label1 = format!("{:3} {:12}", note.map(|n|n.to_string()).unwrap_or(String::default()), sample.name); let label2 = format!("{:>6} {:>6} +0.0", sample.start, sample.end); to.blit(&label1, x+2, y, Some(style.bold())); to.blit(&label2, x+3+label1.len()as u16, y, Some(style)); Ok(label1.len() + label2.len() + 4) } fn read_sample_data (_: &str) -> Usually<(usize, Vec>)> { todo!(); } fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { let (mut subdirs, mut files) = std::fs::read_dir(dir)? .fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{ let entry = entry.expect("failed to read drectory entry"); let meta = entry.metadata().expect("failed to read entry metadata"); if meta.is_file() { files.push(entry.file_name()); } else if meta.is_dir() { subdirs.push(entry.file_name()); } (subdirs, files) }); subdirs.sort(); files.sort(); Ok((subdirs, files)) } def_command!(SamplerCommand: |sampler: Sampler| { RecordToggle { slot: usize } => { let slot = *slot; let recording = sampler.recording.as_ref().map(|x|x.0); let _ = Self::RecordFinish.execute(sampler)?; // autoslice: continue recording at next slot if recording != Some(slot) { Self::RecordBegin { slot }.execute(sampler) } else { Ok(None) } }, RecordBegin { slot: usize } => { let slot = *slot; sampler.recording = Some(( slot, Some(Arc::new(RwLock::new(Sample::new( "Sample", 0, 0, vec![vec![];sampler.audio_ins.len()] )))) )); Ok(None) }, RecordFinish => { let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{ std::mem::swap(sample, &mut sampler.mapped[*index]); sample }); // TODO: undo Ok(None) }, RecordCancel => { sampler.recording = None; Ok(None) }, PlaySample { slot: usize } => { let slot = *slot; if let Some(ref sample) = sampler.mapped[slot] { sampler.voices.write().unwrap().push(Sample::play(sample, 0, &u7::from(128))); } Ok(None) }, StopSample { slot: usize } => { let slot = *slot; todo!(); Ok(None) }, }); def_command!(FileBrowserCommand: |sampler: Sampler|{ //("begin" [] Some(Self::Begin)) //("cancel" [] Some(Self::Cancel)) //("confirm" [] Some(Self::Confirm)) //("select" [i: usize] Some(Self::Select(i.expect("no index")))) //("chdir" [p: PathBuf] Some(Self::Chdir(p.expect("no path")))) //("filter" [f: Arc] Some(Self::Filter(f.expect("no filter"))))) }); def_command!(SceneCommand: |scene: Scene| { SetSize { size: usize } => { todo!() }, SetZoom { size: usize } => { todo!() }, SetName { name: Arc } => swap_value(&mut scene.name, name, |name|Self::SetName{name}), SetColor { color: ItemTheme } => swap_value(&mut scene.color, color, |color|Self::SetColor{color}), }); def_sizes_iter!(TracksSizes => Track); def_command!(TrackCommand: |track: Track| { Stop => { track.sequencer.enqueue_next(None); Ok(None) }, SetMute { mute: Option } => todo!(), SetSolo { solo: Option } => todo!(), SetSize { size: usize } => todo!(), SetZoom { zoom: usize } => todo!(), SetName { name: Arc } => swap_value(&mut track.name, name, |name|Self::SetName { name }), SetColor { color: ItemTheme } => swap_value(&mut track.color, color, |color|Self::SetColor { color }), SetRec { rec: Option } => toggle_bool(&mut track.sequencer.recording, rec, |rec|Self::SetRec { rec }), SetMon { mon: Option } => toggle_bool(&mut track.sequencer.monitoring, mon, |mon|Self::SetMon { mon }), }); pub(crate) fn track_width (_index: usize, track: &Track) -> u16 { track.width as u16 } fn view_track_header (theme: ItemTheme, content: impl Content) -> impl Content { Fixed::X(12, Tui::bg(theme.darker.rgb, Fill::X(Align::e(content)))) } fn view_ports_status <'a, T: JackPort> (theme: ItemTheme, title: &'a str, ports: &'a [T]) -> impl Content + use<'a, T> { let ins = ports.len() as u16; let frame = Outer(true, Style::default().fg(Tui::g(96))); let iter = move||ports.iter(); let names = Map::south(1, iter, move|port, index|Fill::Y(Align::w(format!(" {index} {}", port.port_name())))); let field = FieldV(theme, title, names); Fixed::XY(20, 1 + ins, frame.enclose(Fixed::XY(20, 1 + ins, field))) } pub(crate) fn per_track_top <'a, T: Content + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a ) -> impl Content + 'a { Align::x(Tui::bg(Reset, Map::new(tracks, move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{ let width = (x2 - x1) as u16; map_east(x1 as u16, width, Fixed::X(width, Tui::fg_bg( track.color.lightest.rgb, track.color.base.rgb, callback(index, track))))}))) } pub(crate) fn per_track <'a, T: Content + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a ) -> impl Content + 'a { per_track_top(tracks, move|index, track|Fill::Y(Align::y(callback(index, track)))) } pub(crate) fn io_ports <'a, T: PortsSizes<'a>> ( fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a ) -> impl Content + 'a { Map::new(iter, move|( _index, name, connections, y, y2 ): (usize, &'a Arc, &'a [Connect], usize, usize), _| map_south(y as u16, (y2-y) as u16, Bsp::s( Fill::Y(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(&" 󰣲 ", name))))), Map::new(||connections.iter(), move|connect: &'a Connect, index|map_south(index as u16, 1, Fill::Y(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, &connect.info))))))))) } #[cfg(feature = "vst2")] fn set_vst_plugin ( host: &Arc>>, _path: &str ) -> Usually { let mut loader = ::vst::host::PluginLoader::load( &std::path::Path::new("/nix/store/ij3sz7nqg5l7v2dygdvzy3w6cj62bd6r-helm-0.9.0/lib/lxvst/helm.so"), host.clone() )?; Ok(PluginKind::VST2 { instance: loader.instance()? }) }