mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-08-07 14:06:57 +02:00
multiline view error
This commit is contained in:
parent
07f2290017
commit
b4424fcb69
15 changed files with 979 additions and 949 deletions
398
src/app.rs
398
src/app.rs
|
|
@ -1,398 +0,0 @@
|
|||
use crate::*;
|
||||
pub mod audio; #[allow(unused)] pub use self::audio::*;
|
||||
pub mod bind; pub use self::bind::*;
|
||||
pub mod config; pub use self::config::*;
|
||||
pub mod draw; pub use self::draw::*;
|
||||
pub mod size; pub use self::size::*;
|
||||
primitive!(u8: try_to_u8);
|
||||
primitive!(u16: try_to_u16);
|
||||
primitive!(usize: try_to_usize);
|
||||
primitive!(isize: try_to_isize);
|
||||
impl_has!(Clock: |self: App|self.project.clock);
|
||||
impl_has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
|
||||
impl_has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
|
||||
impl_has!(Dialog: |self: App|self.dialog);
|
||||
impl_has!(Jack<'static>: |self: App|self.jack);
|
||||
impl_has!(Pool: |self: App|self.pool);
|
||||
impl_has!(Selection: |self: App|self.project.selection);
|
||||
impl_as_ref!(Vec<Scene>: |self: App|self.project.as_ref());
|
||||
impl_as_mut!(Vec<Scene>: |self: App|self.project.as_mut());
|
||||
impl_as_ref_opt!(MidiEditor: |self: App|self.project.as_ref_opt());
|
||||
impl_as_mut_opt!(MidiEditor: |self: App|self.project.as_mut_opt());
|
||||
impl_has_clips!( |self: App|self.pool.clips);
|
||||
/// Total application state.
|
||||
///
|
||||
/// ```
|
||||
/// use tek::{HasTracks, HasScenes, TracksView, ScenesView};
|
||||
/// let mut app = tek::App::default();
|
||||
/// let _ = app.scene_add(None, None).unwrap();
|
||||
/// let _ = app.update_clock();
|
||||
/// app.project.editor = Some(Default::default());
|
||||
/// //let _: Vec<_> = app.project.inputs_with_sizes().collect();
|
||||
/// //let _: Vec<_> = app.project.outputs_with_sizes().collect();
|
||||
/// let _: Vec<_> = app.project.tracks_with_sizes().collect();
|
||||
/// //let _: Vec<_> = app.project.scenes_with_sizes(true, 10, 10).collect();
|
||||
/// //let _: Vec<_> = app.scenes_with_colors(true, 10).collect();
|
||||
/// //let _: Vec<_> = app.scenes_with_track_colors(true, 10, 10).collect();
|
||||
/// let _ = app.project.w();
|
||||
/// //let _ = app.project.w_sidebar();
|
||||
/// //let _ = app.project.w_tracks_area();
|
||||
/// let _ = app.project.h();
|
||||
/// //let _ = app.project.h_tracks_area();
|
||||
/// //let _ = app.project.h_inputs();
|
||||
/// //let _ = app.project.h_outputs();
|
||||
/// let _ = app.project.h_scenes();
|
||||
/// ```
|
||||
#[derive(Default, Debug)]
|
||||
#[namespace(u8)]
|
||||
#[namespace(isize)]
|
||||
#[namespace(ItemTheme)]
|
||||
#[namespace(Arc<str> App::get_arc_str)]
|
||||
#[namespace(u16 App::get_u16)]
|
||||
#[namespace(usize App::get_usize)]
|
||||
#[namespace(bool App::get_bool)]
|
||||
#[namespace(Selection App::get_selection)]
|
||||
#[namespace(Color App::get_color)]
|
||||
#[namespace(Option<u7> App::get_opt_u7)]
|
||||
#[namespace(Option<u16> App::get_opt_u16)]
|
||||
#[namespace(Option<usize> App::get_opt_usize)]
|
||||
#[namespace(Option<Arc<RwLock<MidiClip>>> App::get_clip)]
|
||||
pub struct App {
|
||||
/// Base color.
|
||||
pub color: ItemTheme,
|
||||
/// Must not be dropped for the duration of the process
|
||||
pub jack: Jack<'static>,
|
||||
/// Display size
|
||||
pub size: Sizer,
|
||||
/// Performance counter
|
||||
pub perf: PerfModel,
|
||||
/// Available view modes and input bindings
|
||||
pub config: Config,
|
||||
/// Currently selected mode
|
||||
pub mode: Arc<Mode<Arc<str>>>,
|
||||
/// Undo history
|
||||
pub history: Vec<(AppCommand, Option<AppCommand>)>,
|
||||
/// Dialog overlay
|
||||
pub dialog: Dialog,
|
||||
/// Contains all recently created clips.
|
||||
pub pool: Pool,
|
||||
/// Contains the currently edited musical arrangement
|
||||
pub project: Arrangement,
|
||||
/// Error, if any
|
||||
pub error: Arc<RwLock<Option<Arc<str>>>>
|
||||
}
|
||||
impl App {
|
||||
/// Create a new application instance from a backend, project, config, and mode
|
||||
///
|
||||
/// ```
|
||||
/// let jack = tek::tengri::Jack::new(&"test_tek").expect("failed to connect to jack");
|
||||
/// let proj = tek::Arrangement::default();
|
||||
/// let mut conf = tek::Config::default();
|
||||
/// conf.add("(mode hello)");
|
||||
/// let tek = tek::App::new(&jack, proj, conf, "hello");
|
||||
/// ```
|
||||
pub fn new (
|
||||
jack: &Jack<'static>, project: Arrangement, config: Config, mode: impl AsRef<str>
|
||||
) -> Self {
|
||||
let mode: &str = mode.as_ref();
|
||||
App {
|
||||
color: ItemTheme::random(),
|
||||
dialog: Dialog::welcome(),
|
||||
jack: jack.clone(),
|
||||
mode: config.get_mode(mode).expect(&format!("failed to find mode '{mode}'")),
|
||||
config,
|
||||
project,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
/// Update memoized render of clock values.
|
||||
/// ```
|
||||
/// tek::App::default().update_clock();
|
||||
/// ```
|
||||
pub fn update_clock (&self) {
|
||||
ClockView::update_clock(&self.project.clock.view_cache, self.clock(), self.size.w() > 80)
|
||||
}
|
||||
|
||||
/// Set modal dialog.
|
||||
///
|
||||
/// ```
|
||||
/// let previous: tek::Dialog = tek::App::default().set_dialog(tek::Dialog::welcome());
|
||||
/// ```
|
||||
pub fn set_dialog (&mut self, mut dialog: Dialog) -> Dialog {
|
||||
std::mem::swap(&mut self.dialog, &mut dialog);
|
||||
dialog
|
||||
}
|
||||
|
||||
/// FIXME: generalize. Set picked device in device pick dialog.
|
||||
///
|
||||
/// ```
|
||||
/// tek::App::default().device_pick(0);
|
||||
/// ```
|
||||
pub fn device_pick (&mut self, index: usize) {
|
||||
self.dialog = Dialog::Device(index);
|
||||
}
|
||||
|
||||
/// FIXME: generalize. Add device to current track.
|
||||
pub fn add_device (&mut self, index: usize) -> Usually<()> {
|
||||
match index {
|
||||
0 => {
|
||||
let name = self.jack.with_client(|c|c.name().to_string());
|
||||
let midi = self.project.track().expect("no active track").sequencer.midi_outs[0].port_name();
|
||||
let track = self.track().expect("no active track");
|
||||
let port = format!("{}/Sampler", &track.name);
|
||||
let connect = Connect::exact(format!("{name}:{midi}"));
|
||||
let sampler = if let Ok(sampler) = Sampler::new(
|
||||
&self.jack, &port, &[connect], &[&[], &[]], &[&[], &[]]
|
||||
) {
|
||||
self.dialog = Dialog::None;
|
||||
Device::Sampler(sampler)
|
||||
} else {
|
||||
self.dialog = Dialog::Message("Failed to add device.".into());
|
||||
return Err("failed to add device".into())
|
||||
};
|
||||
let track = self.track_mut().expect("no active track");
|
||||
track.devices.push(sampler);
|
||||
Ok(())
|
||||
},
|
||||
1 => {
|
||||
todo!();
|
||||
//Ok(())
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return reference to content browser if open.
|
||||
///
|
||||
/// ```
|
||||
/// assert_eq!(tek::App::default().browser(), None);
|
||||
/// ```
|
||||
pub fn browser (&self) -> Option<&Browse> {
|
||||
if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None }
|
||||
}
|
||||
|
||||
/// Is a MIDI editor currently focused?
|
||||
///
|
||||
/// ```
|
||||
/// tek::App::default().editor_focused();
|
||||
/// ```
|
||||
pub fn editor_focused (&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Toggle MIDI editor.
|
||||
///
|
||||
/// ```
|
||||
/// tek::App::default().toggle_editor(None);
|
||||
/// ```
|
||||
pub fn toggle_editor (&mut self, value: Option<bool>) {
|
||||
//FIXME: self.editing.store(value.unwrap_or_else(||!self.is_editing()), Relaxed);
|
||||
let value = value.unwrap_or_else(||!self.editor().is_some());
|
||||
if value {
|
||||
// Create new clip in pool when entering empty cell
|
||||
if let Selection::TrackClip { track, scene } = *self.selection()
|
||||
&& let Some(scene) = self.project.scenes.get_mut(scene)
|
||||
&& let Some(slot) = scene.clips.get_mut(track)
|
||||
&& slot.is_none()
|
||||
&& let Some(track) = self.project.tracks.get_mut(track)
|
||||
{
|
||||
let (_index, clip) = self.pool.add_new_clip();
|
||||
// autocolor: new clip colors from scene and track color
|
||||
let color = track.color.base.mix(scene.color.base, 0.5);
|
||||
clip.write().unwrap().color = ItemColor::random_near(color, 0.2).into();
|
||||
if let Some(editor) = &mut self.project.editor {
|
||||
editor.set_clip(Some(&clip));
|
||||
}
|
||||
*slot = Some(clip.clone());
|
||||
//Some(clip)
|
||||
} else {
|
||||
//None
|
||||
}
|
||||
} else if let Selection::TrackClip { track, scene } = *self.selection()
|
||||
&& let Some(scene) = self.project.scenes.get_mut(scene)
|
||||
&& let Some(slot) = scene.clips.get_mut(track)
|
||||
&& let Some(clip) = slot.as_mut()
|
||||
{
|
||||
// Remove clip from arrangement when exiting empty clip editor
|
||||
let mut swapped = None;
|
||||
if clip.read().unwrap().count_midi_messages() == 0 {
|
||||
std::mem::swap(&mut swapped, slot);
|
||||
}
|
||||
if let Some(clip) = swapped {
|
||||
self.pool.delete_clip(&clip.read().unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
fn get_arc_str (&self, src: impl Language) -> Perhaps<Arc<str>> {
|
||||
Ok(src.src()?.map(|x|x.into()))
|
||||
}
|
||||
fn get_u16 (&self, src: impl Language) -> Perhaps<u16> {
|
||||
Ok(Some(match src.word()? {
|
||||
Some(":w/sidebar") => self.project.w_sidebar(self.editor().is_some()),
|
||||
Some(":h/sample-detail") => 6.max(self.size.h() as u16 * 3 / 9),
|
||||
_ => return try_to_u16(src)
|
||||
}))
|
||||
}
|
||||
fn get_usize (&self, src: impl Language) -> Perhaps<usize> {
|
||||
Ok(Some(match src.word()? {
|
||||
Some(":scene-count") => self.scenes().len(),
|
||||
Some(":track-count") => self.tracks().len(),
|
||||
Some(":device-kind") => self.dialog.device_kind().unwrap_or(0),
|
||||
Some(":device-kind/next") => self.dialog.device_kind_next().unwrap_or(0),
|
||||
Some(":device-kind/prev") => self.dialog.device_kind_prev().unwrap_or(0),
|
||||
_ => return try_to_usize(src)
|
||||
}))
|
||||
}
|
||||
fn get_bool (&self, src: impl Language) -> Perhaps<bool> {
|
||||
src.word()?.map(|word|Ok(match word {
|
||||
"Y" => true,
|
||||
"N" => false,
|
||||
":mode/editor" => self.project.editor.is_some(),
|
||||
":focused/dialog" => !matches!(self.dialog, Dialog::None),
|
||||
":focused/message" => matches!(self.dialog, Dialog::Message(..)),
|
||||
":focused/add_device" => matches!(self.dialog, Dialog::Device(..)),
|
||||
":focused/browser" => self.dialog.browser().is_some(),
|
||||
":focused/pool/import" => matches!(self.pool.mode, Some(PoolMode::Import(..))),
|
||||
":focused/pool/export" => matches!(self.pool.mode, Some(PoolMode::Export(..))),
|
||||
":focused/pool/rename" => matches!(self.pool.mode, Some(PoolMode::Rename(..))),
|
||||
":focused/pool/length" => matches!(self.pool.mode, Some(PoolMode::Length(..))),
|
||||
":focused/clip" => !self.editor_focused() && matches!(self.selection(), Selection::TrackClip{..}),
|
||||
":focused/track" => !self.editor_focused() && matches!(self.selection(), Selection::Track(..)),
|
||||
":focused/scene" => !self.editor_focused() && matches!(self.selection(), Selection::Scene(..)),
|
||||
":focused/mix" => !self.editor_focused() && matches!(self.selection(), Selection::Mix),
|
||||
_ => return Err(format!("not bool: {word}").into())
|
||||
})).transpose()
|
||||
}
|
||||
fn get_selection (&self, src: impl Language) -> Perhaps<Selection> {
|
||||
src.word()?.map(|word|Ok(match word {
|
||||
":select/scene" => self.selection().select_scene(self.tracks().len()),
|
||||
":select/scene/next" => self.selection().select_scene_next(self.scenes().len()),
|
||||
":select/scene/prev" => self.selection().select_scene_prev(),
|
||||
":select/track" => self.selection().select_track(self.tracks().len()),
|
||||
":select/track/next" => self.selection().select_track_next(self.tracks().len()),
|
||||
":select/track/prev" => self.selection().select_track_prev(),
|
||||
_ => return Err(format!("not selection: {word}").into())
|
||||
})).transpose()
|
||||
}
|
||||
fn get_color (&self, src: impl Language) -> Perhaps<Color> {
|
||||
if let Some(expr) = src.expr()? {
|
||||
match (expr.head()?, expr.tail()?) {
|
||||
(Some("g"), Some(tail)) => {
|
||||
let n = try_to_u8(expr.tail().map_err(Into::into))?.ok_or(LanguageError::Domain("not gray"))?;
|
||||
Ok(Some(Color::Rgb(n, n, n)))
|
||||
},
|
||||
(Some("rgb"), Some(tail)) => {
|
||||
let r = try_to_u8(expr.tail().map_err(Into::into))?
|
||||
.ok_or(LanguageError::Domain("not red"))?;
|
||||
let g = try_to_u8(expr.tail().tail().head().map_err(Into::into))?
|
||||
.ok_or(LanguageError::Domain("not green"))?;
|
||||
let b = try_to_u8(expr.tail().tail().tail().head().map_err(Into::into))?
|
||||
.ok_or(LanguageError::Domain("not blue"))?;
|
||||
Ok(Some(Color::Rgb(r, g, b)))
|
||||
},
|
||||
(Some(_), _) => return Err(format!("not a color expression: {expr}").into()),
|
||||
(None, _) => return Err(format!("not a color expression: {expr}").into()),
|
||||
}
|
||||
} else if let Ok(Some(sym)) = src.word() {
|
||||
Ok(match sym {
|
||||
":color/bg" => Some(Color::Rgb(28, 32, 36)),
|
||||
":color/fg" => Some(Color::Rgb(98, 92, 96)),
|
||||
_ => return Err(format!("not a color: {sym}").into())
|
||||
})
|
||||
} else {
|
||||
return Err(format!("not a color: {:?}", src.src()?).into())
|
||||
}
|
||||
}
|
||||
fn get_opt_u7 (&self, src: impl Language) -> Perhaps<Option<u7>> {
|
||||
src.word()?.map(|word|Ok(match word {
|
||||
":editor/pitch" => Some((
|
||||
self.editor().as_ref().map(|e|e.get_note_pos()).unwrap() as u8
|
||||
).into()),
|
||||
_ => return Err(format!("unknown midi note: {word}").into())
|
||||
})).transpose()
|
||||
}
|
||||
fn get_opt_u16 (&self, _src: impl Language) -> Perhaps<Option<u16>> {
|
||||
Ok(None)
|
||||
}
|
||||
fn get_opt_usize (&self, src: impl Language) -> Perhaps<Option<usize>> {
|
||||
src.word()?.map(|word|Ok(match word {
|
||||
":selected/scene" => self.selection().scene(),
|
||||
":selected/track" => self.selection().track(),
|
||||
_ => return Err(format!("unknown opt<usize>: {word}").into())
|
||||
})).transpose()
|
||||
}
|
||||
fn get_clip (&self, src: impl Language) -> Perhaps<Option<Arc<RwLock<MidiClip>>>> {
|
||||
src.word()?.map(|word|Ok(match word {
|
||||
":selected/clip" if let Selection::TrackClip { track, scene } = self.selection() =>
|
||||
self.scenes()[*scene].clips[*track].clone(),
|
||||
_ => return Err(format!("not a clip: {word}").into())
|
||||
})).transpose()
|
||||
}
|
||||
pub fn inc (&mut self, axis: &ControlAxis) -> Perhaps<AppCommand> {
|
||||
Ok(match (&self.dialog, axis) {
|
||||
(Dialog::None, _) => todo!(),
|
||||
(Dialog::Menu(_, _), ControlAxis::Y) =>
|
||||
AppCommand::SetDialog { dialog: self.dialog.menu_next() }.act(self)?,
|
||||
_ => todo!()
|
||||
})
|
||||
}
|
||||
pub fn dec (&mut self, axis: &ControlAxis) -> Perhaps<AppCommand> {
|
||||
Ok(match (&self.dialog, axis) {
|
||||
(Dialog::None, _) => None,
|
||||
(Dialog::Menu(_, _), ControlAxis::Y) =>
|
||||
AppCommand::SetDialog { dialog: self.dialog.menu_prev() }.act(self)?,
|
||||
_ => todo!()
|
||||
})
|
||||
}
|
||||
pub fn confirm (&mut self) -> Perhaps<AppCommand> {
|
||||
Ok(match &self.dialog {
|
||||
Dialog::Menu(index, items) => {
|
||||
let callback = items.0[*index].1.clone();
|
||||
callback(self)?;
|
||||
None
|
||||
},
|
||||
_ => todo!(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn swap_value <T: Clone + PartialEq, U> (
|
||||
target: &mut T, value: &T, returned: impl Fn(T)->U
|
||||
) -> Perhaps<U> {
|
||||
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 <U> (
|
||||
target: &mut bool, value: &Option<bool>, returned: impl Fn(Option<bool>)->U
|
||||
) -> Perhaps<U> {
|
||||
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 scan (dir: &PathBuf) -> Usually<(Vec<OsString>, Vec<OsString>)> {
|
||||
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))
|
||||
}
|
||||
|
|
@ -11,13 +11,15 @@ fn tek_commands_collect (app: &App, input: &TuiEvent)
|
|||
-> Usually<Vec<AppCommand>>
|
||||
{
|
||||
let mut commands = vec![];
|
||||
for id in app.mode.keys.iter() {
|
||||
if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref())
|
||||
&& let Some(bindings) = event_map.query(input) {
|
||||
for binding in bindings {
|
||||
for command in binding.commands.iter() {
|
||||
if let Some(command) = app.namespace(command)? as Option<AppCommand> {
|
||||
commands.push(command)
|
||||
if let Some(ref mode) = app.mode {
|
||||
for id in mode.keys.iter() {
|
||||
if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref())
|
||||
&& let Some(bindings) = event_map.query(input) {
|
||||
for binding in bindings {
|
||||
for command in binding.commands.iter() {
|
||||
if let Some(command) = app.namespace(command)? as Option<AppCommand> {
|
||||
commands.push(command)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,6 +115,49 @@ impl Config {
|
|||
}
|
||||
}
|
||||
|
||||
mod views; pub use self::views::*;
|
||||
mod modes; pub use self::modes::*;
|
||||
mod mode; pub use self::mode::*;
|
||||
pub fn print_config (config: &Config) {
|
||||
use ::ansi_term::Color::*;
|
||||
println!("{:?}", config.dirs);
|
||||
for (k, v) in config.views.read().unwrap().iter() {
|
||||
println!("{} {} {v}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}")));
|
||||
}
|
||||
for (k, v) in config.binds.read().unwrap().iter() {
|
||||
println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}")));
|
||||
for (k, v) in v.0.iter() {
|
||||
print!("{} ", &Yellow.paint(match &k.0 {
|
||||
Event::Key(KeyEvent { modifiers, .. }) =>
|
||||
format!("{:>16}", format!("{modifiers}")),
|
||||
_ => unimplemented!()
|
||||
}));
|
||||
print!("{}", &Yellow.bold().paint(match &k.0 {
|
||||
Event::Key(KeyEvent { code, .. }) =>
|
||||
format!("{:<10}", format!("{code}")),
|
||||
_ => unimplemented!()
|
||||
}));
|
||||
for v in v.iter() {
|
||||
print!(" => {:?}", v.commands);
|
||||
print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default());
|
||||
println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default());
|
||||
//println!(" {:?}", v.source);
|
||||
}
|
||||
}
|
||||
}
|
||||
config.modes.for_each(|k, v|{
|
||||
println!();
|
||||
for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); }
|
||||
for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); }
|
||||
print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}")));
|
||||
print!("\n{}", Blue.paint("KEYS"));
|
||||
for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); }
|
||||
println!();
|
||||
v.modes.for_each(|k, v|{
|
||||
print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name);
|
||||
print!( " INFO={:?}", v.info);
|
||||
print!( " VIEW={:?}", v.view);
|
||||
println!(" KEYS={:?}", v.keys);
|
||||
});
|
||||
print!("{}", Blue.paint("VIEW"));
|
||||
for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); }
|
||||
println!();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
/// Collection of UI modes.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Modes(
|
||||
Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode<Arc<str>>>>>>
|
||||
);
|
||||
|
||||
impl Modes {
|
||||
pub fn add (&self, name: &impl AsRef<str>, body: &impl Language) -> Usually<()> {
|
||||
let mut mode = Mode::default();
|
||||
body.each(|item|mode.add(item))?;
|
||||
self.0.write().unwrap().insert(name.as_ref().into(), Arc::new(mode));
|
||||
Ok(())
|
||||
}
|
||||
pub fn get (&self, name: impl AsRef<str>) -> Option<Arc<Mode<Arc<str>>>> {
|
||||
self.0.read().unwrap().get(name.as_ref()).cloned()
|
||||
}
|
||||
pub fn for_each <T> (&self, mut ator: impl FnMut(&str, &Mode<Arc<str>>)->T) {
|
||||
for (k, v) in self.0.read().unwrap().iter() {
|
||||
let _ = ator(k.as_ref(), v.as_ref());
|
||||
}
|
||||
}
|
||||
pub fn len (&self) -> usize {
|
||||
self.0.read().unwrap().len()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
/// Collection of custom view definitions.
|
||||
pub type Views = Arc<RwLock<BTreeMap<Arc<str>, Arc<str>>>>;
|
||||
|
||||
/// Load custom view definition.
|
||||
pub(crate) fn load_view (views: &Views, name: &impl AsRef<str>, body: &impl Language) -> Usually<()> {
|
||||
views.write().unwrap().insert(name.as_ref().into(), body.src()?.unwrap_or_default().into());
|
||||
Ok(())
|
||||
}
|
||||
179
src/app/draw.rs
179
src/app/draw.rs
|
|
@ -1,5 +1,21 @@
|
|||
use crate::*;
|
||||
|
||||
/// Collection of custom view definitions.
|
||||
pub type Views = Arc<RwLock<BTreeMap<Arc<str>, Arc<str>>>>;
|
||||
|
||||
/// Load custom view definition.
|
||||
pub(crate) fn load_view (
|
||||
views: &Views,
|
||||
name: &impl AsRef<str>,
|
||||
body: &impl Language,
|
||||
) -> Usually<()> {
|
||||
views.write().unwrap().insert(
|
||||
name.as_ref().into(),
|
||||
body.src()?.unwrap_or_default().into()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The [Draw] implementation for [App] handles the loaded view,
|
||||
/// which is defined in terms of [dizzle] DSL.
|
||||
///
|
||||
|
|
@ -9,17 +25,24 @@ impl View<Tui> for App {
|
|||
fn view (&self) -> impl Draw<Tui> {
|
||||
thunk(|to: &mut Tui|{
|
||||
let xywh = to.area().into();
|
||||
|
||||
if let Some(e) = self.error.read().unwrap().as_ref() {
|
||||
to.show(area(xywh, e.as_ref()))?;
|
||||
//to.show(area(xywh, format!("KYPbanica {xywh:?}").align_c()))?;
|
||||
//to.show(ShowSize.align_se())?;
|
||||
to.show(e.as_ref().align_c())?;
|
||||
}
|
||||
for (index, dsl) in self.mode.view.iter().enumerate() {
|
||||
if let Err(e) = self.interpret(to, dsl) {
|
||||
*self.error.write().unwrap() = Some(format!(
|
||||
"mode {:?} view #{index}: {e}", &self.mode.name,
|
||||
).into());
|
||||
break;
|
||||
|
||||
if let Some(ref mode) = self.mode {
|
||||
for (index, dsl) in mode.view.iter().enumerate() {
|
||||
if let Err(e) = self.interpret(to, dsl) {
|
||||
let src = &dsl.src().unwrap_or(Some("<source error>")).unwrap_or("<no source>");
|
||||
let message = format!("mode {:?} view #{index}:\n{e}\n{}", &mode.name, &src);
|
||||
*self.error.write().unwrap() = Some(message.into());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(xywh))
|
||||
})
|
||||
}
|
||||
|
|
@ -144,6 +167,77 @@ pub fn draw_templates (to: &mut Tui, frags: std::str::Split<&str>, state: &App)
|
|||
}).min_w(30).exact_h(height).draw(to)
|
||||
}
|
||||
|
||||
pub fn per_track <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
|
||||
tracks: impl Fn() -> U + Send + Sync + 'a,
|
||||
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
||||
) -> impl Draw<Tui> + 'a {
|
||||
per_track_top(tracks, move|index, track|callback(index, track).full_h().align_y())
|
||||
}
|
||||
|
||||
pub fn per_track_top <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
|
||||
tracks: impl Fn() -> U + Send + Sync + 'a,
|
||||
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
||||
) -> impl Draw<Tui> + 'a {
|
||||
bg(Reset, iter_east(tracks,
|
||||
move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{
|
||||
fg_bg(
|
||||
track.color.lightest.term,
|
||||
track.color.base.term,
|
||||
callback(index, track)
|
||||
).exact_w((x2 - x1) as u16)
|
||||
}).align_x())
|
||||
}
|
||||
|
||||
pub fn field_h <T: Screen> (
|
||||
_theme: ItemTheme, _head: impl Draw<T>, _body: impl Draw<T>
|
||||
) -> impl Draw<T> {
|
||||
}
|
||||
|
||||
pub fn field_v <T: Screen> (
|
||||
_theme: ItemTheme, _head: impl Draw<T>, _body: impl Draw<T>
|
||||
) -> impl Draw<T> {
|
||||
}
|
||||
|
||||
pub fn view_sessions () -> impl Draw<Tui> {
|
||||
let h = 6;
|
||||
let w = Some(30);
|
||||
let f = Rgb(224, 192, 128);
|
||||
thunk(move |to: &mut Tui|{
|
||||
for (index, name) in ["session1", "session2", "session3"].iter().enumerate() {
|
||||
let b = if index == 0 { Rgb(50,50,50) } else { Rgb(40,40,40) };
|
||||
let y = (2 * index) as u16;
|
||||
let h = 2;
|
||||
bg(b, fg(f, *name).align_w()).full_w().exact_h(h).push_y(y).draw(to)?;
|
||||
}
|
||||
Ok(Some(to.area().into()))
|
||||
}).min_w(w).exact_h(h)
|
||||
}
|
||||
|
||||
pub fn view_browse_title (state: &App) -> impl Draw<Tui> {
|
||||
field_v(ItemTheme::default(),
|
||||
match state.dialog.browser_target().unwrap() {
|
||||
BrowseTarget::SaveProject => "Save project:",
|
||||
BrowseTarget::LoadProject => "Load project:",
|
||||
BrowseTarget::ImportSample(_) => "Import sample:",
|
||||
BrowseTarget::ExportSample(_) => "Export sample:",
|
||||
BrowseTarget::ImportClip(_) => "Import clip:",
|
||||
BrowseTarget::ExportClip(_) => "Export clip:",
|
||||
}, fg(g(96), x_repeat("🭻")).exact_h(1)
|
||||
).align_w().full_w()
|
||||
}
|
||||
|
||||
pub fn view_device (state: &App) -> impl Draw<Tui> {
|
||||
let selected = state.dialog.device_kind().unwrap();
|
||||
south(bold(true, "Add device"), iter_south(
|
||||
move||device_kinds().iter(),
|
||||
move|_label: &&'static str, i|{
|
||||
let b = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) };
|
||||
let l = if i == selected { "[ " } else { " " };
|
||||
let r = if i == selected { " ]" } else { " " };
|
||||
bg(b, east(l, west(r, "FIXME device name"))).full_w()
|
||||
}))
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// let x = "";
|
||||
/// let _ = tek::view_transport(true, x.as_ref(), x.as_ref(), x.as_ref());
|
||||
|
|
@ -677,74 +771,3 @@ pub fn view_track_devices (
|
|||
Some(h + 1),
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn per_track <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
|
||||
tracks: impl Fn() -> U + Send + Sync + 'a,
|
||||
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
||||
) -> impl Draw<Tui> + 'a {
|
||||
per_track_top(tracks, move|index, track|callback(index, track).full_h().align_y())
|
||||
}
|
||||
|
||||
pub fn per_track_top <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
|
||||
tracks: impl Fn() -> U + Send + Sync + 'a,
|
||||
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
||||
) -> impl Draw<Tui> + 'a {
|
||||
bg(Reset, iter_east(tracks,
|
||||
move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{
|
||||
fg_bg(
|
||||
track.color.lightest.term,
|
||||
track.color.base.term,
|
||||
callback(index, track)
|
||||
).exact_w((x2 - x1) as u16)
|
||||
}).align_x())
|
||||
}
|
||||
|
||||
pub fn field_h <T: Screen> (
|
||||
_theme: ItemTheme, _head: impl Draw<T>, _body: impl Draw<T>
|
||||
) -> impl Draw<T> {
|
||||
}
|
||||
|
||||
pub fn field_v <T: Screen> (
|
||||
_theme: ItemTheme, _head: impl Draw<T>, _body: impl Draw<T>
|
||||
) -> impl Draw<T> {
|
||||
}
|
||||
|
||||
pub fn view_sessions () -> impl Draw<Tui> {
|
||||
let h = 6;
|
||||
let w = Some(30);
|
||||
let f = Rgb(224, 192, 128);
|
||||
thunk(move |to: &mut Tui|{
|
||||
for (index, name) in ["session1", "session2", "session3"].iter().enumerate() {
|
||||
let b = if index == 0 { Rgb(50,50,50) } else { Rgb(40,40,40) };
|
||||
let y = (2 * index) as u16;
|
||||
let h = 2;
|
||||
bg(b, fg(f, *name).align_w()).full_w().exact_h(h).push_y(y).draw(to)?;
|
||||
}
|
||||
Ok(Some(to.area().into()))
|
||||
}).min_w(w).exact_h(h)
|
||||
}
|
||||
|
||||
pub fn view_browse_title (state: &App) -> impl Draw<Tui> {
|
||||
field_v(ItemTheme::default(),
|
||||
match state.dialog.browser_target().unwrap() {
|
||||
BrowseTarget::SaveProject => "Save project:",
|
||||
BrowseTarget::LoadProject => "Load project:",
|
||||
BrowseTarget::ImportSample(_) => "Import sample:",
|
||||
BrowseTarget::ExportSample(_) => "Export sample:",
|
||||
BrowseTarget::ImportClip(_) => "Import clip:",
|
||||
BrowseTarget::ExportClip(_) => "Export clip:",
|
||||
}, fg(g(96), x_repeat("🭻")).exact_h(1)
|
||||
).align_w().full_w()
|
||||
}
|
||||
|
||||
pub fn view_device (state: &App) -> impl Draw<Tui> {
|
||||
let selected = state.dialog.device_kind().unwrap();
|
||||
south(bold(true, "Add device"), iter_south(
|
||||
move||device_kinds().iter(),
|
||||
move|_label: &&'static str, i|{
|
||||
let b = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) };
|
||||
let l = if i == selected { "[ " } else { " " };
|
||||
let r = if i == selected { " ]" } else { " " };
|
||||
bg(b, east(l, west(r, "FIXME device name"))).full_w()
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,31 @@
|
|||
use crate::*;
|
||||
|
||||
/// Collection of UI modes.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Modes(
|
||||
Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode<Arc<str>>>>>>
|
||||
);
|
||||
|
||||
impl Modes {
|
||||
pub fn add (&self, name: &impl AsRef<str>, body: &impl Language) -> Usually<()> {
|
||||
let mut mode = Mode::default();
|
||||
body.each(|item|mode.add(item))?;
|
||||
self.0.write().unwrap().insert(name.as_ref().into(), Arc::new(mode));
|
||||
Ok(())
|
||||
}
|
||||
pub fn get (&self, name: impl AsRef<str>) -> Option<Arc<Mode<Arc<str>>>> {
|
||||
self.0.read().unwrap().get(name.as_ref()).cloned()
|
||||
}
|
||||
pub fn for_each <T> (&self, mut ator: impl FnMut(&str, &Mode<Arc<str>>)->T) {
|
||||
for (k, v) in self.0.read().unwrap().iter() {
|
||||
let _ = ator(k.as_ref(), v.as_ref());
|
||||
}
|
||||
}
|
||||
pub fn len (&self) -> usize {
|
||||
self.0.read().unwrap().len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Group of view and keys definitions.
|
||||
///
|
||||
/// ```
|
||||
|
|
@ -41,7 +67,7 @@ impl Mode<Arc<str>> {
|
|||
_ => self.add_view(tail)?,
|
||||
};
|
||||
} else if let Ok(Some(word)) = dsl.word() {
|
||||
self.add_view(word);
|
||||
self.add_view(word)?;
|
||||
} else {
|
||||
return Err(format!("Mode::add: unexpected: {dsl:?}").into());
|
||||
})
|
||||
32
src/deps.rs
32
src/deps.rs
|
|
@ -1,32 +0,0 @@
|
|||
#[allow(unused)]
|
||||
pub(crate) use ::{
|
||||
std::{
|
||||
cmp::Ord,
|
||||
collections::BTreeMap,
|
||||
error::Error,
|
||||
ffi::OsString,
|
||||
fmt::{Write, Debug, Formatter},
|
||||
fs::File,
|
||||
ops::{Add, Sub, Mul, Div, Rem},
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed}},
|
||||
time::Duration,
|
||||
thread::{spawn, JoinHandle},
|
||||
},
|
||||
xdg::{
|
||||
BaseDirectories,
|
||||
},
|
||||
tengri::{
|
||||
*,
|
||||
lang::*,
|
||||
midly::{Smf, TrackEventKind, MidiMessage, Error as MidiError, num::*, live::*},
|
||||
crossterm::event::{Event, KeyEvent},
|
||||
ratatui::{
|
||||
self,
|
||||
prelude::{Rect, Style, Stylize, Buffer, Color::{self, *}},
|
||||
widgets::{Widget, canvas::{Canvas, Line}},
|
||||
},
|
||||
},
|
||||
};
|
||||
#[cfg(feature = "cli")]
|
||||
pub(crate) use ::clap::{self, Parser, Subcommand};
|
||||
144
src/device.rs
144
src/device.rs
|
|
@ -1,144 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
def_command!(DeviceCommand: |device: Device| {});
|
||||
|
||||
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!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A device that can be plugged into the chain.
|
||||
///
|
||||
/// ```
|
||||
/// let device = tek::Device::default();
|
||||
/// ```
|
||||
#[derive(Debug, Default)] pub enum Device {
|
||||
#[default]
|
||||
Bypass,
|
||||
Mute,
|
||||
#[cfg(feature = "sampler")]
|
||||
Sampler(Sampler),
|
||||
#[cfg(feature = "lv2")] // TODO
|
||||
Lv2(Lv2),
|
||||
#[cfg(feature = "vst2")] // TODO
|
||||
Vst2,
|
||||
#[cfg(feature = "vst3")] // TODO
|
||||
Vst3,
|
||||
#[cfg(feature = "clap")] // TODO
|
||||
Clap,
|
||||
#[cfg(feature = "sf2")] // TODO
|
||||
Sf2,
|
||||
}
|
||||
|
||||
/// Some sort of wrapper?
|
||||
pub struct DeviceAudio<'a>(pub &'a mut Device);
|
||||
|
||||
impl_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
|
||||
}
|
||||
});
|
||||
|
||||
pub fn device_kinds () -> &'static [&'static str] {
|
||||
&[
|
||||
#[cfg(feature = "sampler")] "Sampler",
|
||||
#[cfg(feature = "lv2")] "Plugin (LV2)",
|
||||
]
|
||||
}
|
||||
|
||||
impl<T: AsRef<Vec<Device>> + AsMut<Vec<Device>>> HasDevices for T {
|
||||
fn devices (&self) -> &Vec<Device> {
|
||||
self.as_ref()
|
||||
}
|
||||
fn devices_mut (&mut self) -> &mut Vec<Device> {
|
||||
self.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasDevices: AsRef<Vec<Device>> + AsMut<Vec<Device>> {
|
||||
fn devices (&self) -> &Vec<Device> {
|
||||
self.as_ref()
|
||||
}
|
||||
fn devices_mut (&mut self) -> &mut Vec<Device> {
|
||||
self.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
pub use ::tengri::sing::*;
|
||||
pub mod arrange; pub use self::arrange::*;
|
||||
pub mod browse; pub use self::browse::*;
|
||||
pub mod clock; pub use self::clock::*;
|
||||
pub mod dialog; pub use self::dialog::*;
|
||||
pub mod editor; pub use self::editor::*;
|
||||
pub mod menu; pub use self::menu::*;
|
||||
pub mod meter; pub use self::meter::*;
|
||||
pub mod mix; pub use self::mix::*;
|
||||
pub mod sampler; pub use self::sampler::*;
|
||||
pub mod sequence; pub use self::sequence::*;
|
||||
|
||||
#[cfg(feature = "plugin")] pub mod plugin;
|
||||
#[cfg(feature = "plugin")] pub use self::plugin::*;
|
||||
|
||||
def_command!(AudioInputCommand: |port: AudioInput| {
|
||||
Close => todo!(),
|
||||
Connect { audio_out: Arc<str> } => todo!(),
|
||||
});
|
||||
|
||||
def_command!(AudioOutputCommand: |port: AudioOutput| {
|
||||
Close => todo!(),
|
||||
Connect { audio_in: Arc<str> } => todo!(),
|
||||
});
|
||||
|
||||
def_command!(MidiInputCommand: |port: MidiInput| {
|
||||
Close => todo!(),
|
||||
Connect { midi_out: Arc<str> } => todo!(),
|
||||
});
|
||||
|
||||
def_command!(MidiOutputCommand: |port: MidiOutput| {
|
||||
Close => todo!(),
|
||||
Connect { midi_in: Arc<str> } => todo!(),
|
||||
});
|
||||
|
||||
pub struct Junction<T: JackPort>(T);
|
||||
|
||||
impl<T: JackPort> View<Tui> for Junction<T> {
|
||||
fn view (&self) -> impl Draw<Tui> {
|
||||
T::KIND
|
||||
}
|
||||
}
|
||||
|
|
@ -7,12 +7,12 @@ use crate::*;
|
|||
/// ```
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Arrangement {
|
||||
/// JACK client handle.
|
||||
pub jack: Jack<'static>,
|
||||
/// Project name.
|
||||
pub name: Arc<str>,
|
||||
/// Base color.
|
||||
pub color: ItemTheme,
|
||||
/// JACK client handle.
|
||||
pub jack: Jack<'static>,
|
||||
/// FIXME a render of the project arrangement, redrawn on update.
|
||||
/// TODO rename to "render_cache" or smth
|
||||
pub arranger: Arc<RwLock<Buffer>>,
|
||||
|
|
|
|||
|
|
@ -55,17 +55,23 @@ impl Dialog {
|
|||
/// ```
|
||||
pub fn welcome () -> Self {
|
||||
Self::Menu(1, MenuItems([
|
||||
|
||||
MenuItem("Resume session".into(), Arc::new(Box::new(|_|Ok(())))),
|
||||
|
||||
MenuItem("New session".into(), Arc::new(Box::new(|app|Ok({
|
||||
app.dialog = Dialog::None;
|
||||
app.mode = app.config.modes.get(":arranger").unwrap();
|
||||
app.mode = app.config.modes.get(":arranger");
|
||||
})))),
|
||||
|
||||
MenuItem("Load session".into(), Arc::new(Box::new(|_|Ok(())))),
|
||||
|
||||
MenuItem("Exit".into(), Arc::new(Box::new(|_|Ok({
|
||||
()
|
||||
})))),
|
||||
|
||||
].into()))
|
||||
}
|
||||
|
||||
/// FIXME: generalize
|
||||
/// ```
|
||||
/// let _ = tek::Dialog::welcome().menu_selected();
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ impl <T: AsRef<Sequencer>+AsMut<Sequencer>> HasSequencer for T {}
|
|||
/// let mut clip = tek::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, ::tengri::midly::MidiMessage::NoteOn { key: 36.into(), vel: 100.into() });
|
||||
/// assert!(clip.contains_note_on(36.into(), 6, 18));
|
||||
/// assert_eq!(&clip.notes, &clip.duplicate().notes);
|
||||
///
|
||||
|
|
|
|||
65
src/tek.edn
65
src/tek.edn
|
|
@ -2,12 +2,14 @@
|
|||
(bsp/s (exact/y 1 (text ~~~~ ║ ~ ╟─╌ ~╟─< ~~ v0.3.0 ~~))
|
||||
(bsp/s (exact/y 1 (text ~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~)) (text dig?)))))
|
||||
|
||||
(view :browse (bsp/s (padding 3 1 :browse-title) (enclose (fg (g 96)) browser)))
|
||||
(view :browse (bsp/s
|
||||
(padding 3 1 :browse-title)
|
||||
(enclose (fg (g 96)) browser)))
|
||||
|
||||
(mode :transport (name Transport) (info JACK transport controller.) (keys :clock :global)
|
||||
:transport)
|
||||
|
||||
(mode :menu (name Menu) (info Mode selector.) (keys :axis/y :confirm)
|
||||
(mode :menu (name Menu) (info Mode selector.) (keys :axis/y :confirm)
|
||||
(view (bg (g 0)
|
||||
(bsp/s (max/y 2 :transport
|
||||
(bsp/s (max/y 3 (bg (g 80) :ports/out))
|
||||
|
|
@ -29,13 +31,13 @@
|
|||
(bsp/n (fixed/y 1 :status)
|
||||
(fill :samples/grid))))
|
||||
|
||||
(view :ports/out (fill/x (bsp/s (fill/x (align/w (text L-AUDIO-OUT)))
|
||||
(bsp/e (text MIDI-OUT)
|
||||
(fill/x (align/e (text AUDIO-OUT-R)))))))
|
||||
(view :ports/out
|
||||
(fill/x (bsp/s (fill/x (align/w (text L-AUDIO-OUT)))
|
||||
(bsp/e (text MIDI-OUT) (fill/x (align/e (text AUDIO-OUT-R)))))))
|
||||
|
||||
(view :ports/in (fill/x (bsp/s (fill/x (align/w (text L-AUDIO-IN)))
|
||||
(bsp/e (text MIDI-IN)
|
||||
(fill/x (align/e (text AUDIO-IN-R)))))))
|
||||
(view :ports/in
|
||||
(fill/x (bsp/s (fill/x (align/w (text L-AUDIO-IN)))
|
||||
(bsp/e (text MIDI-IN) (fill/x (align/e (text AUDIO-IN-R)))))))
|
||||
|
||||
(mode :groovebox (name Groovebox) (info Sequencer with sampler.)
|
||||
(keys :clock :editor :sampler :global)
|
||||
|
|
@ -84,7 +86,8 @@
|
|||
(keys :axis/w (@openbracket w/dec) (@closebracket w/inc))
|
||||
(keys :axis/w2 (@openbrace w2/dec) (@closebrace w2/inc))
|
||||
(keys :focus)
|
||||
(keys :editor (see :axis/i :axis/i2 :axis/y :page :editor/view :editor/add :editor/del))
|
||||
(keys :editor (see :axis/i :axis/i2 :axis/y
|
||||
:page :editor/view :editor/add :editor/del))
|
||||
(keys :editor/view (see :axis/x :axis/x2 :axis/z :axis/z2)
|
||||
(@z toggle :lock))
|
||||
(keys :editor/add (@a editor/append :true)
|
||||
|
|
@ -115,35 +118,35 @@
|
|||
(@up select :select/scene/dec)
|
||||
(@down select :select/scene/inc))
|
||||
(keys :scene (see :color :launch :axis/z :axis/z2 :delete))
|
||||
(keys :help (@f1 dialog :help))
|
||||
(keys :page (@pgup page/up)
|
||||
(@pgdn page/down))
|
||||
(keys :delete (@delete delete)
|
||||
(keys :help (@f1 dialog :help))
|
||||
(keys :page (@pgup page/up)
|
||||
(@pgdn page/down))
|
||||
(keys :delete (@delete delete)
|
||||
(@backspace delete/back))
|
||||
(keys :input (see :axis/x :delete) (:char input))
|
||||
(keys :list (see :axis/y :confirm))
|
||||
(keys :length (see :axis/x :axis/y :confirm))
|
||||
(keys :browse (see :list :input :focus))
|
||||
(keys :history (@u undo 1)
|
||||
(@r redo 1))
|
||||
(keys :saveload (@f6 dialog :save)
|
||||
(@f9 dialog :load))
|
||||
(keys :color (@c color))
|
||||
(keys :launch (@q launch))
|
||||
(keys :clock (@space clock/toggle 0)
|
||||
(keys :history (@u undo 1)
|
||||
(@r redo 1))
|
||||
(keys :saveload (@f6 dialog :save)
|
||||
(@f9 dialog :load))
|
||||
(keys :color (@c color))
|
||||
(keys :launch (@q launch))
|
||||
(keys :clock (@space clock/toggle 0)
|
||||
(@shift/space clock/toggle 0))
|
||||
(keys :global (see :history :saveload)
|
||||
(@f8 dialog :options)
|
||||
(@f10 dialog :quit))
|
||||
(@f8 dialog :options)
|
||||
(@f10 dialog :quit))
|
||||
(keys :clip (see :color :launch :axis/z :axis/z2 :delete)
|
||||
(@l toggle :loop))
|
||||
(@l toggle :loop))
|
||||
(keys :sequencer (see :color :launch)
|
||||
(@shift/I input/add)
|
||||
(@shift/O output/add))
|
||||
(@shift/I input/add)
|
||||
(@shift/O output/add))
|
||||
(keys :pool (see :axis-y :axis-w :axis/z2 :color :delete)
|
||||
(@n rename/begin)
|
||||
(@t length/begin)
|
||||
(@m import/begin)
|
||||
(@x export/begin)
|
||||
(@shift/A clip/add :after :new/clip)
|
||||
(@shift/D clip/add :after :cloned/clip))
|
||||
(@n rename/begin)
|
||||
(@t length/begin)
|
||||
(@m import/begin)
|
||||
(@x export/begin)
|
||||
(@shift/A clip/add :after :new/clip)
|
||||
(@shift/D clip/add :after :cloned/clip))
|
||||
|
|
|
|||
964
src/tek.rs
964
src/tek.rs
File diff suppressed because it is too large
Load diff
2
tengri
2
tengri
|
|
@ -1 +1 @@
|
|||
Subproject commit c0d6d0174e8858108ec053f4ac486cf6229980e1
|
||||
Subproject commit 25354099fe3cde43a242d41fdb673c4fce5c943e
|
||||
Loading…
Add table
Add a link
Reference in a new issue