Compare commits

...

15 commits

23 changed files with 244 additions and 212 deletions

View file

@ -25,17 +25,17 @@ or [**matrix** `@unspeaker:matrix.org`](https://matrix.to/#/@unspeaker:matrix.or
## keymaps
* Arranger:
* [x] `arrows/wasd`: navigate
* [x] `tab`: enter editor
* [x] arrows: navigate
* [x] tab: enter editor
* [x] `q`: enqueue clip
* [x] space: play/pause
* Editor:
* [x] `arrows/wasd`: navigate
* [x] arrows: navigate
* [x] `,` / `.`: change note length
* [x] `enter`: write note
* [x] enter: write note
* [x] `-` / `=`: zoom midi editor
* [ ] `z`: zoom lock/unlock
* [ ] `del`: delete
* [ ] del: delete
* Global:
* [x] esc: options menu
* [x] f1: help/command list

View file

@ -3,25 +3,25 @@
(info "A session grid.")
(view
(bsp/a :modal
(bsp/s (fixed/y 1 :transport)
(bsp/n (fixed/y 1 :status)
(bsp/a :view-dialog
(bsp/s (fixed/y 1 :view-transport)
(bsp/n (fixed/y 1 :view-status)
(fill/xy (bsp/a
(fill/xy (align/e :pool))
:arranger))))))
(fill/xy (align/e :view-pool))
:view-arranger))))))
(keys
(layer-if :mode-message "./keys_message.edn")
(layer-if :mode-device-add "./keys_device_add.edn")
(layer-if :mode-pool-import "./keys_pool_file.edn")
(layer-if :mode-pool-export "./keys_pool_file.edn")
(layer-if :mode-pool-rename "./keys_clip_rename.edn")
(layer-if :mode-pool-length "./keys_clip_length.edn")
(layer "./keys_global.edn")
(layer-if :mode-editor "./keys_editor.edn")
(layer-if :mode-clip "./keys_clip.edn")
(layer-if :mode-track "./keys_track.edn")
(layer-if :mode-scene "./keys_scene.edn")
(layer-if :mode-mix "./keys_mix.edn")
(layer "./keys_clock.edn")
(layer "./keys_arranger.edn"))
(layer-if :focus-message "./keys_message.edn")
(layer-if :focus-device-add "./keys_device_add.edn")
(layer-if :focus-pool-import "./keys_pool_file.edn")
(layer-if :focus-pool-export "./keys_pool_file.edn")
(layer-if :focus-pool-rename "./keys_clip_rename.edn")
(layer-if :focus-pool-length "./keys_clip_length.edn")
(layer "./keys_global.edn")
(layer-if :focus-editor "./keys_editor.edn")
(layer-if :focus-clip "./keys_clip.edn")
(layer-if :focus-track "./keys_track.edn")
(layer-if :focus-scene "./keys_scene.edn")
(layer-if :focus-mix "./keys_mix.edn")
(layer "./keys_clock.edn")
(layer "./keys_arranger.edn"))

View file

@ -1,22 +1,22 @@
(name "Arranger")
(name "Groovebox")
(info "A sequencer with built-in sampler.")
(view
(bsp/a :modal
(bsp/s (fixed/y 1 :transport)
(bsp/n (fixed/y 1 :status)
(bsp/n (fixed/y 5 :sample-viewer)
(bsp/w (fixed/x :w-sidebar :pool)
(bsp/e :samples-keys
(fill/y :editor))))))))
(bsp/a :view-dialog
(bsp/s (fixed/y 1 :view-transport)
(bsp/n (fixed/y 1 :view-status)
(bsp/n (fixed/y 5 :view-sample-viewer)
(bsp/w (fixed/x :w-sidebar :view-pool)
(bsp/e :view-samples-keys
(fill/y :view-editor))))))))
(keys
(layer-if :mode-pool-import "./keys_pool_file.edn")
(layer-if :mode-pool-export "./keys_pool_file.edn")
(layer-if :mode-pool-rename "./keys_clip_rename.edn")
(layer-if :mode-pool-length "./keys_clip_length.edn")
(layer "./keys_global.edn")
(layer-if :mode-editor "./keys_editor.edn")
(layer "./keys_clock.edn")
(layer "./keys_arranger.edn"))
(layer-if :focus-pool-import "./keys_pool_file.edn")
(layer-if :focus-pool-export "./keys_pool_file.edn")
(layer-if :focus-pool-rename "./keys_clip_rename.edn")
(layer-if :focus-pool-length "./keys_clip_length.edn")
(layer "./keys_global.edn")
(layer "./keys_clock.edn")
(layer "./keys_editor.edn")
(layer "./keys_sampler.edn"))

View file

@ -3,10 +3,10 @@
(info "A sampling soundboard.")
(view
(bsp/a :modal
(bsp/s (fixed/y 1 :transport)
(bsp/n (fixed/y 1 :status)
(fill/xy :samples-grid)))))
(bsp/a :view-dialog
(bsp/s (fixed/y 1 :view-transport)
(bsp/n (fixed/y 1 :view-status)
(fill/xy :view-samples-grid)))))
(keys
(layer "./keys_global.edn")

View file

@ -3,12 +3,12 @@
(info "A MIDI sequencer.")
(view
(bsp/a :modal
(bsp/s (fixed/y 1 :transport)
(bsp/n (fixed/y 1 :status)
(bsp/a :view-dialog
(bsp/s (fixed/y 1 :view-transport)
(bsp/n (fixed/y 1 :view-status)
(fill/xy (bsp/a
(fill/xy (align/e :pool))
:editor)))))
(fill/xy (align/e :view-pool))
:view-editor)))))
(keys
(layer-if :mode-pool-import "./keys_pool_file.edn")

View file

@ -2,7 +2,7 @@
(info "A JACK transport controller.")
(view :transport)
(view :view-transport)
(keys
(layer "./keys_global.edn")

View file

@ -1,6 +1,6 @@
(@c color)
(@q launch)
(@t select :track 0)
(@t select :select-track-header)
(@tab edit :clip)
(@shift-I input add)
(@shift-O output add)
@ -8,11 +8,7 @@
(@shift-T track add)
(@shift-Z device picker)
(@up select :scene-prev)
(@w select :scene-prev)
(@down select :scene-next)
(@s select :scene-next)
(@left select :track-prev)
(@a select :track-prev)
(@right select :track-next)
(@d select :track-next)
(@up select :select-scene-prev)
(@down select :select-scene-next)
(@left select :select-track-prev)
(@right select :select-track-next)

View file

@ -1,2 +1,2 @@
(@space clock toggle)
(@shift-space clock toggle 0)
(@space clock toggle-playback 0)
(@shift-space clock toggle-playback 0)

View file

@ -1,30 +1,25 @@
(@up editor note/pos :note-pos-next)
(@w editor note/pos :note-pos-next)
(@down editor note/pos :note-pos-prev)
(@s editor note/pos :note-pos-prev)
(@up editor note-pos :note-pos-next)
(@down editor note-pos :note-pos-prev)
(@pgup editor note-pos :note-pos-next-octave)
(@pgdn editor note-pos :note-pos-prev-octave)
(@pgup editor note/pos :note-pos-next-octave)
(@pgdn editor note/pos :note-pos-prev-octave)
(@comma editor note-len :note-len-prev)
(@period editor note-len :note-len-next)
(@lt editor note-len :note-len-prev)
(@gt editor note-len :note-len-next)
(@comma editor note/len :note-len-prev)
(@period editor note/len :note-len-next)
(@lt editor note/len :note-len-prev)
(@gt editor note/len :note-len-next)
(@plus editor note-range :note-range-next)
(@underscore editor note-range :note-range-prev)
(@plus editor note/range :note-range-next)
(@underscore editor note/range :note-range-prev)
(@left editor time-pos :time-pos-prev)
(@right editor time-pos :time-pos-next)
(@left editor time/pos :time-pos-prev)
(@a editor time/pos :time-pos-prev)
(@right editor time/pos :time-pos-next)
(@d editor time/pos :time-pos-next)
(@equal editor time-zoom :time-zoom-prev)
(@minus editor time-zoom :time-zoom-next)
(@equal editor time/zoom :time-zoom-prev)
(@minus editor time/zoom :time-zoom-next)
(@z editor time-lock)
(@z editor time/lock)
(@enter editor note/put)
(@shift-enter editor note/append)
(@del editor note/del)
(@shift-del editor note/del)
(@enter editor note-put)
(@shift-enter editor note-append)
(@del editor note-del)
(@shift-del editor note-del)

View file

@ -1,13 +1,5 @@
(@up sampler select :sample-up)
(@w sampler select :sample-up)
(@down sampler select :sample-down)
(@s sampler select :sample-down)
(@left sampler select :sample-left)
(@a sampler select :sample-left)
(@right sampler select :sample-right)
(@d sampler select :sample-right)
(@r sampler record/toggle :sample)

View file

@ -11,7 +11,10 @@ impl HasClips for ExampleClips {
}
}
fn main () -> Result<(), Box<dyn std::error::Error>> {
let mut clips = ExampleClips(Arc::new(vec![].into()));
PoolClipCommand::Import(0, std::path::PathBuf::from("./example.mid")).execute(&mut clips)?;
let mut clips = MidiPool::default();//ExampleClips(Arc::new(vec![].into()));
PoolClipCommand::Import {
index: 0,
path: std::path::PathBuf::from("./example.mid")
}.execute(&mut clips)?;
Ok(())
}

View file

@ -18,6 +18,12 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm
}));
#[tengri_proc::expose] impl App {
fn _todo_isize_stub (&self) -> isize {
todo!()
}
fn _todo_item_theme_stub (&self) -> ItemTheme {
todo!()
}
fn focus_editor (&self) -> bool {
self.is_editing()
}
@ -113,22 +119,25 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm
fn scene_selected (&self) -> Option<usize> {
self.selected.scene()
}
fn scene_select_next (&self) -> Selection {
self.selected.scene_next(self.scenes.len())
}
fn scene_select_prev (&self) -> Selection {
self.selected.scene_prev()
}
fn track_count (&self) -> usize {
self.tracks.len()
}
fn track_selected (&self) -> Option<usize> {
self.selected.track()
}
fn track_select_next (&self) -> Selection {
fn select_scene_next (&self) -> Selection {
self.selected.scene_next(self.scenes.len())
}
fn select_scene_prev (&self) -> Selection {
self.selected.scene_prev()
}
fn select_track_header (&self) -> Selection {
self.selected.track_header(self.tracks.len())
}
fn select_track_next (&self) -> Selection {
self.selected.track_next(self.tracks.len())
}
fn track_select_prev (&self) -> Selection {
fn select_track_prev (&self) -> Selection {
self.selected.track_prev()
}
fn clip_selected (&self) -> Option<Arc<RwLock<MidiClip>>> {
@ -161,6 +170,15 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm
}
#[tengri_proc::expose] impl MidiPool {
fn _todo_bool_stub (&self) -> bool {
todo!()
}
fn _todo_path_buf_stub (&self) -> PathBuf {
todo!()
}
fn _todo_arc_str_stub (&self) -> Arc<str> {
todo!()
}
fn clip_new (&self) -> MidiClip {
self.new_clip()
}
@ -185,6 +203,9 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm
}
#[tengri_proc::expose] impl MidiEditor {
fn _todo_opt_clip_stub (&self) -> Option<Arc<RwLock<MidiClip>>> {
todo!()
}
fn time_lock (&self) -> bool {
self.get_time_lock()
}
@ -289,6 +310,35 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm
None
})
}
fn color (app: &mut App, theme: ItemTheme) -> Perhaps<Self> {
Ok(app.set_color(Some(theme)).map(|theme|Self::Color{theme}))
}
fn enqueue (app: &mut App, clip: Option<Arc<RwLock<MidiClip>>>) -> Perhaps<Self> {
todo!()
}
fn history (app: &mut App, delta: isize) -> Perhaps<Self> {
todo!()
}
fn zoom (app: &mut App, zoom: usize) -> Perhaps<Self> {
todo!()
}
fn launch (app: &mut App) -> Perhaps<Self> {
app.launch();
Ok(None)
}
fn select (app: &mut App, selection: Selection) -> Perhaps<Self> {
app.select(selection);
Ok(None)
//("select" [t: usize, s: usize] Some(match (t.expect("no track"), s.expect("no scene")) {
//(0, 0) => Self::Select(Selection::Mix),
//(t, 0) => Self::Select(Selection::Track(t)),
//(0, s) => Self::Select(Selection::Scene(s)),
//(t, s) => Self::Select(Selection::TrackClip { track: t, scene: s }) })))
}
fn stop_all (app: &mut App) -> Perhaps<Self> {
app.stop_all();
Ok(None)
}
fn sampler (app: &mut App, command: SamplerCommand) -> Perhaps<Self> {
Ok(app.sampler_mut()
.map(|s|command.delegate(s, |command|Self::Sampler{command}))
@ -319,34 +369,29 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm
fn message (app: &mut App, command: MessageCommand) -> Perhaps<Self> {
Ok(command.delegate(app, |command|Self::Message{command})?)
}
fn color (app: &mut App, theme: ItemTheme) -> Perhaps<Self> {
Ok(app.set_color(Some(theme)).map(|theme|Self::Color{theme}))
}
impl<'state> Context<'state, ClockCommand> for App {
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<ClockCommand> {
Context::get(&self.clock, iter)
}
fn enqueue (app: &mut App, clip: Option<Arc<RwLock<MidiClip>>>) -> Perhaps<Self> {
todo!()
}
impl<'state> Context<'state, MidiEditCommand> for App {
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<MidiEditCommand> {
self.editor().map(|e|Context::get(e, iter)).flatten()
}
fn history (app: &mut App, delta: isize) -> Perhaps<Self> {
todo!()
}
impl<'state> Context<'state, PoolCommand> for App {
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<PoolCommand> {
self.pool().map(|p|Context::get(p, iter)).flatten()
}
fn zoom (app: &mut App, zoom: usize) -> Perhaps<Self> {
todo!()
}
fn launch (app: &mut App) -> Perhaps<Self> {
app.launch();
Ok(None)
}
fn select (app: &mut App, selection: Selection) -> Perhaps<Self> {
app.select(selection);
Ok(None)
//("select" [t: usize, s: usize] Some(match (t.expect("no track"), s.expect("no scene")) {
//(0, 0) => Self::Select(Selection::Mix),
//(t, 0) => Self::Select(Selection::Track(t)),
//(0, s) => Self::Select(Selection::Scene(s)),
//(t, s) => Self::Select(Selection::TrackClip { track: t, scene: s }) })))
}
fn stop_all (app: &mut App) -> Perhaps<Self> {
app.stop_all();
Ok(None)
}
impl<'state> Context<'state, SamplerCommand> for App {
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<SamplerCommand> {
self.sampler().map(|p|Context::get(p, iter)).flatten()
}
}

View file

@ -122,8 +122,7 @@ impl Configuration {
return Err(format!("(e4) unexpected non-symbol {next:?}").into())
};
let next = exp.next();
if let Some(Token { value: Value::Str(path), .. }) = next {
if let Some(Token { value: Value::Str(path), .. }) = exp.peek() {
let path = base.as_ref().parent().unwrap().join(unquote(path));
if !std::fs::exists(&path)? {
return Err(format!("(e5) not found: {path:?}").into())
@ -133,8 +132,9 @@ impl Configuration {
println!("ok");
let cond = cond.unwrap();
map.add_layer_if(
Box::new(|state|{
Context::get(state, &Value::Sym(cond)).unwrap_or(false)
Box::new(move |state|{
let mut exp = exp.clone();
Context::get(state, &mut exp).unwrap_or(false)
}),
keys
);

View file

@ -42,33 +42,29 @@ mod config; pub use self::config::*;
mod model; pub use self::model::*;
mod view; pub use self::view::*;
#[cfg(test)] #[test] fn test_model () {
#[cfg(test)] #[test] fn test_model () -> Usually<()> {
let mut app = App::default();
let _ = app.clip();
let _ = app.toggle_loop();
}
#[cfg(test)] #[test] fn test_model_scene () {
let mut app = App::default();
//let _ = app.tracks_add(8, None, &[], &[])?;
//let _ = app.track_add_focus()?;
let _ = app.scene_longest();
let _ = app.scene();
let _ = app.scene_mut();
let _ = app.scene_add(None, None);
app.scene_del(0);
let _ = app.scene_add(None, None)?;
let _ = app.scene_add_focus()?;
let scene = app.scene_del(0);
let scene = Scene::default();
let _ = scene.pulses();
let _ = scene.is_playing(&[]);
}
#[cfg(test)] #[test] fn test_view_clock () {
let _ = button_play_pause(true);
let mut app = App::default();
let _ = app.view_transport();
let _ = app.view_status();
let _ = app.update_clock();
Ok(())
}
#[cfg(test)] #[test] fn test_view_layout () {
let _ = button_play_pause(true);
let _ = button_2("", "", true);
let _ = button_2("", "", false);
let _ = button_3("", "", "", true);
@ -122,3 +118,29 @@ mod view; pub use self::view::*;
let _ = app.h_outputs();
let _ = app.h_scenes();
}
#[cfg(test)] #[test] fn test_midi_edit () {
let editor = MidiEditor::default();
let mut editor = MidiEditor {
mode: PianoHorizontal::new(Some(&Arc::new(RwLock::new(MidiClip::stop_all())))),
size: Default::default(),
//keys: Default::default(),
};
let _ = editor.put_note(true);
let _ = editor.put_note(false);
let _ = editor.clip_status();
let _ = editor.edit_status();
struct TestEditorHost(Option<MidiEditor>);
has_editor!(|self: TestEditorHost|{
editor = self.0;
editor_w = 0;
editor_h = 0;
is_editing = false;
});
let mut host = TestEditorHost(Some(editor));
let _ = host.editor();
let _ = host.editor_mut();
let _ = host.is_editing();
let _ = host.editor_w();
let _ = host.editor_h();
}

View file

@ -46,13 +46,16 @@ pub trait HasScenes: HasSelection + HasEditor + Send + Sync {
self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max)
}
fn scene (&self) -> Option<&Scene> {
self.selected().scene().and_then(|s|self.scenes().get(s))
self.selected().scene()
.and_then(|s|self.scenes().get(s))
}
fn scene_mut (&mut self) -> Option<&mut Scene> {
self.selected().scene().and_then(|s|self.scenes_mut().get_mut(s))
self.selected().scene()
.and_then(|s|self.scenes_mut().get_mut(s))
}
fn scene_del (&mut self, index: usize) {
self.selected().scene().and_then(|s|Some(self.scenes_mut().remove(index)));
fn scene_del (&mut self, index: usize) -> Option<Scene> {
self.selected().scene()
.and_then(|s|Some(self.scenes_mut().remove(index)))
}
/// Set the color of a scene, returning the previous one.
fn scene_set_color (&mut self, index: usize, color: ItemTheme) -> ItemTheme {

View file

@ -53,6 +53,16 @@ impl Selection {
_ => None
}
}
pub fn track_header (&self, track_count: usize) -> Self {
use Selection::*;
match self {
Mix => Track(0),
Scene(_) => Mix,
Track(t) => Track((t + 1) % track_count),
TrackClip { track, .. } => Track(*track),
_ => todo!(),
}
}
pub fn track_next (&self, len: usize) -> Self {
use Selection::*;
match self {

View file

@ -4,10 +4,10 @@ pub(crate) use ::tengri::tui::ratatui::prelude::Position;
#[tengri_proc::view(TuiOut)]
impl App {
fn view_nil (&self) -> impl Content<TuiOut> + use<'_> {
pub fn view_nil (&self) -> impl Content<TuiOut> + use<'_> {
"nil"
}
fn view_status (&self) -> impl Content<TuiOut> + use<'_> {
pub fn view_status (&self) -> impl Content<TuiOut> + use<'_> {
self.update_clock();
let cache = self.view_cache.read().unwrap();
view_status(
@ -15,7 +15,7 @@ impl App {
cache.sr.view.clone(), cache.buf.view.clone(), cache.lat.view.clone(),
)
}
fn view_transport (&self) -> impl Content<TuiOut> + use<'_> {
pub fn view_transport (&self) -> impl Content<TuiOut> + use<'_> {
self.update_clock();
let cache = self.view_cache.read().unwrap();
view_transport(
@ -23,25 +23,25 @@ impl App {
cache.bpm.view.clone(), cache.beat.view.clone(), cache.time.view.clone(),
)
}
fn view_arranger (&self) -> impl Content<TuiOut> + use<'_> {
pub fn view_arranger (&self) -> impl Content<TuiOut> + use<'_> {
ArrangerView::new(self)
}
fn view_pool (&self) -> impl Content<TuiOut> + use<'_> {
pub fn view_pool (&self) -> impl Content<TuiOut> + use<'_> {
self.pool().map(|p|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), p)))
}
fn view_editor (&self) -> impl Content<TuiOut> + use<'_> {
pub fn view_editor (&self) -> impl Content<TuiOut> + use<'_> {
self.editor().map(|e|Bsp::n(Bsp::e(e.clip_status(), e.edit_status()), e))
}
fn view_samples_keys (&self) -> impl Content<TuiOut> + use<'_> {
pub fn view_samples_keys (&self) -> impl Content<TuiOut> + use<'_> {
self.sampler().map(|s|s.view_list(false, self.editor().unwrap()))
}
fn view_samples_grid (&self) -> impl Content<TuiOut> + use<'_> {
pub fn view_samples_grid (&self) -> impl Content<TuiOut> + use<'_> {
self.sampler().map(|s|s.view_grid())
}
fn view_sample_viewer (&self) -> impl Content<TuiOut> + use<'_> {
pub fn view_sample_viewer (&self) -> impl Content<TuiOut> + use<'_> {
self.sampler().map(|s|s.view_sample(self.editor().unwrap().get_note_pos()))
}
fn view_dialog (&self) -> impl Content<TuiOut> + use<'_> {
pub fn view_dialog (&self) -> impl Content<TuiOut> + use<'_> {
When::new(self.dialog.is_some(), Bsp::b(
Fill::xy(Tui::fg_bg(Rgb(64,64,64), Rgb(32,32,32), "")),
Fixed::xy(30, 15, Tui::fg_bg(Rgb(255,255,255), Rgb(16,16,16), Bsp::b(

View file

@ -29,11 +29,11 @@ impl ClockCommand {
state.pause_at(position)?;
Ok(None)
}
fn toggle_playback (state: &mut Clock, position: Option<u32>) -> Perhaps<Self> {
fn toggle_playback (state: &mut Clock, position: u32) -> Perhaps<Self> {
if state.is_rolling() {
state.pause_at(position)?;
state.pause_at(Some(position))?;
} else {
state.play_from(position)?;
state.play_from(Some(position))?;
}
Ok(None)
}

View file

@ -2,15 +2,4 @@ mod lv2_model; pub use self::lv2_model::*;
mod lv2_audio; pub use self::lv2_audio::*;
mod lv2_gui; pub use self::lv2_gui::*;
mod lv2_tui; pub use self::lv2_tui::*;
pub(self) use std::thread::JoinHandle;
pub(self) use ::livi::{
World,
Instance,
Plugin as LiviPlugin,
Features,
FeaturesBuilder,
Port as LiviPort,
event::LV2AtomSequence,
};

View file

@ -13,7 +13,7 @@ mod seq_view; pub use self::seq_view::*;
let mut clip = MidiClip::new("clip", true, 1, None, None);
clip.set_length(96);
clip.toggle_loop();
clip.record_event(12, midly::MidiMessage::NoteOn { key: 36.into(), vel: 100.into() });
clip.record_event(12, MidiMessage::NoteOn { key: 36.into(), vel: 100.into() });
assert!(clip.contains_note_on(36.into(), 6, 18));
assert_eq!(&clip.notes, &clip.duplicate().notes);
@ -25,29 +25,3 @@ mod seq_view; pub use self::seq_view::*;
let player = MidiPlayer::default();
println!("{player:?}");
}
#[cfg(test)] #[test] fn test_midi_edit () {
let editor = MidiEditor::default();
let mut editor = MidiEditor {
mode: PianoHorizontal::new(Some(&Arc::new(RwLock::new(MidiClip::stop_all())))),
size: Default::default(),
keys: Default::default(),
};
let _ = editor.put_note(true);
let _ = editor.put_note(false);
let _ = editor.clip_status();
let _ = editor.edit_status();
struct TestEditorHost(Option<MidiEditor>);
has_editor!(|self: TestEditorHost|{
editor = self.0;
editor_w = 0;
editor_h = 0;
is_editing = false;
});
let mut host = TestEditorHost(Some(editor));
let _ = host.editor();
let _ = host.editor_mut();
let _ = host.is_editing();
let _ = host.editor_w();
let _ = host.editor_h();
}

View file

@ -85,7 +85,11 @@ impl Jack {
// implements audio and MIDI input and output on a realtime basis.
ClosureProcessHandler::new(Box::new({
let app = app.clone();
move|c: &_, s: &_|app.write().unwrap().process(c, s)
move|c: &_, s: &_|if let Ok(mut app) = app.write() {
app.process(c, s)
} else {
Control::Quit
}
}) as BoxedAudioHandler<'j>),
)?;
*self.state.write().unwrap() = Active(client);

View file

@ -5,8 +5,7 @@ mod note; pub use self::note::*;
pub mod jack; pub use self::jack::*;
pub mod midi; pub use self::midi::*;
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, AtomicBool, Ordering::Relaxed}};
pub(crate) use std::path::PathBuf;
pub(crate) use std::sync::{Arc, atomic::{AtomicUsize, AtomicBool, Ordering::Relaxed}};
pub(crate) use std::fmt::Debug;
pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem};
@ -59,14 +58,14 @@ impl InteriorMutable<usize> for AtomicUsize {
#[cfg(test)] #[test] fn test_midi_range () {
let model = MidiRangeModel::from((1, false));
let _ = model.time_len();
let _ = model.time_zoom();
let _ = model.time_lock();
let _ = model.time_start();
let _ = model.time_axis();
let _ = model.time_end();
let _ = model.get_time_len();
let _ = model.get_time_zoom();
let _ = model.get_time_lock();
let _ = model.get_time_start();
let _ = model.get_time_axis();
let _ = model.get_time_end();
let _ = model.note_lo();
let _ = model.note_axis();
let _ = model.note_hi();
let _ = model.get_note_lo();
let _ = model.get_note_axis();
let _ = model.get_note_hi();
}

2
deps/tengri vendored

@ -1 +1 @@
Subproject commit 22d63eed9c9cb5bed5016d851f90773e0f60280d
Subproject commit cb8fd26922fd1cfad4ceadeb89e48544531a178e