mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
flatten workspace into 1 crate
This commit is contained in:
parent
7c4e1e2166
commit
d926422c67
147 changed files with 66 additions and 126 deletions
35
.old/Cargo.toml
Normal file
35
.old/Cargo.toml
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
[package]
|
||||
name = "tek"
|
||||
edition = "2021"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
clojure-reader = "0.1.0"
|
||||
microxdg = "0.1.2"
|
||||
|
||||
tek_core = { path = "../tek_core" }
|
||||
#tek_sequencer = { path = "../tek_sequencer" }
|
||||
#tek_mixer = { path = "../tek_mixer" }
|
||||
#jack = "0.10"
|
||||
#crossterm = "0.27"
|
||||
#ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] }
|
||||
#backtrace = "0.3.72"
|
||||
#toml = "0.8.12"
|
||||
#better-panic = "0.3.0"
|
||||
#midly = "0.5"
|
||||
|
||||
#vst = "0.4.0"
|
||||
##vst3 = "0.1.0"
|
||||
#livi = "0.7.4"
|
||||
##atomic_enum = "0.3.0"
|
||||
#wavers = "1.4.3"
|
||||
#music-math = "0.1.1"
|
||||
#fraction = "0.15.3"
|
||||
#rlsf = "0.2.1"
|
||||
#r8brain-rs = "0.3.5"
|
||||
|
||||
#symphonia = { version = "0.5.4", features = [ "all" ] }
|
||||
|
||||
#dasp = { version = "0.11.0", features = [ "all" ] }
|
||||
|
||||
#rubato = "0.15.0"
|
||||
3
.old/README.md
Normal file
3
.old/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# `tek`
|
||||
|
||||
This crate unifies the `tek_*` subcrates into a single application.
|
||||
139
.old/example.edn
Normal file
139
.old/example.edn
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
;(bpm 150)
|
||||
|
||||
;(midi-in "nanoKEY Studio.*capture.*")
|
||||
;(midi-in "nanoKEY Studio.*capture.*")
|
||||
;(audio-out "Built-.+:playback_FL", "Built-.+:playback_FR")
|
||||
|
||||
;(scene { :name "Intro" } 0 0 _ _)
|
||||
;(scene { :name "Hook" } 1 1 0 _)
|
||||
;(scene { :name "Verse" } 2 2 1 _)
|
||||
;(scene { :name "Chorus" } 3 3 2 _)
|
||||
;(scene { :name "Bridge" } _ 4 3 _)
|
||||
;(scene { :name "Outro" } 4 1 4 _)
|
||||
|
||||
;(track { :name "Drums" :gain +0.0 }
|
||||
;(phrase { :name "4 kicks" :beats 4 :steps 16 }
|
||||
;(:00 (36 128))
|
||||
;(:04 (36 100))
|
||||
;(:08 (36 100))
|
||||
;(:12 (36 100)))
|
||||
;(phrase { :name "5 kicks" :beats 4 :steps 16 }
|
||||
;(:00 (36 128))
|
||||
;(:04 (36 100))
|
||||
;(:08 (36 128))
|
||||
;(:12 (36 100))
|
||||
;(:14 (36 110)))
|
||||
;(phrase { :name "D Beat" :beats 8 :steps 32 }
|
||||
;(:00 (44 70) (36 128) (49 110))
|
||||
;(:02 (44 30))
|
||||
;(:04 (44 80) (40 100))
|
||||
;(:06 (44 50))
|
||||
;(:08 (44 30) (36 100))
|
||||
;(:10 (44 50) (36 100))
|
||||
;(:12 (44 80) (40 100))
|
||||
;(:14 (44 50))
|
||||
;(:15 (36 50))
|
||||
;(:16 (44 60) (36 80))
|
||||
;(:18 (44 60) (36 80))
|
||||
;(:20 (44 60) (40 80))
|
||||
;(:22 (44 60))
|
||||
;(:24 (44 60))
|
||||
;(:26 (44 30) (36 80))
|
||||
;(:27 (44 60))
|
||||
;(:28 (44 60) (40 80))
|
||||
;(:30 (44 60)))
|
||||
;(phrase { :name "Garage" :beats 4 :steps 16 }
|
||||
;(:00 (44 100) (36 100) (35 100))
|
||||
;(:01 (44 100))
|
||||
;(:02 (44 100) (35 100))
|
||||
;(:03 (44 100))
|
||||
;(:04 (44 100) (40 100))
|
||||
;(:06 (44 100))
|
||||
;(:07 (44 100) (34 100))
|
||||
;(:09 (44 100))
|
||||
;(:10 (44 100))
|
||||
;(:11 (35 100) (36 100))
|
||||
;(:12 (44 100) (40 100))
|
||||
;(:14 (44 100)))
|
||||
|
||||
;(phrase { :name "Trap Pinging" :beats 8 :steps 96 }
|
||||
;(:00 (42 100) (36 100) (34 120) (49 100))
|
||||
;(:01 (42 100))
|
||||
;(:02 (42 100))
|
||||
;(:06 (42 100) (35 80) (36 80) (49 100))
|
||||
;(:07 (42 100))
|
||||
;(:08 (42 100))
|
||||
;(:12 (42 100))
|
||||
;(:15 (39 100) (34 100))
|
||||
;(:18 (42 100))
|
||||
;(:24 (42 100) (38 50) (40 50))
|
||||
;(:27 (42 100) (36 50))
|
||||
;(:30 (42 100))
|
||||
;(:33 (42 100) (36 50) (34 100))
|
||||
;(:36 (42 90))
|
||||
;(:39 (42 80))
|
||||
;(:42 (42 70))
|
||||
;(:45 (42 60))
|
||||
|
||||
;(:48 (42 100) (36 100) (34 100))
|
||||
;(:50 (42 100))
|
||||
;(:52 (42 110))
|
||||
;(:54 (46 50) (42 120))
|
||||
;(:56 (42 90))
|
||||
;(:58 (42 100))
|
||||
;(:60 (42 100) (35 100))
|
||||
;(:64 (39 100))
|
||||
;(:66 (42 100) (34 100))
|
||||
|
||||
;(:70 (42 100))
|
||||
;(:71 (42 100))
|
||||
;(:72 (42 100) (38 50) (40 50))
|
||||
;(:75 (42 100) (36 50) (34 80))
|
||||
;(:78 (42 100))
|
||||
;(:81 (42 100) (36 50))
|
||||
;(:84 (38 40) (40 50) (34 90))
|
||||
;(:87 (42 90) (35 40))
|
||||
;(:90 (42 70)))
|
||||
|
||||
;(sampler { :name "DrumKit1" :dir "/home/user/Lab/Music/pak" }
|
||||
;(sample { :midi 34 :name "808 D" :file "808.wav" })
|
||||
;(sample { :midi 35 :name "Kick 1" :file "kik.wav" })
|
||||
;(sample { :midi 36 :name "Kick 2" :file "kik2.wav" })
|
||||
;(sample { :midi 37 :name "Rim" :file "rim.wav" })
|
||||
;(sample { :midi 38 :name "Snare 1" :file "sna.wav" })
|
||||
;(sample { :midi 39 :name "Shaker" :file "shk.wav" })
|
||||
;(sample { :midi 40 :name "Snare 2" :file "sna2.wav" })
|
||||
;(sample { :midi 42 :name "Closed HH 1" :file "chh.wav" })
|
||||
;(sample { :midi 44 :name "Closed HH 2" :file "chh2.wav" })
|
||||
;(sample { :midi 45 :name "Open HH 0" :file "ohh.wav" })
|
||||
;(sample { :midi 46 :name "Open HH 1" :file "ohh1.wav" })
|
||||
;(sample { :midi 47 :name "Open HH 2" :file "ohh2.wav" })
|
||||
;(sample { :midi 49 :name "Crash" :file "crs.wav" })))
|
||||
|
||||
;(track { :name "Bass" :gain +0.0 }
|
||||
;(phrase { :name "Bass 1" :beats 4 })
|
||||
;(phrase { :name "Bass 2" :beats 4 })
|
||||
;(phrase { :name "Bass 3" :beats 4 })
|
||||
;(phrase { :name "Bass 4" :beats 4 })
|
||||
;(phrase { :name "Bass 5" :beats 4 })
|
||||
;(phrase { :name "Bass 6" :beats 4 })
|
||||
;(phrase { :name "Bass 7" :beats 4 })
|
||||
;(phrase { :name "Bass 8" :beats 4 })
|
||||
;(lv2 {
|
||||
;:name "Odin2"
|
||||
;:path "file:///home/user/.lv2/Odin2.lv2"
|
||||
;}))
|
||||
|
||||
;(track { :name "Lead" :gain +0.0 }
|
||||
;(phrase { :name "Lead 1" :beats 4 })
|
||||
;(phrase { :name "Lead 2" :beats 4 })
|
||||
;(phrase { :name "Lead 3" :beats 4 })
|
||||
;(phrase { :name "Lead 4" :beats 4 })
|
||||
;(phrase { :name "Lead 5" :beats 4 })
|
||||
;(phrase { :name "Lead 6" :beats 4 })
|
||||
;(phrase { :name "Lead 7" :beats 4 })
|
||||
;(phrase { :name "Lead 8" :beats 4 })
|
||||
;(lv2 {
|
||||
;:name "Odin2"
|
||||
;:path "file:///home/user/.lv2/Odin2.lv2"
|
||||
;}))
|
||||
151
.old/src/app.rs
Normal file
151
.old/src/app.rs
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
use crate::*;
|
||||
use tek_core::Direction;
|
||||
use tek_mixer::Mixer;
|
||||
use tek_sequencer::{Arranger, TransportToolbar};
|
||||
|
||||
/// Root of application state.
|
||||
pub struct App {
|
||||
/// Whether the currently focused section has input priority
|
||||
pub entered: bool,
|
||||
/// Currently focused section
|
||||
pub section: AppFocus,
|
||||
/// Transport model and view.
|
||||
pub transport: TransportToolbar,
|
||||
/// Arranger (contains sequencers)
|
||||
pub arranger: Arranger,
|
||||
/// Mixer (contains tracks)
|
||||
pub mixer: Mixer,
|
||||
/// Main JACK client.
|
||||
pub jack: Option<JackClientt>,
|
||||
/// Map of external MIDI outs in the jack graph
|
||||
/// to internal MIDI ins of this app.
|
||||
pub midi_in: Option<Arc<Port<MidiIn>>>,
|
||||
/// Names of ports to connect to main MIDI IN.
|
||||
pub midi_ins: Vec<String>,
|
||||
/// Display mode of chain section
|
||||
pub chain_mode: bool,
|
||||
/// Main audio outputs.
|
||||
pub audio_outs: Vec<Arc<Port<Unowned>>>,
|
||||
/// Number of frames requested by process callback
|
||||
chunk_size: usize,
|
||||
/// Paths to user directories
|
||||
_xdg: Option<Arc<XdgApp>>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new() -> Usually<Self> {
|
||||
let xdg = Arc::new(XdgApp::new("tek")?);
|
||||
let first_run = AppPaths::new(&xdg)?.should_create();
|
||||
let jack = JackClient::Inactive(Client::new("tek", ClientOptions::NO_START_SERVER)?.0);
|
||||
*MODAL.lock().unwrap() =
|
||||
first_run.then(|| ExitableComponent::boxed(SetupModal(Some(xdg.clone()), false)));
|
||||
Ok(Self {
|
||||
entered: true,
|
||||
section: AppFocus::default(),
|
||||
transport: TransportToolbar::new(Some(jack.transport())),
|
||||
arranger: Arranger::new(""),
|
||||
mixer: Mixer::new("")?,
|
||||
jack: Some(jack),
|
||||
audio_outs: vec![],
|
||||
chain_mode: false,
|
||||
chunk_size: 0,
|
||||
midi_in: None,
|
||||
midi_ins: vec![],
|
||||
_xdg: Some(xdg),
|
||||
})
|
||||
}
|
||||
pub fn client(&self) -> &Client {
|
||||
self.jack.as_ref().unwrap().client()
|
||||
}
|
||||
pub fn audio_out(&self, index: usize) -> Option<Arc<Port<Unowned>>> {
|
||||
self.audio_outs.get(index).map(|x| x.clone())
|
||||
}
|
||||
pub fn with_midi_ins(mut self, names: &[&str]) -> Usually<Self> {
|
||||
self.midi_ins = names.iter().map(|x| x.to_string()).collect();
|
||||
Ok(self)
|
||||
}
|
||||
pub fn with_audio_outs(mut self, names: &[&str]) -> Usually<Self> {
|
||||
let client = self.client();
|
||||
self.audio_outs = names
|
||||
.iter()
|
||||
.map(|name| {
|
||||
client
|
||||
.ports(Some(name), None, PortFlags::empty())
|
||||
.get(0)
|
||||
.map(|name| client.port_by_name(name))
|
||||
})
|
||||
.flatten()
|
||||
.filter_map(|x| x)
|
||||
.map(Arc::new)
|
||||
.collect();
|
||||
Ok(self)
|
||||
}
|
||||
pub fn activate(
|
||||
mut self,
|
||||
init: Option<impl FnOnce(&Arc<RwLock<Self>>) -> Usually<()>>,
|
||||
) -> Usually<Arc<RwLock<Self>>> {
|
||||
let jack = self.jack.take().expect("no jack client");
|
||||
let app = Arc::new(RwLock::new(self));
|
||||
app.write().unwrap().jack = Some(jack.activate(&app.clone(), |state, client, scope| {
|
||||
state.write().unwrap().process(client, scope)
|
||||
})?);
|
||||
if let Some(init) = init {
|
||||
init(&app)?;
|
||||
}
|
||||
Ok(app)
|
||||
}
|
||||
}
|
||||
|
||||
render!(
|
||||
App | self,
|
||||
buf,
|
||||
area | {
|
||||
Stack::down()
|
||||
.add_ref(&self.transport)
|
||||
.add_ref(&self.arranger)
|
||||
.add(If(
|
||||
self.arranger.selected.is_clip(),
|
||||
&Stack::right()
|
||||
.add(tek_mixer::TrackView {
|
||||
direction: Direction::Down,
|
||||
entered: self.entered,
|
||||
focused: self.section == AppFocus::Chain,
|
||||
chain: self.mixer.track(),
|
||||
})
|
||||
.add_ref(&self.arranger.sequencer()),
|
||||
))
|
||||
.render(buf, area)?;
|
||||
if let Some(ref modal) = *MODAL.lock().unwrap() {
|
||||
modal.render(buf, area)?;
|
||||
}
|
||||
Ok(area)
|
||||
}
|
||||
);
|
||||
|
||||
process!(
|
||||
App | self,
|
||||
_client,
|
||||
scope | {
|
||||
let (reset, current_frames, chunk_size, current_usecs, next_usecs, period_usecs) =
|
||||
self.transport.update(&scope);
|
||||
self.chunk_size = chunk_size;
|
||||
for track in self.arranger.tracks.iter_mut() {
|
||||
track.process(
|
||||
self.midi_in.as_ref().map(|p| p.iter(&scope)),
|
||||
&self.transport.timebase,
|
||||
self.transport.playing,
|
||||
self.transport.started,
|
||||
self.transport.quant as usize,
|
||||
reset,
|
||||
&scope,
|
||||
(current_frames as usize, self.chunk_size),
|
||||
(
|
||||
current_usecs as usize,
|
||||
next_usecs.saturating_sub(current_usecs) as usize,
|
||||
),
|
||||
period_usecs as f64,
|
||||
);
|
||||
}
|
||||
Control::Continue
|
||||
}
|
||||
);
|
||||
37
.old/src/app_focus.rs
Normal file
37
.old/src/app_focus.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
use crate::*;
|
||||
|
||||
/// Different sections of the UI that may be focused.
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub enum AppFocus {
|
||||
/// The transport is selected.
|
||||
Transport,
|
||||
/// The arranger is selected.
|
||||
Arranger,
|
||||
/// The sequencer is selected.
|
||||
Sequencer,
|
||||
/// The device chain is selected.
|
||||
Chain,
|
||||
}
|
||||
|
||||
impl Default for AppFocus {
|
||||
fn default () -> Self { Self::Arranger }
|
||||
}
|
||||
|
||||
impl AppFocus {
|
||||
pub fn prev (&mut self) {
|
||||
*self = match self {
|
||||
Self::Transport => Self::Chain,
|
||||
Self::Arranger => Self::Transport,
|
||||
Self::Sequencer => Self::Arranger,
|
||||
Self::Chain => Self::Sequencer,
|
||||
}
|
||||
}
|
||||
pub fn next (&mut self) {
|
||||
*self = match self {
|
||||
Self::Transport => Self::Arranger,
|
||||
Self::Arranger => Self::Sequencer,
|
||||
Self::Sequencer => Self::Chain,
|
||||
Self::Chain => Self::Transport,
|
||||
}
|
||||
}
|
||||
}
|
||||
52
.old/src/app_paths.rs
Normal file
52
.old/src/app_paths.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
//! Global settings.
|
||||
|
||||
use crate::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fs::{File, create_dir_all};
|
||||
|
||||
const CONFIG_FILE_NAME: &str = "tek.toml";
|
||||
const PROJECT_FILE_NAME: &str = "project.toml";
|
||||
|
||||
/// Filesystem locations of things.
|
||||
pub struct AppPaths {
|
||||
config_dir: PathBuf,
|
||||
config_file: PathBuf,
|
||||
data_dir: PathBuf,
|
||||
project_file: PathBuf,
|
||||
}
|
||||
|
||||
impl AppPaths {
|
||||
pub fn new (xdg: &XdgApp) -> Usually<Self> {
|
||||
let config_dir = PathBuf::from(xdg.app_config()?);
|
||||
let config_file = PathBuf::from(config_dir.join(CONFIG_FILE_NAME));
|
||||
let data_dir = PathBuf::from(xdg.app_data()?);
|
||||
let project_file = PathBuf::from(data_dir.join(PROJECT_FILE_NAME));
|
||||
Ok(Self { config_dir, config_file, data_dir, project_file })
|
||||
}
|
||||
pub fn should_create (&self) -> bool {
|
||||
for path in [
|
||||
&self.config_dir,
|
||||
&self.config_file,
|
||||
&self.data_dir,
|
||||
&self.project_file,
|
||||
].iter() {
|
||||
if !Path::new(path).exists() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
pub fn create (&self) -> Usually<()> {
|
||||
for dir in [&self.config_dir, &self.data_dir].iter() {
|
||||
if !Path::new(dir).exists() {
|
||||
create_dir_all(&dir)?;
|
||||
}
|
||||
}
|
||||
for file in [&self.config_file, &self.project_file].iter() {
|
||||
if !Path::new(file).exists() {
|
||||
File::create_new(&file)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
29
.old/src/cli.rs
Normal file
29
.old/src/cli.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
//! Command line option parser.
|
||||
|
||||
use tek_core::clap::{self, Parser, Subcommand};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub command: Option<Command>
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Subcommand)]
|
||||
pub enum Command {
|
||||
/// Launch or control a master transport
|
||||
Transport,
|
||||
/// Launch or control a sequencer
|
||||
Sequencer {
|
||||
#[arg(long="input")]
|
||||
inputs: Vec<Option<String>>,
|
||||
#[arg(long="output")]
|
||||
outputs: Vec<Option<String>>,
|
||||
},
|
||||
/// Launch or control a sampler
|
||||
Sampler,
|
||||
/// Launch or control a mixer
|
||||
Mixer,
|
||||
/// Launch or control a looper
|
||||
Looper,
|
||||
}
|
||||
184
.old/src/control.rs
Normal file
184
.old/src/control.rs
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
//! Handling of input events.
|
||||
|
||||
use crate::*;
|
||||
|
||||
handle!{
|
||||
App |self, e| {
|
||||
if handle_modal(e)? {
|
||||
return Ok(true)
|
||||
}
|
||||
Ok(if self.entered {
|
||||
handle_focused(self, e)?
|
||||
|| handle_keymap(self, e, KEYMAP_GLOBAL)?
|
||||
|| handle_keymap(self, e, crate::control::KEYMAP_FOCUS)?
|
||||
} else {
|
||||
handle_keymap(self, e, KEYMAP_GLOBAL)?
|
||||
|| handle_keymap(self, e, crate::control::KEYMAP_FOCUS)?
|
||||
|| handle_focused(self, e)?
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_modal (e: &AppEvent) -> Usually<bool> {
|
||||
let mut handled = false;
|
||||
let mut close = false;
|
||||
if let Some(ref mut modal) = *MODAL.lock().unwrap() {
|
||||
if modal.handle(e)? {
|
||||
handled = true;
|
||||
if modal.exited() {
|
||||
close = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
if close {
|
||||
*MODAL.lock().unwrap() = None;
|
||||
}
|
||||
Ok(handled)
|
||||
}
|
||||
|
||||
fn handle_focused (state: &mut App, e: &AppEvent) -> Usually<bool> {
|
||||
unimplemented!()
|
||||
//match state.section {
|
||||
//AppFocus::Transport => state.transport.handle(e),
|
||||
//AppFocus::Arranger => state.arranger.sequencer_mut().map(|s|s.handle(e)),
|
||||
//AppFocus::Sequencer => state.arranger.sequencer_mut().map(|s|s.handle(e)),
|
||||
//AppFocus::Chain => Ok(false)[>if state.entered {
|
||||
//handle_device(state, e)? ||
|
||||
//handle_keymap(state, e, crate::control::KEYMAP_CHAIN)?
|
||||
//} else {
|
||||
//handle_keymap(state, e, crate::control::KEYMAP_CHAIN)? || handle_device(state, e)?
|
||||
//})*/
|
||||
//}
|
||||
}
|
||||
|
||||
fn handle_device (state: &mut App, e: &AppEvent) -> Usually<bool> {
|
||||
state.mixer.track()
|
||||
.and_then(|track|track.device_mut())
|
||||
.map(|mut device|device.handle(e))
|
||||
.transpose()
|
||||
.map(|x|x.unwrap_or(false))
|
||||
}
|
||||
|
||||
/// Global key bindings.
|
||||
pub const KEYMAP_GLOBAL: &'static [KeyBinding<App>] = keymap!(App {
|
||||
[Char(' '), NONE, "play_toggle", "play or pause", |app: &mut App| {
|
||||
app.transport.toggle_play()?;
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('r'), NONE, "record_toggle", "toggle recording", |app: &mut App| {
|
||||
app.arranger.track_mut().map(|t|t.toggle_record());
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('o'), NONE, "overdub_toggle", "toggle overdub", |app: &mut App| {
|
||||
app.arranger.track_mut().map(|t|t.toggle_overdub());
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('m'), NONE, "monitor_toggle", "toggle monitor", |app: &mut App| {
|
||||
app.arranger.track_mut().map(|t|t.toggle_monitor());
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('+'), NONE, "quant_inc", "quantize coarser", |app: &mut App| {
|
||||
app.transport.quant = Note::next(app.transport.quant);
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('_'), NONE, "quant_dec", "quantize finer", |app: &mut App| {
|
||||
app.transport.quant = Note::prev(app.transport.quant);
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('='), NONE, "zoom_in", "show fewer ticks per block", |app: &mut App| {
|
||||
app.arranger.sequencer_mut().map(|s|s.time_axis.scale_mut(&Note::prev));
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('-'), NONE, "zoom_out", "show more ticks per block", |app: &mut App| {
|
||||
app.arranger.sequencer_mut().map(|s|s.time_axis.scale_mut(&Note::next));
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('x'), NONE, "extend", "double the current clip", |app: &mut App| {
|
||||
app.arranger.phrase().map(|x|x.write().unwrap()).map(|mut phrase|{
|
||||
let mut notes = phrase.notes.clone();
|
||||
notes.extend_from_slice(&mut phrase.notes);
|
||||
phrase.notes = notes;
|
||||
phrase.length = phrase.length * 2;
|
||||
});
|
||||
//app.arranger.show_phrase()?;
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('l'), NONE, "loop_toggle", "toggle looping", |_app: &mut App| {
|
||||
// TODO: This toggles the loop flag for the clip under the cursor.
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('['), NONE, "loop_start_dec", "move loop start back", |_app: &mut App| {
|
||||
// TODO: This moves the loop start to the previous quant.
|
||||
Ok(true)
|
||||
}],
|
||||
[Char(']'), NONE, "loop_start_inc", "move loop start forward", |_app: &mut App| {
|
||||
// TODO: This moves the loop start to the next quant.
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('{'), NONE, "loop_end_dec", "move loop end back", |_app: &mut App| {
|
||||
// TODO: This moves the loop end to the previous quant.
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('}'), NONE, "loop_end_inc", "move loop end forward", |_app: &mut App| {
|
||||
// TODO: This moves the loop end to the next quant.
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('a'), CONTROL, "scene_add", "add a new scene", |app: &mut App| {
|
||||
app.arranger.scene_add(None)?;
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('t'), CONTROL, "track_add", "add a new track", |app: &mut App| {
|
||||
app.arranger.track_add(None)?;
|
||||
Ok(true)
|
||||
}],
|
||||
});
|
||||
|
||||
/// Generic key bindings for views that support focus.
|
||||
pub const KEYMAP_FOCUS: &'static [KeyBinding<App>] = keymap!(App {
|
||||
[Char(';'), NONE, "command", "open command palette", |_: &mut App| {
|
||||
*MODAL.lock().unwrap() = Some(Box::new(HelpModal::new()));
|
||||
Ok(true)
|
||||
}],
|
||||
[Tab, NONE, "focus_next", "focus next area", focus_next],
|
||||
[Tab, SHIFT, "focus_prev", "focus previous area", focus_prev],
|
||||
[Esc, NONE, "focus_exit", "unfocus", |app: &mut App|{
|
||||
app.entered = false;
|
||||
app.transport.entered = app.entered;
|
||||
//app.arranger.entered = app.entered;
|
||||
app.arranger.sequencer_mut().map(|s|s.entered = app.entered);
|
||||
Ok(true)
|
||||
}],
|
||||
[Enter, NONE, "focus_enter", "activate item at cursor", |app: &mut App|{
|
||||
app.entered = true;
|
||||
app.transport.entered = app.entered;
|
||||
//app.arranger.entered = app.entered;
|
||||
app.arranger.sequencer_mut().map(|s|s.entered = app.entered);
|
||||
Ok(true)
|
||||
}],
|
||||
});
|
||||
|
||||
pub fn focus_next (app: &mut App) -> Usually<bool> {
|
||||
app.section.next();
|
||||
app.transport.focused = app.section == AppFocus::Transport;
|
||||
app.transport.entered = app.entered;
|
||||
//app.arranger.focused = app.section == AppFocus::Arranger;
|
||||
//app.arranger.entered = app.entered;
|
||||
app.arranger.sequencer_mut().map(|s|{
|
||||
s.focused = app.section == AppFocus::Sequencer;
|
||||
s.entered = app.entered;
|
||||
});
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn focus_prev (app: &mut App) -> Usually<bool> {
|
||||
app.section.prev();
|
||||
app.transport.focused = app.section == AppFocus::Transport;
|
||||
app.transport.entered = app.entered;
|
||||
//app.arranger.focused = app.section == AppFocus::Arranger;
|
||||
//app.arranger.entered = app.entered;
|
||||
app.arranger.sequencer_mut().map(|s|{
|
||||
s.focused = app.section == AppFocus::Sequencer;
|
||||
s.entered = app.entered;
|
||||
});
|
||||
Ok(true)
|
||||
}
|
||||
99
.old/src/edn.rs
Normal file
99
.old/src/edn.rs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
//! Project file format.
|
||||
//!
|
||||
//! This module `impl`s the `from_edn`, `load_edn`, etc. methods
|
||||
//! of structs that are defined in other modules. See:
|
||||
//!
|
||||
//! * [App::from_edn]
|
||||
//! * [App::load_edn]
|
||||
//! * [App::load_edn_one]
|
||||
//! * [Scene::load_edn]
|
||||
//! * [Track::load_edn]
|
||||
//! * [Phrase::load_edn]
|
||||
//! * [Sampler::load_edn]
|
||||
//! * [Sample::load_edn]
|
||||
//! * [LV2Plugin::load_edn]
|
||||
|
||||
use crate::*;
|
||||
|
||||
impl App {
|
||||
|
||||
pub fn from_edn (src: &str) -> Usually<Self> {
|
||||
let mut app = Self::new()?;
|
||||
app.load_edn(src)?;
|
||||
Ok(app)
|
||||
}
|
||||
|
||||
pub fn load_edn (&mut self, mut src: &str) -> Usually<&mut Self> {
|
||||
loop {
|
||||
match read(src) {
|
||||
Ok((edn, rest)) => {
|
||||
self.load_edn_one(edn)?;
|
||||
if rest.len() > 0 {
|
||||
src = rest;
|
||||
} else {
|
||||
break
|
||||
}
|
||||
},
|
||||
Err(EdnError { ptr: None, .. }) => {
|
||||
break
|
||||
},
|
||||
Err(e) => {
|
||||
panic!("{e:?}");
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn load_edn_one <'e> (&mut self, edn: Edn<'e>) -> Usually<()> {
|
||||
match edn {
|
||||
Edn::List(items) => {
|
||||
match items.get(0) {
|
||||
Some(Edn::Symbol("bpm")) => {
|
||||
match items.get(1) {
|
||||
Some(Edn::Int(b)) =>
|
||||
self.transport.timebase.set_bpm(*b as f64),
|
||||
Some(Edn::Double(b)) =>
|
||||
self.transport.timebase.set_bpm(f64::from(*b)),
|
||||
_ => panic!("unspecified bpm")
|
||||
}
|
||||
},
|
||||
Some(Edn::Symbol("scene")) => {
|
||||
tek_sequencer::Scene::from_edn(&items[1..])?;
|
||||
},
|
||||
Some(Edn::Symbol("track")) => {
|
||||
tek_mixer::Track::from_edn(&items[1..])?;
|
||||
},
|
||||
Some(Edn::Symbol("midi-in")) => {
|
||||
self.midi_ins = items[1..].iter().map(|x|match x {
|
||||
Edn::Str(n) => n.to_string(),
|
||||
_ => panic!("unexpected midi-in")
|
||||
}).collect::<Vec<_>>();
|
||||
},
|
||||
Some(Edn::Symbol("audio-out")) => {
|
||||
let client = self.client();
|
||||
self.audio_outs = items[1..].iter().map(|x|match x {
|
||||
Edn::Str(n) => n.to_string(),
|
||||
_ => panic!("unexpected midi-in")
|
||||
}).collect::<Vec<_>>()
|
||||
.iter()
|
||||
.map(|name|client
|
||||
.ports(Some(name), None, PortFlags::empty())
|
||||
.get(0)
|
||||
.map(|name|client.port_by_name(name)))
|
||||
.flatten()
|
||||
.filter_map(|x|x)
|
||||
.map(Arc::new)
|
||||
.collect();
|
||||
},
|
||||
_ => panic!("unexpected edn: {:?}", items.get(0))
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
panic!("unexpected edn: {edn:?}");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
96
.old/src/help.rs
Normal file
96
.old/src/help.rs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
//! Help modal / command palette.
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// Command palette.
|
||||
pub struct HelpModal {
|
||||
cursor: usize,
|
||||
search: Option<String>,
|
||||
exited: bool,
|
||||
}
|
||||
impl HelpModal {
|
||||
pub fn new () -> Self {
|
||||
Self { cursor: 0, search: None, exited: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl Exit for HelpModal {
|
||||
fn exited (&self) -> bool {
|
||||
self.exited
|
||||
}
|
||||
fn exit (&mut self) {
|
||||
self.exited = true
|
||||
}
|
||||
}
|
||||
|
||||
render!(HelpModal |self, buf, area|{
|
||||
make_dim(buf);
|
||||
let area = center_box(area, 64, 20);
|
||||
fill_fg(buf, area, Color::Reset);
|
||||
fill_bg(buf, area, Nord::bg_lo(true, true));
|
||||
fill_char(buf, area, ' ');
|
||||
let x = area.x + 2;
|
||||
let y = area.y + 1;
|
||||
"Command:"
|
||||
.blit(buf, x, y, Some(Style::default().bold()))?;
|
||||
" ".repeat(area.width as usize - 13)
|
||||
.blit(buf, x + 9, y, Some(Style::default().bg(Color::Reset)))?;
|
||||
if let Some(search) = self.search.as_ref() {
|
||||
search.blit(buf, x + 9, y, Some(Style::default().not_dim()))?;
|
||||
}
|
||||
let y = y + 1;
|
||||
fill_char(buf, Rect { y, height: 1, ..area }, '-');
|
||||
let y = y + 1;
|
||||
for i in 0..area.height-3 {
|
||||
let y = y + i;
|
||||
if let Some(command) = crate::control::KEYMAP_FOCUS.get(i as usize) {
|
||||
format!("{:?}", command.0).blit(buf, x, y, Some(Style::default().white().bold()))?;
|
||||
command.2.blit(buf, x + 11, y, Some(Style::default().white().bold()))?;
|
||||
command.3.blit(buf, x + 26, y, Some(Style::default().white().dim()))?;
|
||||
} else if let Some(command) = crate::control::KEYMAP_GLOBAL.get((i as usize) - crate::control::KEYMAP_FOCUS.len()) {
|
||||
format!("{:?}", command.0).blit(buf, x, y, Some(Style::default().white().bold()))?;
|
||||
command.2.blit(buf, x + 11, y, Some(Style::default().white().bold()))?;
|
||||
command.3.blit(buf, x + 26, y, Some(Style::default().white().dim()))?;
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
let hi_area = Rect { x: area.x + 1, width: area.width - 2, y: area.y + 3 + self.cursor as u16, height: 1 };
|
||||
fill_bg(buf, hi_area, Nord::bg_hi(true, true));
|
||||
fill_fg(buf, hi_area, Color::White);
|
||||
Lozenge(Style::default()).draw(buf, area)
|
||||
});
|
||||
|
||||
handle!(HelpModal |self, e| {
|
||||
if handle_keymap(self, e, KEYMAP_HELP)? {
|
||||
return Ok(true)
|
||||
}
|
||||
Ok(match e {
|
||||
AppEvent::Input(Event::Key(KeyEvent {
|
||||
code: KeyCode::Char(c),
|
||||
modifiers: KeyModifiers::NONE, ..
|
||||
})) => {
|
||||
if self.search.is_none() {
|
||||
self.search = Some(String::new());
|
||||
}
|
||||
self.search.as_mut().unwrap().push(*c);
|
||||
true
|
||||
},
|
||||
_ => true
|
||||
})
|
||||
});
|
||||
|
||||
pub const KEYMAP_HELP: &'static [KeyBinding<HelpModal>] = keymap!(HelpModal {
|
||||
[Esc, NONE, "help_close", "close help dialog", |modal: &mut HelpModal|{
|
||||
modal.exit();
|
||||
Ok(true)
|
||||
}],
|
||||
[Up, NONE, "help_prev", "select previous command", |modal: &mut HelpModal|{
|
||||
modal.cursor = modal.cursor.saturating_sub(1);
|
||||
Ok(true)
|
||||
}],
|
||||
[Down, NONE, "help_next", "select next command", |modal: &mut HelpModal|{
|
||||
modal.cursor = modal.cursor + 1;
|
||||
Ok(true)
|
||||
}],
|
||||
});
|
||||
65
.old/src/main.rs
Normal file
65
.old/src/main.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
//! ***Tek*** is a MIDI sequencer, sampler, and audio plugin host for the Linux terminal.
|
||||
|
||||
//#[global_allocator]
|
||||
//static A: rlsf::SmallGlobalTlsf = rlsf::SmallGlobalTlsf::new();
|
||||
//#![feature(fn_traits)]
|
||||
//#![feature(unboxed_closures)]
|
||||
#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]
|
||||
#![allow(ambiguous_glob_reexports)]
|
||||
|
||||
pub(crate) use tek_core::{*, jack::*};
|
||||
pub(crate) use tek_sequencer::*;
|
||||
pub(crate) use tek_mixer::*;
|
||||
pub(crate) use microxdg::XdgApp;
|
||||
|
||||
submod! {
|
||||
app
|
||||
app_focus
|
||||
app_paths
|
||||
cli
|
||||
control
|
||||
edn
|
||||
help
|
||||
setup
|
||||
}
|
||||
|
||||
/// Global modal dialog
|
||||
pub static MODAL: Lazy<Arc<Mutex<Option<Box<dyn ExitableComponent>>>>> =
|
||||
Lazy::new(||Arc::new(Mutex::new(None)));
|
||||
|
||||
/// Application entrypoint.
|
||||
pub fn main () -> Usually<()> {
|
||||
run(App::from_edn(include_str!("../example.edn"))?
|
||||
.activate(Some(|app: &Arc<RwLock<App>>|Ok({
|
||||
let (midi_in, mut midi_outs) = {
|
||||
let app = app.read().unwrap();
|
||||
let jack = app.jack.as_ref().unwrap();
|
||||
let midi_in = jack.register_port("midi-in", MidiIn)?;
|
||||
let midi_outs = app.arranger.tracks.iter()
|
||||
.map(|t|Some(jack.register_port(&t.name.read().unwrap(), MidiOut).unwrap()))
|
||||
.collect::<Vec<_>>();
|
||||
(midi_in, midi_outs)
|
||||
};
|
||||
{
|
||||
let mut app = app.write().unwrap();
|
||||
let jack = app.jack.as_ref().unwrap();
|
||||
for name in app.midi_ins.iter() {
|
||||
let ports = jack.client().ports(Some(name), None, PortFlags::empty());
|
||||
for port in ports.iter() {
|
||||
if let Some(port) = jack.client().port_by_name(port) {
|
||||
jack.client().connect_ports(&port, &midi_in)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
app.midi_in = Some(Arc::new(midi_in));
|
||||
for (index, track) in app.arranger.tracks.iter_mut().enumerate() {
|
||||
track.midi_out = midi_outs[index].take();
|
||||
}
|
||||
//for track in app.arranger.tracks.iter() {
|
||||
//track.connect_first_device()?;
|
||||
//track.connect_last_device(&app)?;
|
||||
//}
|
||||
};
|
||||
})))?)?;
|
||||
Ok(())
|
||||
}
|
||||
55
.old/src/setup.rs
Normal file
55
.old/src/setup.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
//! Inital setup dialog (TODO: make this the options dialog too?)
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// Appears on first run (i.e. if state dir is missing).
|
||||
pub struct SetupModal(pub Option<Arc<XdgApp>>, pub bool);
|
||||
|
||||
render!(SetupModal |self, buf, area| {
|
||||
for cell in buf.content.iter_mut() {
|
||||
cell.fg = ratatui::style::Color::Gray;
|
||||
cell.modifier = ratatui::style::Modifier::DIM;
|
||||
}
|
||||
let lines = [
|
||||
(" ", Style::default().white().on_black().not_dim().bold()),
|
||||
(" Welcome to TEK! ", Style::default().white().on_black().not_dim().bold()),
|
||||
(" ", Style::default().white().on_black().not_dim().bold()),
|
||||
(" Press ENTER to create the ", Style::default().white().on_black().not_dim()),
|
||||
(" following directories: ", Style::default().white().on_black().not_dim()),
|
||||
(" ", Style::default().white().on_black().not_dim().bold()),
|
||||
(" Configuration directory: ", Style::default().white().on_black().not_dim()),
|
||||
(" ~/.config/tek ", Style::default().white().on_black().not_dim().bold()),
|
||||
(" ", Style::default().white().on_black().not_dim()),
|
||||
(" Data directory: ", Style::default().white().on_black().not_dim()),
|
||||
(" ~/.local/share/tek ", Style::default().white().on_black().not_dim().bold()),
|
||||
(" ", Style::default().white().on_black().not_dim().bold()),
|
||||
(" Or press CTRL-C to exit. ", Style::default().white().on_black().not_dim()),
|
||||
(" ", Style::default().white().on_black().not_dim()),
|
||||
];
|
||||
let width = lines[0].0.len() as u16;
|
||||
let x = area.x + (area.width - width) / 2;
|
||||
for (i, (line, style)) in lines.iter().enumerate() {
|
||||
line.blit(buf, x, area.y + area.height / 2 - (lines.len() / 2) as u16 + i as u16, Some(*style))?;
|
||||
}
|
||||
Ok(area)
|
||||
});
|
||||
handle!(SetupModal |self, e| {
|
||||
if let AppEvent::Input(Event::Key(KeyEvent {
|
||||
code: KeyCode::Enter,
|
||||
..
|
||||
})) = e {
|
||||
AppPaths::new(&self.0.as_ref().unwrap())?.create()?;
|
||||
self.exit();
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
});
|
||||
impl Exit for SetupModal {
|
||||
fn exited (&self) -> bool {
|
||||
self.1
|
||||
}
|
||||
fn exit (&mut self) {
|
||||
self.1 = true
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue