mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-10-08 18:56:42 +02:00
1011 lines
37 KiB
Rust
1011 lines
37 KiB
Rust
use crate::*;
|
|
|
|
tui_keys!(self: App, input {
|
|
#[cfg(feature = "prof2")] profiling::scope!("App::tui_keys!");
|
|
self.input_cmds(input)
|
|
});
|
|
|
|
impl App {
|
|
fn input_cmds (&mut self, input: &TuiEvent) -> Usually<()> {
|
|
if let Some(bindings) = Self::input_cmds_find(self, input) {
|
|
return Self::input_cmds_exec(self, bindings)
|
|
}
|
|
Ok(())
|
|
}
|
|
fn input_cmds_find (state: &Self, input: &TuiEvent) -> Option<Arc<[Binding<Arc<str>>]>> {
|
|
for name in state.modes.read().iter().rev() {
|
|
let mode = state.config.get_mode(name).as_ref().map(Arc::clone);
|
|
if let Some(mode) = mode {
|
|
let binds = state.config.binds.clone();
|
|
for id in mode.keys.iter() {
|
|
if let Some(event_map) = binds.find(id)
|
|
&& let Some(bindings) = event_map.query(input) {
|
|
return Some(bindings.into())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
fn input_cmds_exec (state: &mut Self, bindings: Arc<[Binding<Arc<str>>]>) -> Usually<()> {
|
|
for binding in bindings.iter() {
|
|
for src in binding.commands.iter() {
|
|
if let Some(command) = Namespace::<AppCmd>::namespace(state, &src)? {
|
|
Self::cmd_dispatch(state, command)?;
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
fn cmd_dispatch (state: &mut Self, command: AppCmd) -> Usually<()> {
|
|
match command.clone().dispatch(state) {
|
|
Ok(undo) => {
|
|
state.history.push((command, undo));
|
|
Ok(())
|
|
},
|
|
Err(err) => {
|
|
state.history.push((command, None));
|
|
Err(err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl_from!(AppCmd: |x: AxisCmd| AppCmd::Axis { command: x });
|
|
impl_from!(AppCmd: |x: BrowseCmd| AppCmd::Browse { command: x });
|
|
impl_from!(AppCmd: |x: MidiClipCmd| AppCmd::MidiClip { command: x });
|
|
impl_from!(AppCmd: |x: MidiEditCmd| AppCmd::MidiEdit { command: x });
|
|
impl_from!(AppCmd: |x: ProjectCmd| AppCmd::Project { command: x });
|
|
impl_from!(AppCmd: |x: SceneCmd| AppCmd::Scene { command: x });
|
|
impl_from!(AppCmd: |x: ScenesCmd| AppCmd::Scenes { command: x });
|
|
impl_from!(AppCmd: |x: TrackCmd| AppCmd::Track { command: x });
|
|
impl_from!(AppCmd: |x: TracksCmd| AppCmd::Tracks { command: x });
|
|
|
|
#[tek_proc::commands(AppCmd)] impl App {
|
|
/// Exit the application.
|
|
#[command(Exit = "exit")] pub fn cmd_exit (&mut self) -> Perhaps<AppCmd> {
|
|
self.exit.exit();
|
|
Ok(None)
|
|
}
|
|
/// Switch to another set of contextual modes.
|
|
#[command(Mode = "mode")] pub fn cmd_mode (&mut self, modes: Vec<Arc<str>>) -> Perhaps<AppCmd> {
|
|
*self.modes_mut() = modes.into();
|
|
Ok(None)
|
|
}
|
|
/// Enter a contextual mode.
|
|
#[command(Enter = "enter")] pub fn enter_mode (&mut self, mode: Arc<str>) -> Perhaps<AppCmd> {
|
|
let mut modes = self.modes_mut();
|
|
Ok(if modes.last() != Some(&mode) {
|
|
modes.push(mode);
|
|
Some(AppCmd::Leave)
|
|
} else {
|
|
None
|
|
})
|
|
}
|
|
/// Leave a contextual mode.
|
|
#[command(Leave = "leave")] fn leave_mode (&mut self) -> Perhaps<AppCmd> {
|
|
let mut modes = self.modes_mut();
|
|
if modes.len() > 1 {
|
|
let mode = modes.pop();
|
|
Ok(mode.map(|mode|AppCmd::Enter { mode }))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
/// Navigate along a contextual axis.
|
|
#[command(Axis = "axis")] pub fn cmd_axis (&mut self, command: AxisCmd) -> Perhaps<AppCmd> {
|
|
Ok(command.dispatch(self)?.map(Into::into))
|
|
}
|
|
/// Cancel current selection.
|
|
#[command(Cancel = "cancel")] pub fn cmd_cancel (&mut self) -> Perhaps<AppCmd> {
|
|
todo!("cancel")
|
|
}
|
|
/// Confirm current selection.
|
|
#[command(Confirm = "confirm")] pub fn cmd_confirm (&mut self) -> Perhaps<AppCmd> {
|
|
Ok(match self.dialog.clone() {
|
|
Dialog::Menu(index, items) => {
|
|
for command in items[index].1.iter() {
|
|
App::cmd_dispatch(self, command.clone())?;
|
|
}
|
|
None
|
|
}
|
|
_ => todo!(),
|
|
})
|
|
}
|
|
/// Swap currently active modal dialog.
|
|
///
|
|
/// ```
|
|
/// let _ = tek::App::default().cmd_dialog(tek::Dialog::welcome());
|
|
/// ```
|
|
#[command(Dialog = "dialog")] pub fn cmd_dialog (&mut self, dialog: Dialog) -> Perhaps<AppCmd> {
|
|
let mut dialog = dialog.clone();
|
|
std::mem::swap(&mut self.dialog, &mut dialog);
|
|
Ok(Some(AppCmd::Dialog { dialog }))
|
|
}
|
|
/// Command the file browser.
|
|
#[command(Browse = "browse")] pub fn cmd_browse (&mut self, command: BrowseCmd) -> Perhaps<AppCmd> {
|
|
Ok(command.dispatch(self)?.map(Into::into))
|
|
}
|
|
/// Commands that alter the current session.
|
|
#[command(Project = "session")] pub fn cmd_session (&mut self, command: ProjectCmd) -> Perhaps<AppCmd> {
|
|
Ok(command.dispatch(self)?.map(Into::into))
|
|
}
|
|
/// Commands pertaining to MIDI clips.
|
|
#[command(MidiClip = "clip")] pub fn cmd_midi_clip (&mut self, command: MidiClipCmd) -> Perhaps<AppCmd> {
|
|
Ok(command.dispatch(self)?.map(Into::into))
|
|
}
|
|
/// Commands pertaining to the MIDI clip editor.
|
|
#[command(MidiEdit = "edit")] pub fn cmd_midi_edit (&mut self, command: MidiEditCmd) -> Perhaps<AppCmd> {
|
|
if matches!(command, MidiEditCmd::Toggle) {
|
|
if self.is_editing() {
|
|
self.leave_mode()?;
|
|
} else {
|
|
self.enter_mode("Edit".into())?;
|
|
}
|
|
}
|
|
Ok(command.dispatch(self)?.map(Into::into))
|
|
}
|
|
#[command(Scene = "scene")] pub fn cmd_scene (&mut self, command: SceneCmd) -> Perhaps<AppCmd> {
|
|
Ok(command.dispatch(self)?.map(Into::into))
|
|
}
|
|
#[command(Scenes = "scenes")] pub fn cmd_scenes (&mut self, command: ScenesCmd) -> Perhaps<AppCmd> {
|
|
Ok(command.dispatch(self)?.map(Into::into))
|
|
}
|
|
#[command(Track = "track")] pub fn cmd_track (&mut self, command: TrackCmd) -> Perhaps<AppCmd> {
|
|
Ok(command.dispatch(self)?.map(Into::into))
|
|
}
|
|
#[command(Tracks = "tracks")] pub fn cmd_tracks (&mut self, command: TracksCmd) -> Perhaps<AppCmd> {
|
|
Ok(command.dispatch(self)?.map(Into::into))
|
|
}
|
|
//#[command(Pool = "pool")] pub fn cmd_pool (&mut self, command: PoolCmd) -> Perhaps<AppCmd> {
|
|
//Ok(command.dispatch(self)?.map(Into::into))
|
|
//}
|
|
}
|
|
|
|
impl<T: HasProject> ProjectControl for T {}
|
|
|
|
#[tek_proc::commands(ProjectCmd)] pub trait ProjectControl: HasProject {
|
|
#[command(LoadLast = "load-last")] fn load_last (&mut self) -> Perhaps<ProjectCmd> {
|
|
todo!()
|
|
}
|
|
#[command(Load = "load")] fn load (&mut self) -> Perhaps<ProjectCmd> {
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
impl<T> ClockControl for T where T: HasClock
|
|
+ Namespace<u32> + Namespace<f64> + Namespace<Option<u32>>
|
|
{}
|
|
#[tek_proc::commands(ClockCmd = "clock")] pub trait ClockControl: HasClock
|
|
+ Namespace<u32> + Namespace<f64> + Namespace<Option<u32>>
|
|
{
|
|
#[command(SeekUsec = "usec")] fn seek_usec (&mut self, usec: f64) -> Perhaps<ClockCmd> {
|
|
self.clock().playhead.update_from_usec(usec);
|
|
Ok(None)
|
|
}
|
|
#[command(SeekSample = "sample")] fn seek_sample (&mut self, sample: f64) -> Perhaps<ClockCmd> {
|
|
self.clock().playhead.update_from_sample(sample);
|
|
Ok(None)
|
|
}
|
|
#[command(SeekPulse = "pulse")] fn seek_pulse (&mut self, pulse: f64) -> Perhaps<ClockCmd> {
|
|
self.clock().playhead.update_from_pulse(pulse);
|
|
Ok(None)
|
|
}
|
|
#[command(SetBpm = "bpm")] fn set_bpm (&mut self, bpm: f64) -> Perhaps<ClockCmd> {
|
|
Ok(Some(ClockCmd::SetBpm {
|
|
bpm: self.clock().timebase().bpm.set(bpm)
|
|
}))
|
|
}
|
|
#[command(SetQuant = "quant")] fn set_quant (&mut self, quant: f64) -> Perhaps<ClockCmd> {
|
|
Ok(Some(ClockCmd::SetQuant {
|
|
quant: self.clock().quant.set(quant)
|
|
}))
|
|
}
|
|
#[command(SetSync = "sync")] fn set_sync (&mut self, sync: f64) -> Perhaps<ClockCmd> {
|
|
Ok(Some(ClockCmd::SetSync {
|
|
sync: self.clock().sync.set(sync)
|
|
}))
|
|
}
|
|
#[command(Play = "play")] fn play (&mut self, position: Option<u32>) -> Perhaps<ClockCmd> {
|
|
self.clock().play_from(position)?;
|
|
Ok(None) /* TODO Some(Pause(previousPosition)) */
|
|
}
|
|
#[command(Pause = "pause")] fn pause (&mut self, position: Option<u32>) -> Perhaps<ClockCmd> {
|
|
self.clock().pause_at(position)?;
|
|
Ok(None)
|
|
}
|
|
#[command(TogglePlayback = "toggle")] fn toggle (&mut self, position: u32) -> Perhaps<ClockCmd> {
|
|
Ok(if self.clock().is_rolling() {
|
|
self.clock().pause_at(Some(position))?;
|
|
None
|
|
} else {
|
|
self.clock().play_from(Some(position))?;
|
|
None
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<T> TracksControl for T where T: HasTracks
|
|
+ HasScenes
|
|
+ HasSelection
|
|
+ HasJack<'static>
|
|
+ Namespace<ClipPool>
|
|
{}
|
|
|
|
#[tek_proc::commands(TracksCmd)] pub trait TracksControl: HasTracks
|
|
+ HasScenes
|
|
+ HasSelection
|
|
+ HasJack<'static>
|
|
+ Namespace<ClipPool>
|
|
{
|
|
/// Stop all playing clips
|
|
#[command(Stop = "stop")] fn tracks_stop_all (&mut self) -> Perhaps<TracksCmd> {
|
|
for track in self.tracks_mut().iter_mut() {
|
|
track.sequencer.enqueue_next(None);
|
|
}
|
|
Ok(None)
|
|
}
|
|
/// Launch multiple clips
|
|
#[command(Launch = "launch")] fn tracks_launch (&mut self, clips: ClipPool)
|
|
-> Perhaps<TracksCmd>
|
|
{
|
|
if clips.len() == 0 {
|
|
for track in self.tracks_mut().iter_mut() {
|
|
track.sequencer.enqueue_next(None);
|
|
}
|
|
return Ok(None)
|
|
}
|
|
for (clip, track) in clips.0.read().iter().zip(self.tracks_mut()) {
|
|
track.sequencer.enqueue_next(clip.read().as_ref());
|
|
}
|
|
Ok(None)
|
|
}
|
|
#[command(Add = "add")] fn tracks_add (&mut self) -> Perhaps<TracksCmd>
|
|
where Self: HasScenes + HasJack<'static> + Sized
|
|
{
|
|
let (_index, _) = self.tracks_add_one(None, None, [].into(), [].into())?;
|
|
Ok(None)
|
|
}
|
|
#[command(Select = "select")] fn track_select (&mut self) -> Perhaps<TracksCmd> {
|
|
let tracks = self.tracks().len();
|
|
let select = self.selection_mut().select_track(tracks);
|
|
*self.selection_mut() = select;
|
|
Ok(None)
|
|
}
|
|
#[command(Next = "next")] fn track_select_next (&mut self) -> Perhaps<TracksCmd> {
|
|
let tracks = self.tracks().len();
|
|
let select = self.selection().select_track_next(tracks);
|
|
*self.selection_mut() = select;
|
|
Ok(None)
|
|
}
|
|
#[command(Prev = "prev")] fn track_select_prev (&mut self) -> Perhaps<TracksCmd> {
|
|
let select = self.selection_mut().select_track_prev();
|
|
*self.selection_mut() = select;
|
|
Ok(None)
|
|
}
|
|
|
|
/// Run audio callbacks for every track and every device
|
|
fn tracks_jack_process (&mut self, client: &Client, scope: &ProcessScope) -> Control
|
|
where Self: HasJack<'static>
|
|
{
|
|
for track in self.tracks_mut().iter_mut() {
|
|
if Control::Quit == Audio::process(&mut track.sequencer, client, scope) {
|
|
return Control::Quit
|
|
}
|
|
for device in track.devices.iter_mut() {
|
|
if Control::Quit == DeviceAudio(device).process(client, scope) {
|
|
return Control::Quit
|
|
}
|
|
}
|
|
}
|
|
Control::Continue
|
|
}
|
|
|
|
/// Add multiple tracks
|
|
fn tracks_add_many (
|
|
&mut self,
|
|
count: usize,
|
|
width: Option<usize>,
|
|
mins: Arc<[Connect]>,
|
|
mouts: Arc<[Connect]>,
|
|
) -> Usually<()>
|
|
where Self: HasJack<'static> + HasScenes
|
|
{
|
|
let track_color_1 = ItemColor::random();
|
|
let track_color_2 = ItemColor::random();
|
|
for i in 0..count {
|
|
let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into();
|
|
let track = self.tracks_add_one(None, Some(color), mins.clone(), mouts.clone())?.1;
|
|
if let Some(width) = width {
|
|
track.width = width;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Add a track
|
|
fn tracks_add_one (
|
|
&mut self,
|
|
name: Option<Arc<str>>,
|
|
color: Option<ItemTheme>,
|
|
mins: Arc<[Connect]>,
|
|
mouts: Arc<[Connect]>,
|
|
) -> Usually<(usize, &mut Track)>
|
|
where Self: HasJack<'static> + HasScenes
|
|
{
|
|
let name: Arc<str> = name.map_or_else(
|
|
||format!("trk{}", self.tracks_last()).into(),
|
|
|x|x.to_string().into()
|
|
);
|
|
*self.tracks_last_mut() += 1;
|
|
let track = Track::new_added(self.jack(), Some(self.clock()), &name, color, None, mins, mouts)?;
|
|
self.tracks_mut().push(track);
|
|
let len = self.tracks().len();
|
|
let index = len - 1;
|
|
for scene in self.scenes_mut().iter_mut() {
|
|
while scene.clips.len() < len {
|
|
scene.clips.append(None);
|
|
}
|
|
}
|
|
Ok((index, &mut self.tracks_mut()[index]))
|
|
}
|
|
|
|
fn track_longest_name (&self) -> usize {
|
|
self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max)
|
|
}
|
|
}
|
|
|
|
impl<T: HasTrack
|
|
+ Namespace<usize>
|
|
+ Namespace<Arc<str>>
|
|
+ Namespace<ItemTheme>
|
|
+ Namespace<Option<bool>>
|
|
> TrackControl for T {}
|
|
|
|
#[tek_proc::commands(TrackCmd)] pub trait TrackControl: HasTrack
|
|
+ Namespace<usize>
|
|
+ Namespace<Arc<str>>
|
|
+ Namespace<ItemTheme>
|
|
+ Namespace<Option<bool>>
|
|
{
|
|
#[command(Stop = "track/stop")]
|
|
fn track_stop (&mut self) -> Perhaps<TrackCmd> {
|
|
self.track_mut().map(|track|track.sequencer.enqueue_next(None));
|
|
Ok(None)
|
|
}
|
|
#[command(SetMute = "track/mute")]
|
|
fn track_set_mute (&mut self, _mute: Option<bool>) -> Perhaps<TrackCmd> {
|
|
todo!()
|
|
}
|
|
#[command(SetSolo = "track/solo")]
|
|
fn track_set_solo (&mut self, _solo: Option<bool>) -> Perhaps<TrackCmd> {
|
|
todo!()
|
|
}
|
|
#[command(SetSize = "track/size")]
|
|
fn track_set_size (&mut self, _size: usize) -> Perhaps<TrackCmd> {
|
|
todo!()
|
|
}
|
|
#[command(SetZoom = "track/zoom")]
|
|
fn track_set_zoom (&mut self, _zoom: usize) -> Perhaps<TrackCmd> {
|
|
todo!()
|
|
}
|
|
#[command(SetName = "track/name")]
|
|
fn track_set_name (&mut self, name: Arc<str>) -> Perhaps<TrackCmd> {
|
|
self.track_mut()
|
|
.map(|track|swap_value(&mut track.name, &name, |name|TrackCmd::SetName { name }))
|
|
.transpose()
|
|
.map(Option::flatten)
|
|
}
|
|
#[command(SetColor = "track/color")]
|
|
fn track_set_color (&mut self, color: ItemTheme) -> Perhaps<TrackCmd> {
|
|
self.track_mut()
|
|
.map(|track|swap_value(&mut track.color, &color, |color|TrackCmd::SetColor { color }))
|
|
.transpose()
|
|
.map(Option::flatten)
|
|
}
|
|
#[command(SetRec = "track/rec")]
|
|
fn track_set_rec (&mut self, rec: Option<bool>) -> Perhaps<TrackCmd> {
|
|
self.track_mut()
|
|
.map(|track|toggle_bool(&mut track.sequencer.recording, &rec, |rec|TrackCmd::SetRec { rec }))
|
|
.transpose()
|
|
.map(Option::flatten)
|
|
}
|
|
#[command(SetMon = "track/mon")]
|
|
fn track_set_mon (&mut self, mon: Option<bool>) -> Perhaps<TrackCmd> {
|
|
self.track_mut()
|
|
.map(|track|toggle_bool(&mut track.sequencer.monitoring, &mon, |mon|TrackCmd::SetMon { mon }))
|
|
.transpose()
|
|
.map(Option::flatten)
|
|
}
|
|
}
|
|
|
|
impl<T: HasScenes
|
|
+ HasTracks
|
|
+ HasTrackScroll
|
|
+ HasSelection
|
|
+ HasJack<'static>
|
|
> ScenesControl for T {}
|
|
|
|
#[tek_proc::commands(ScenesCmd)] pub trait ScenesControl: HasScenes
|
|
+ HasTracks
|
|
+ HasTrackScroll
|
|
+ HasSelection
|
|
+ HasJack<'static>
|
|
{
|
|
#[command(Add = "add")] fn scenes_add (&mut self) -> Perhaps<ScenesCmd> where Self: Sized {
|
|
let (_index, _) = self.scenes_add_one(None, None)?;
|
|
Ok(None)
|
|
}
|
|
#[command(Select = "select")] fn scene_select (&mut self) -> Perhaps<ScenesCmd> {
|
|
let scenes = self.scenes().len();
|
|
let select = self.selection_mut().select_scene(scenes);
|
|
*self.selection_mut() = select;
|
|
Ok(None)
|
|
}
|
|
#[command(Next = "next")] fn scene_select_next (&mut self) -> Perhaps<ScenesCmd> {
|
|
let scenes = self.scenes().len();
|
|
let select = self.selection().select_scene_next(scenes);
|
|
*self.selection_mut() = select;
|
|
Ok(None)
|
|
}
|
|
#[command(Prev = "prev")] fn scene_select_prev (&mut self) -> Perhaps<ScenesCmd> {
|
|
let select = self.selection_mut().select_scene_prev();
|
|
*self.selection_mut() = select;
|
|
Ok(None)
|
|
}
|
|
/// Generate the default name for a new scene
|
|
fn scenes_default_name (&self) -> Arc<str> {
|
|
format!("s{:3>}", self.scenes().len() + 1).into()
|
|
}
|
|
fn scenes_longest_name (&self) -> usize {
|
|
self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max)
|
|
}
|
|
/// Add multiple scenes
|
|
fn scenes_add_many (&mut self, n: usize)
|
|
-> Usually<()> where Self: HasTracks
|
|
{
|
|
let scene_color_1 = ItemColor::random();
|
|
let scene_color_2 = ItemColor::random();
|
|
for i in 0..n {
|
|
let _ = self.scenes_add_one(None, Some(
|
|
scene_color_1.mix(scene_color_2, i as f32 / n as f32).into()
|
|
))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
/// Add a scene
|
|
fn scenes_add_one (&mut self, name: Option<Arc<str>>, color: Option<ItemTheme>)
|
|
-> Usually<(usize, &mut Scene)>
|
|
where
|
|
Self: HasTracks
|
|
{
|
|
let scene = Scene {
|
|
name: name.map_or_else(||self.scenes_default_name(), |x|x.to_string().into()),
|
|
clips: Pool::from(vec![None;self.tracks().len()]),
|
|
color: color.unwrap_or_else(ItemTheme::random),
|
|
};
|
|
self.scenes_mut().push(scene);
|
|
let index = self.scenes().len() - 1;
|
|
Ok((index, &mut self.scenes_mut()[index]))
|
|
}
|
|
}
|
|
|
|
impl<T: HasScene
|
|
+ Namespace<usize> + Namespace<Arc<str>> + Namespace<ItemTheme>
|
|
> SceneControl for T {}
|
|
|
|
#[tek_proc::commands(SceneCmd)] pub trait SceneControl: HasScene
|
|
+ Namespace<usize> + Namespace<Arc<str>> + Namespace<ItemTheme>
|
|
{
|
|
#[command(SetSize = "size")] fn scene_set_size (&mut self, _size: usize) -> Perhaps<SceneCmd> {
|
|
todo!()
|
|
}
|
|
#[command(SetZoom = "zoom")] fn scene_set_zoom (&mut self, _size: usize) -> Perhaps<SceneCmd> {
|
|
todo!()
|
|
}
|
|
#[command(SetName = "name")] fn scene_set_name (&mut self, name: Arc<str>) -> Perhaps<SceneCmd> {
|
|
Ok(self.scene_mut().map(|scene|swap_value(
|
|
&mut scene.name,
|
|
&name,
|
|
|name|SceneCmd::SetName { name }
|
|
)).transpose()?.flatten())
|
|
}
|
|
#[command(SetColor = "color")] fn scene_set_color (&mut self, color: ItemTheme) -> Perhaps<SceneCmd> {
|
|
Ok(self.scene_mut().map(|scene|swap_value(
|
|
&mut scene.color,
|
|
&color,
|
|
|color|SceneCmd::SetColor { color }
|
|
)).transpose()?.flatten())
|
|
}
|
|
}
|
|
|
|
#[tek_proc::commands(SamplerCmd = "sampler")] pub trait SamplerControl: HasSampler
|
|
+ Namespace<usize>
|
|
{
|
|
#[command(RecToggle = "rec-toggle")] fn record_toggle (&mut self, slot: usize) -> Perhaps<SamplerCmd>
|
|
where Self: Sized
|
|
{
|
|
let sampler = self.sampler_mut();
|
|
let recording = sampler.recording.as_ref().map(|x|x.0);
|
|
let _ = SamplerCmd::RecFinish.dispatch(self)?;
|
|
// autoslice: continue recording at next slot
|
|
if recording != Some(slot) {
|
|
SamplerCmd::RecBegin { slot }.dispatch(self)
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
#[command(RecBegin = "rec-begin")] fn record_begin (&mut self, slot: usize) -> Perhaps<SamplerCmd> {
|
|
let sampler = self.sampler_mut();
|
|
sampler.recording = Some((
|
|
slot,
|
|
Some(Arc::new(RwLock::new(Sample::new(
|
|
"Sample", 0, 0, vec![vec![]; sampler.audio_ins.len()]
|
|
))))
|
|
));
|
|
Ok(None)
|
|
}
|
|
#[command(RecFinish = "rec-finish")] fn record_finish (&mut self) -> Perhaps<SamplerCmd> {
|
|
let sampler = self.sampler_mut();
|
|
let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{
|
|
std::mem::swap(sample, &mut sampler.samples.0[*index]);
|
|
sample
|
|
}); // TODO: undo
|
|
Ok(None)
|
|
}
|
|
#[command(RecCancel = "rec-cancel")] fn record_cancel (&mut self) -> Perhaps<SamplerCmd> {
|
|
self.sampler_mut().recording = None;
|
|
Ok(None)
|
|
}
|
|
#[command(PlaySample = "sample-play")] fn sample_play (&mut self, slot: usize) -> Perhaps<SamplerCmd> {
|
|
let sampler = self.sampler_mut();
|
|
if let Some(ref sample) = sampler.samples.0[slot] {
|
|
sampler.voices.write().push(Sample::play(sample, 0, &u7::from(128)));
|
|
}
|
|
Ok(None)
|
|
}
|
|
#[command(StopSample = "sample-stop")] fn sample_stop (&mut self, slot: usize) -> Perhaps<SamplerCmd> {
|
|
todo!();
|
|
//Ok(None)
|
|
}
|
|
}
|
|
|
|
impl<T: HasClip
|
|
+ HasEditor
|
|
+ Namespace<u32>
|
|
+ Namespace<f64>
|
|
+ Namespace<bool>
|
|
+ Namespace<usize>
|
|
+ Namespace<Option<u32>>
|
|
+ Namespace<ClipSlot>
|
|
> MidiEditControl for T {}
|
|
|
|
#[tek_proc::commands(MidiEditCmd)] pub trait MidiEditControl: HasClip
|
|
+ HasEditor
|
|
+ Namespace<u32>
|
|
+ Namespace<f64>
|
|
+ Namespace<bool>
|
|
+ Namespace<usize>
|
|
+ Namespace<Option<u32>>
|
|
+ Namespace<ClipSlot>
|
|
{
|
|
#[command(Toggle = "toggle")] fn edit_toggle (&mut self) -> Perhaps<MidiEditCmd> {
|
|
if self.is_editing() {
|
|
*self.editor_mut() = None;
|
|
} else if let Some(clip) = self.clip() {
|
|
let editor = MidiEditor::new();
|
|
*editor.mode.clip.0.write() = clip.clone();
|
|
editor.redraw();
|
|
*self.editor_mut() = Some(editor);
|
|
} else {
|
|
return Ok(None)
|
|
}
|
|
Ok(Some(MidiEditCmd::Toggle))
|
|
}
|
|
#[command(Show = "show")] fn edit_show (&mut self, clip: ClipSlot) -> Perhaps<MidiEditCmd> {
|
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
|
editor.mode.clip.0 = clip.0;
|
|
editor.redraw();
|
|
None
|
|
}).flatten())
|
|
}
|
|
#[command(DeleteNote = "delete")] fn edit_note_delete (&mut self) -> Perhaps<MidiEditCmd> {
|
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
|
editor.redraw();
|
|
todo!()
|
|
}).flatten())
|
|
}
|
|
#[command(AppendNote = "append")] fn edit_note_append (&mut self, advance: bool) -> Perhaps<MidiEditCmd> {
|
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
|
editor.put_note(advance);
|
|
editor.redraw();
|
|
None
|
|
}).flatten())
|
|
}
|
|
#[command(SetNotePos = "note-pos")] fn edit_note_set_pos (&mut self, pos: usize) -> Perhaps<MidiEditCmd> {
|
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
|
editor.set_note_pos((pos).min(127));
|
|
editor.redraw();
|
|
None
|
|
}).flatten())
|
|
}
|
|
#[command(SetNoteLen = "note-len")] fn edit_note_set_len (&mut self, len: usize) -> Perhaps<MidiEditCmd> {
|
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
|
editor.set_note_len(len);
|
|
editor.redraw();
|
|
None
|
|
}).flatten())
|
|
}
|
|
#[command(SetNoteScroll = "note-scroll")] fn edit_note_set_scroll (&mut self, scroll: usize) -> Perhaps<MidiEditCmd> {
|
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
|
editor.set_note_lo((scroll).min(127));
|
|
editor.redraw();
|
|
None
|
|
}).flatten())
|
|
}
|
|
#[command(SetTimePos = "time-pos")] fn edit_time_set_pos (&mut self, pos: usize) -> Perhaps<MidiEditCmd> {
|
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
|
editor.set_time_pos(pos);
|
|
editor.redraw();
|
|
None
|
|
}).flatten())
|
|
}
|
|
#[command(SetTimeScroll = "time-scroll")] fn edit_time_set_scroll (&mut self, scroll: usize) -> Perhaps<MidiEditCmd> {
|
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
|
editor.set_time_start(scroll);
|
|
editor.redraw();
|
|
None
|
|
}).flatten())
|
|
}
|
|
#[command(SetTimeZoom = "time-zoom")] fn edit_time_set_zoom (&mut self, zoom: usize) -> Perhaps<MidiEditCmd> {
|
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
|
editor.set_time_zoom(zoom);
|
|
editor.redraw();
|
|
None
|
|
}).flatten())
|
|
}
|
|
#[command(SetTimeLock = "time-lock")] fn edit_time_set_lock (&mut self, lock: bool) -> Perhaps<MidiEditCmd> {
|
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
|
editor.set_time_lock(lock);
|
|
editor.redraw();
|
|
None
|
|
}).flatten())
|
|
}
|
|
// TODO: 1-9 seek markers that by default start every 8th of the clip
|
|
}
|
|
|
|
impl<T> MidiClipControl for T where T: HasClip
|
|
+ Namespace<Option<bool>> + Namespace<Option<ItemTheme>> {}
|
|
#[tek_proc::commands(MidiClipCmd)] pub trait MidiClipControl: HasClip
|
|
+ Namespace<Option<bool>> + Namespace<Option<ItemTheme>>
|
|
{
|
|
/// Create a clip at the current position if it does not already exist.
|
|
#[command(Upsert = "upsert")] fn clip_upsert (
|
|
&mut self
|
|
) -> Perhaps<MidiClipCmd> {
|
|
Ok(self.clip_mut().map(|mut clip|if clip.is_none() {
|
|
*clip = Some(Arc::new(RwLock::new(MidiClip {
|
|
ppq: 24,
|
|
length: 96,
|
|
color: ItemTheme::random(),
|
|
..Default::default()
|
|
})));
|
|
None // TODO undo
|
|
} else {
|
|
None
|
|
}).flatten())
|
|
}
|
|
#[command(SetColor = "color")] fn clip_set_color (
|
|
&mut self, color: Option<ItemTheme>
|
|
) -> Perhaps<MidiClipCmd> {
|
|
Ok(self.clip_slot().map_inner_mut(|clip|MidiClipCmd::SetColor {
|
|
color: Some(clip.set_color(color))
|
|
}))
|
|
}
|
|
/// Toggle looping for the active clip
|
|
#[command(SetLoop = "loop")] fn clip_toggle_loop (
|
|
&mut self, looping: Option<bool>
|
|
) -> Perhaps<MidiClipCmd> {
|
|
Ok(self.clip_slot().map_inner_mut(|clip|MidiClipCmd::SetLoop {
|
|
looping: Some(clip.toggle_loop(looping))
|
|
}))
|
|
}
|
|
}
|
|
|
|
impl<T> BrowseControl for T where T:
|
|
Namespace<usize> + Namespace<PathBuf> + Namespace<Arc<str>> {}
|
|
#[tek_proc::commands(BrowseCmd = "browse")] pub trait BrowseControl:
|
|
Namespace<usize> + Namespace<PathBuf> + Namespace<Arc<str>>
|
|
{
|
|
/// Toggle visibility of browser
|
|
#[command(Show = "show")] fn show (&mut self) -> Perhaps<BrowseCmd> {
|
|
todo!()
|
|
}
|
|
/// Set current directory
|
|
#[command(Chdir = "chdir")] fn chdir (&mut self, _path: PathBuf) -> Perhaps<BrowseCmd> {
|
|
todo!()
|
|
}
|
|
/// Set current filter
|
|
#[command(SetSearch = "set-search")] fn set_search (&mut self, _filter: Arc<str>) -> Perhaps<BrowseCmd> {
|
|
todo!()
|
|
}
|
|
/// Set selected item
|
|
#[command(SetCursor = "set-cursor")] fn set_cursor (&mut self, _index: usize) -> Perhaps<BrowseCmd> {
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
#[tek_proc::commands(AxisCmd)] impl App {
|
|
/// Increment the given data axis.
|
|
#[command(Inc = "inc")] pub fn inc (&mut self, axis: ControlAxis) -> Perhaps<AxisCmd> {
|
|
panic!("inc {axis:?}");
|
|
Ok(match (&self.dialog, axis) {
|
|
(Dialog::None, _) => todo!(),
|
|
(Dialog::Menu(_, _), ControlAxis::Y) => {
|
|
AppCmd::Dialog { dialog: self.dialog.menu_next() }.dispatch(self)?;
|
|
None
|
|
},
|
|
_ => todo!()
|
|
})
|
|
}
|
|
/// Decrement the given data axis.
|
|
#[command(Dec = "dec")] pub fn dec (&mut self, axis: ControlAxis) -> Perhaps<AxisCmd> {
|
|
panic!("dec {axis:?}");
|
|
Ok(match (&self.dialog, axis) {
|
|
(Dialog::None, _) => None,
|
|
(Dialog::Menu(_, _), ControlAxis::Y) => {
|
|
AppCmd::Dialog { dialog: self.dialog.menu_prev() }.dispatch(self)?;
|
|
None
|
|
},
|
|
_ => todo!()
|
|
})
|
|
}
|
|
}
|
|
|
|
#[tek_proc::command(AudioInput)]
|
|
#[tek_proc::keyword(AudioInput)]
|
|
#[derive(Debug)]
|
|
pub enum AudioInputCmd {
|
|
Close,
|
|
Connect(Arc<str>),
|
|
}
|
|
|
|
#[tek_proc::command(AudioOutput)]
|
|
#[tek_proc::keyword(AudioOutput)]
|
|
#[derive(Debug)]
|
|
pub enum AudioOutputCmd {
|
|
Close,
|
|
Connect(Arc<str>),
|
|
}
|
|
|
|
#[tek_proc::command(MidiInput)]
|
|
#[tek_proc::keyword(MidiInput)]
|
|
#[derive(Debug)]
|
|
pub enum MidiInputCmd {
|
|
Close,
|
|
Connect(Arc<str>),
|
|
}
|
|
|
|
#[tek_proc::command(MidiOutput)]
|
|
#[tek_proc::keyword(MidiOutput)]
|
|
#[derive(Debug)]
|
|
pub enum MidiOutputCmd {
|
|
Close,
|
|
Connect(Arc<str>),
|
|
}
|
|
|
|
//impl<T: HasPool
|
|
//+ Namespace<bool>
|
|
//+ Namespace<usize>
|
|
//+ Namespace<Arc<str>>
|
|
//+ Namespace<PathBuf>
|
|
//+ Namespace<MidiClip>
|
|
//+ Namespace<ItemColor>
|
|
//+ Namespace<BrowseCmd>
|
|
//> PoolControl for T {}
|
|
|
|
//#[tek_proc::commands(PolCmd = "pool")] pub trait PoolControl: HasPool
|
|
//+ Namespace<bool>
|
|
//+ Namespace<usize>
|
|
//+ Namespace<Arc<str>>
|
|
//+ Namespace<PathBuf>
|
|
//+ Namespace<MidiClip>
|
|
//+ Namespace<ItemColor>
|
|
//+ Namespace<BrowseCmd>
|
|
//{
|
|
///// Toggle visibility of pool
|
|
//#[command(Show = "show")] fn show (&mut self, visible: bool) -> Perhaps<PoolCmd> {
|
|
//self.pool_mut().visible = visible;
|
|
//Ok(Some(PoolCmd::Show { visible: !visible }))
|
|
//}
|
|
///// Set selected clip
|
|
//#[command(Select = "select")] fn select (&mut self, index: usize) -> Perhaps<PoolCmd> {
|
|
//self.pool_mut().set_clip_index(index);
|
|
//Ok(None)
|
|
//}
|
|
///// Delete a clip from the pool
|
|
//#[command(Delete = "delete")] fn delete (&mut self, index: usize) -> Perhaps<PoolCmd> {
|
|
//let clip = self.pool_mut().remove(index).read().clone();
|
|
//Ok(Some(PoolCmd::Add { index, clip }))
|
|
//}
|
|
///// Switch places of two clips in pool
|
|
//#[command(Swap = "swap")] fn swap (&mut self, index: usize, other: usize) -> Perhaps<PoolCmd> {
|
|
//self.pool_mut().swap(index, other);
|
|
//Ok(Some(PoolCmd::Swap { index, other }))
|
|
//}
|
|
///// Add clip to pool
|
|
//#[command(Add = "add")] fn clip_add (&mut self, index: usize, clip: MidiClip) -> Perhaps<PoolCmd> {
|
|
//let mut index = index;
|
|
//let clip = Arc::new(RwLock::new(clip.clone()));
|
|
//let mut clips = self.pool_mut().clips_mut();
|
|
//if index >= clips.len() {
|
|
//index = clips.len();
|
|
//clips.push(clip)
|
|
//} else {
|
|
//clips.insert(index, clip);
|
|
//}
|
|
//Ok(Some(PoolCmd::Delete { index }))
|
|
//}
|
|
///// Set name of clip
|
|
//#[command(SetName = "set-name")] fn clip_set_name (&mut self, index: usize, name: Arc<str>) -> Perhaps<PoolCmd> {
|
|
//Ok(self.pool_mut().clips.read().get(index).map(|clip|{
|
|
//let old_name = clip.read().name.clone();
|
|
//clip.write().name = name.clone();
|
|
//PoolCmd::SetName { index, name: old_name }
|
|
//}))
|
|
//}
|
|
///// Set length of clip
|
|
//#[command(SetLength = "set-length")] fn clip_set_length (&mut self, index: usize, length: usize) -> Perhaps<PoolCmd> {
|
|
//Ok(self.pool_mut().clips.read().get(index).map(|clip|{
|
|
//let old_len = clip.read().length;
|
|
//clip.write().length = length;
|
|
//PoolCmd::SetLength { index, length: old_len }
|
|
//}))
|
|
//}
|
|
///// Set color of clip
|
|
//#[command(SetColor = "set-color")] fn clip_set_color (&mut self, index: usize, color: ItemColor) -> Perhaps<PoolCmd> {
|
|
//let mut color = ItemTheme::from(color);
|
|
//std::mem::swap(&mut color, &mut self.pool().clips.read()[index].write().color);
|
|
//Ok(Some(PoolCmd::SetColor { index, color: color.base }))
|
|
//}
|
|
///// Enter crop mode
|
|
//#[command(CropBegin = "crop/begin")] fn crop_begin (&mut self) -> Perhaps<PoolCmd> {
|
|
//let index = self.pool().clip_index();
|
|
//let length = self.pool().clips.read()[index].read().length;
|
|
//*self.pool_mut().mode_mut() = Some(PoolMode::Length(index, length, ClipLengthFocus::Bar));
|
|
//Ok(None)
|
|
//}
|
|
///// Exit crop mode, discard
|
|
//#[command(CropCancel = "crop/cancel")] fn crop_cancel (&mut self) -> Perhaps<PoolCmd> {
|
|
//if let Some(PoolMode::Length(..)) = self.pool_mut().mode_mut().clone() {
|
|
//*self.pool_mut().mode_mut() = None;
|
|
//}
|
|
//Ok(None)
|
|
//}
|
|
//#[command(CropSet = "crop/set")] fn crop_set (&mut self, _length: usize) -> Perhaps<PoolCmd> {
|
|
//if let Some(PoolMode::Length(clip, ref mut length, ref mut _focus))
|
|
//= self.pool_mut().mode_mut().clone()
|
|
//{
|
|
//let old_length;
|
|
//{
|
|
//let clip = self.pool().clips.read()[clip].clone();//.write();
|
|
//old_length = Some(clip.read().length);
|
|
//clip.write().length = *length;
|
|
//}
|
|
//*self.pool_mut().mode_mut() = None;
|
|
//return Ok(old_length.map(|l|PoolCmd::CropSet { _length: l }))
|
|
//}
|
|
//Ok(None)
|
|
//}
|
|
//#[command(CropNext = "crop/next")] fn crop_next (&mut self) -> Perhaps<PoolCmd> {
|
|
//if let Some(PoolMode::Length(
|
|
//_clip, ref mut _length, ref mut focus
|
|
//)) = self.pool_mut().mode_mut().clone() {
|
|
//focus.next()
|
|
//};
|
|
//Ok(None)
|
|
//}
|
|
//#[command(CropPrev = "crop/prev")] fn crop_prev (&mut self) -> Perhaps<PoolCmd> {
|
|
//if let Some(PoolMode::Length(
|
|
//_clip, ref mut _length, ref mut focus
|
|
//)) = self.pool_mut().mode_mut().clone() {
|
|
//focus.prev()
|
|
//};
|
|
//Ok(None)
|
|
//}
|
|
//#[command(CropInc = "crop/inc")] fn crop_inc (&mut self) -> Perhaps<PoolCmd> {
|
|
//if let Some(PoolMode::Length(
|
|
//_clip, ref mut length, ref mut focus
|
|
//)) = self.pool_mut().mode_mut().clone() {
|
|
//match focus {
|
|
//ClipLengthFocus::Bar => { *length += 4 * PPQ },
|
|
//ClipLengthFocus::Beat => { *length += PPQ },
|
|
//ClipLengthFocus::Tick => { *length += 1 },
|
|
//}
|
|
//}
|
|
//Ok(None)
|
|
//}
|
|
//#[command(CropDec = "crop/dec")] fn crop_dec (&mut self) -> Perhaps<PoolCmd> {
|
|
//if let Some(PoolMode::Length(
|
|
//_clip, ref mut length, ref mut focus
|
|
//)) = self.pool_mut().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)
|
|
//}
|
|
///// Enter rename mode
|
|
//#[command(RenameBegin = "rename/begin")] fn rename_begin (&mut self) -> Perhaps<PoolCmd> {
|
|
//let index = self.pool().clip_index();
|
|
//let name = self.pool().clips.read()[index].read().name.clone();
|
|
//*self.pool_mut().mode_mut() = Some(PoolMode::Rename(index, name));
|
|
//Ok(None)
|
|
//}
|
|
///// Exit rename mode, discard
|
|
//#[command(RenameCancel = "rename/cancel")] fn rename_cancel (&mut self) -> Perhaps<PoolCmd> {
|
|
//if let Some(PoolMode::Rename(clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() {
|
|
//self.pool().clips.read()[clip].write().name = old_name.clone().into();
|
|
//}
|
|
//Ok(None)
|
|
//}
|
|
///// Exit rename mode, update name
|
|
//#[command(RenameConfirm = "rename/confirm")] fn rename_confirm (&mut self) -> Perhaps<PoolCmd> {
|
|
//Ok(if let Some(PoolMode::Rename(_clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() {
|
|
//let old_name = old_name.clone();
|
|
//*self.pool_mut().mode_mut() = None;
|
|
//Some(PoolCmd::RenameSet { value: old_name })
|
|
//} else {
|
|
//None
|
|
//})
|
|
//}
|
|
//#[command(RenameSet = "rename/set")] fn rename_set (&mut self, value: Arc<str>) -> Perhaps<PoolCmd> {
|
|
//if let Some(PoolMode::Rename(clip, ref mut _old_name)) = self.pool_mut().mode_mut().clone() {
|
|
//self.pool().clips.read()[clip].write().name = value.clone();
|
|
//}
|
|
//Ok(None)
|
|
//}
|
|
/////// Import from file
|
|
////#[command(Browse = "browse")]
|
|
////fn browse (&mut self, command: BrowseCmd) -> Perhaps<PoolCmd> {
|
|
////Ok(if let Some(browse) = self.pool_mut().browse.as_mut() {
|
|
////command.act(browse)?.map(|command|PoolCmd::Browse{command})
|
|
////} else {
|
|
////None
|
|
////})
|
|
////}
|
|
///// Import clip to pool from file
|
|
//#[command(Import = "import")]
|
|
//fn clip_import (&mut self, index: usize, path: PathBuf) -> Perhaps<PoolCmd>
|
|
//where Self: Sized
|
|
//{
|
|
//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(PoolCmd::Add { index, clip }.dispatch(self)?)
|
|
//}
|
|
/////// Export to file
|
|
////#[command(Export = "export")]
|
|
////fn export (&mut self, command: BrowseCmd) -> Perhaps<PoolCmd> {
|
|
////Ok(if let Some(browse) = self.pool_mut().browse.as_mut() {
|
|
////command.act(browse)?.map(|command|PoolCmd::Export{command})
|
|
////} else {
|
|
////None
|
|
////})
|
|
////}
|
|
//}
|