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>>
|
-> Usually<Vec<AppCommand>>
|
||||||
{
|
{
|
||||||
let mut commands = vec![];
|
let mut commands = vec![];
|
||||||
for id in app.mode.keys.iter() {
|
if let Some(ref mode) = app.mode {
|
||||||
if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref())
|
for id in mode.keys.iter() {
|
||||||
&& let Some(bindings) = event_map.query(input) {
|
if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref())
|
||||||
for binding in bindings {
|
&& let Some(bindings) = event_map.query(input) {
|
||||||
for command in binding.commands.iter() {
|
for binding in bindings {
|
||||||
if let Some(command) = app.namespace(command)? as Option<AppCommand> {
|
for command in binding.commands.iter() {
|
||||||
commands.push(command)
|
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::*;
|
pub fn print_config (config: &Config) {
|
||||||
mod modes; pub use self::modes::*;
|
use ::ansi_term::Color::*;
|
||||||
mod mode; pub use self::mode::*;
|
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::*;
|
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,
|
/// The [Draw] implementation for [App] handles the loaded view,
|
||||||
/// which is defined in terms of [dizzle] DSL.
|
/// which is defined in terms of [dizzle] DSL.
|
||||||
///
|
///
|
||||||
|
|
@ -9,17 +25,24 @@ impl View<Tui> for App {
|
||||||
fn view (&self) -> impl Draw<Tui> {
|
fn view (&self) -> impl Draw<Tui> {
|
||||||
thunk(|to: &mut Tui|{
|
thunk(|to: &mut Tui|{
|
||||||
let xywh = to.area().into();
|
let xywh = to.area().into();
|
||||||
|
|
||||||
if let Some(e) = self.error.read().unwrap().as_ref() {
|
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) {
|
if let Some(ref mode) = self.mode {
|
||||||
*self.error.write().unwrap() = Some(format!(
|
for (index, dsl) in mode.view.iter().enumerate() {
|
||||||
"mode {:?} view #{index}: {e}", &self.mode.name,
|
if let Err(e) = self.interpret(to, dsl) {
|
||||||
).into());
|
let src = &dsl.src().unwrap_or(Some("<source error>")).unwrap_or("<no source>");
|
||||||
break;
|
let message = format!("mode {:?} view #{index}:\n{e}\n{}", &mode.name, &src);
|
||||||
|
*self.error.write().unwrap() = Some(message.into());
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Some(xywh))
|
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)
|
}).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 x = "";
|
||||||
/// let _ = tek::view_transport(true, x.as_ref(), x.as_ref(), x.as_ref());
|
/// 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),
|
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::*;
|
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.
|
/// Group of view and keys definitions.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -41,7 +67,7 @@ impl Mode<Arc<str>> {
|
||||||
_ => self.add_view(tail)?,
|
_ => self.add_view(tail)?,
|
||||||
};
|
};
|
||||||
} else if let Ok(Some(word)) = dsl.word() {
|
} else if let Ok(Some(word)) = dsl.word() {
|
||||||
self.add_view(word);
|
self.add_view(word)?;
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("Mode::add: unexpected: {dsl:?}").into());
|
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)]
|
#[derive(Default, Debug)]
|
||||||
pub struct Arrangement {
|
pub struct Arrangement {
|
||||||
|
/// JACK client handle.
|
||||||
|
pub jack: Jack<'static>,
|
||||||
/// Project name.
|
/// Project name.
|
||||||
pub name: Arc<str>,
|
pub name: Arc<str>,
|
||||||
/// Base color.
|
/// Base color.
|
||||||
pub color: ItemTheme,
|
pub color: ItemTheme,
|
||||||
/// JACK client handle.
|
|
||||||
pub jack: Jack<'static>,
|
|
||||||
/// FIXME a render of the project arrangement, redrawn on update.
|
/// FIXME a render of the project arrangement, redrawn on update.
|
||||||
/// TODO rename to "render_cache" or smth
|
/// TODO rename to "render_cache" or smth
|
||||||
pub arranger: Arc<RwLock<Buffer>>,
|
pub arranger: Arc<RwLock<Buffer>>,
|
||||||
|
|
|
||||||
|
|
@ -55,17 +55,23 @@ impl Dialog {
|
||||||
/// ```
|
/// ```
|
||||||
pub fn welcome () -> Self {
|
pub fn welcome () -> Self {
|
||||||
Self::Menu(1, MenuItems([
|
Self::Menu(1, MenuItems([
|
||||||
|
|
||||||
MenuItem("Resume session".into(), Arc::new(Box::new(|_|Ok(())))),
|
MenuItem("Resume session".into(), Arc::new(Box::new(|_|Ok(())))),
|
||||||
|
|
||||||
MenuItem("New session".into(), Arc::new(Box::new(|app|Ok({
|
MenuItem("New session".into(), Arc::new(Box::new(|app|Ok({
|
||||||
app.dialog = Dialog::None;
|
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("Load session".into(), Arc::new(Box::new(|_|Ok(())))),
|
||||||
|
|
||||||
MenuItem("Exit".into(), Arc::new(Box::new(|_|Ok({
|
MenuItem("Exit".into(), Arc::new(Box::new(|_|Ok({
|
||||||
()
|
()
|
||||||
})))),
|
})))),
|
||||||
|
|
||||||
].into()))
|
].into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FIXME: generalize
|
/// FIXME: generalize
|
||||||
/// ```
|
/// ```
|
||||||
/// let _ = tek::Dialog::welcome().menu_selected();
|
/// 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);
|
/// let mut clip = tek::MidiClip::new("clip", true, 1, None, None);
|
||||||
/// clip.set_length(96);
|
/// clip.set_length(96);
|
||||||
/// clip.toggle_loop();
|
/// 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!(clip.contains_note_on(36.into(), 6, 18));
|
||||||
/// assert_eq!(&clip.notes, &clip.duplicate().notes);
|
/// 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 ~~~~ ║ ~ ╟─╌ ~╟─< ~~ v0.3.0 ~~))
|
||||||
(bsp/s (exact/y 1 (text ~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~)) (text dig?)))))
|
(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)
|
(mode :transport (name Transport) (info JACK transport controller.) (keys :clock :global)
|
||||||
:transport)
|
: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)
|
(view (bg (g 0)
|
||||||
(bsp/s (max/y 2 :transport
|
(bsp/s (max/y 2 :transport
|
||||||
(bsp/s (max/y 3 (bg (g 80) :ports/out))
|
(bsp/s (max/y 3 (bg (g 80) :ports/out))
|
||||||
|
|
@ -29,13 +31,13 @@
|
||||||
(bsp/n (fixed/y 1 :status)
|
(bsp/n (fixed/y 1 :status)
|
||||||
(fill :samples/grid))))
|
(fill :samples/grid))))
|
||||||
|
|
||||||
(view :ports/out (fill/x (bsp/s (fill/x (align/w (text L-AUDIO-OUT)))
|
(view :ports/out
|
||||||
(bsp/e (text MIDI-OUT)
|
(fill/x (bsp/s (fill/x (align/w (text L-AUDIO-OUT)))
|
||||||
(fill/x (align/e (text AUDIO-OUT-R)))))))
|
(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)))
|
(view :ports/in
|
||||||
(bsp/e (text MIDI-IN)
|
(fill/x (bsp/s (fill/x (align/w (text L-AUDIO-IN)))
|
||||||
(fill/x (align/e (text AUDIO-IN-R)))))))
|
(bsp/e (text MIDI-IN) (fill/x (align/e (text AUDIO-IN-R)))))))
|
||||||
|
|
||||||
(mode :groovebox (name Groovebox) (info Sequencer with sampler.)
|
(mode :groovebox (name Groovebox) (info Sequencer with sampler.)
|
||||||
(keys :clock :editor :sampler :global)
|
(keys :clock :editor :sampler :global)
|
||||||
|
|
@ -84,7 +86,8 @@
|
||||||
(keys :axis/w (@openbracket w/dec) (@closebracket w/inc))
|
(keys :axis/w (@openbracket w/dec) (@closebracket w/inc))
|
||||||
(keys :axis/w2 (@openbrace w2/dec) (@closebrace w2/inc))
|
(keys :axis/w2 (@openbrace w2/dec) (@closebrace w2/inc))
|
||||||
(keys :focus)
|
(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)
|
(keys :editor/view (see :axis/x :axis/x2 :axis/z :axis/z2)
|
||||||
(@z toggle :lock))
|
(@z toggle :lock))
|
||||||
(keys :editor/add (@a editor/append :true)
|
(keys :editor/add (@a editor/append :true)
|
||||||
|
|
@ -115,35 +118,35 @@
|
||||||
(@up select :select/scene/dec)
|
(@up select :select/scene/dec)
|
||||||
(@down select :select/scene/inc))
|
(@down select :select/scene/inc))
|
||||||
(keys :scene (see :color :launch :axis/z :axis/z2 :delete))
|
(keys :scene (see :color :launch :axis/z :axis/z2 :delete))
|
||||||
(keys :help (@f1 dialog :help))
|
(keys :help (@f1 dialog :help))
|
||||||
(keys :page (@pgup page/up)
|
(keys :page (@pgup page/up)
|
||||||
(@pgdn page/down))
|
(@pgdn page/down))
|
||||||
(keys :delete (@delete delete)
|
(keys :delete (@delete delete)
|
||||||
(@backspace delete/back))
|
(@backspace delete/back))
|
||||||
(keys :input (see :axis/x :delete) (:char input))
|
(keys :input (see :axis/x :delete) (:char input))
|
||||||
(keys :list (see :axis/y :confirm))
|
(keys :list (see :axis/y :confirm))
|
||||||
(keys :length (see :axis/x :axis/y :confirm))
|
(keys :length (see :axis/x :axis/y :confirm))
|
||||||
(keys :browse (see :list :input :focus))
|
(keys :browse (see :list :input :focus))
|
||||||
(keys :history (@u undo 1)
|
(keys :history (@u undo 1)
|
||||||
(@r redo 1))
|
(@r redo 1))
|
||||||
(keys :saveload (@f6 dialog :save)
|
(keys :saveload (@f6 dialog :save)
|
||||||
(@f9 dialog :load))
|
(@f9 dialog :load))
|
||||||
(keys :color (@c color))
|
(keys :color (@c color))
|
||||||
(keys :launch (@q launch))
|
(keys :launch (@q launch))
|
||||||
(keys :clock (@space clock/toggle 0)
|
(keys :clock (@space clock/toggle 0)
|
||||||
(@shift/space clock/toggle 0))
|
(@shift/space clock/toggle 0))
|
||||||
(keys :global (see :history :saveload)
|
(keys :global (see :history :saveload)
|
||||||
(@f8 dialog :options)
|
(@f8 dialog :options)
|
||||||
(@f10 dialog :quit))
|
(@f10 dialog :quit))
|
||||||
(keys :clip (see :color :launch :axis/z :axis/z2 :delete)
|
(keys :clip (see :color :launch :axis/z :axis/z2 :delete)
|
||||||
(@l toggle :loop))
|
(@l toggle :loop))
|
||||||
(keys :sequencer (see :color :launch)
|
(keys :sequencer (see :color :launch)
|
||||||
(@shift/I input/add)
|
(@shift/I input/add)
|
||||||
(@shift/O output/add))
|
(@shift/O output/add))
|
||||||
(keys :pool (see :axis-y :axis-w :axis/z2 :color :delete)
|
(keys :pool (see :axis-y :axis-w :axis/z2 :color :delete)
|
||||||
(@n rename/begin)
|
(@n rename/begin)
|
||||||
(@t length/begin)
|
(@t length/begin)
|
||||||
(@m import/begin)
|
(@m import/begin)
|
||||||
(@x export/begin)
|
(@x export/begin)
|
||||||
(@shift/A clip/add :after :new/clip)
|
(@shift/A clip/add :after :new/clip)
|
||||||
(@shift/D clip/add :after :cloned/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