wip: improvements to arranger view

This commit is contained in:
🪞👃🪞 2024-07-18 21:59:45 +03:00
parent 2bb8979058
commit a6a8552996
11 changed files with 762 additions and 577 deletions

2
Cargo.lock generated
View file

@ -1044,7 +1044,7 @@ dependencies = [
[[package]] [[package]]
name = "tek" name = "tek"
version = "0.0.0" version = "0.1.0"
dependencies = [ dependencies = [
"atomic_float", "atomic_float",
"backtrace", "backtrace",

View file

@ -1,12 +1,13 @@
[package] [package]
name = "tek" name = "tek"
edition = "2021" edition = "2021"
version = "0.1.0"
[dependencies] [dependencies]
jack = "0.10" jack = "0.10"
clap = { version = "4.5.4", features = [ "derive" ] } clap = { version = "4.5.4", features = [ "derive" ] }
crossterm = "0.27" crossterm = "0.27"
ratatui = { version = "0.26.3", features = [ "unstable-widget-ref" ] } ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] }
backtrace = "0.3.72" backtrace = "0.3.72"
microxdg = "0.1.2" microxdg = "0.1.2"
toml = "0.8.12" toml = "0.8.12"

View file

@ -55,7 +55,7 @@
(:12 (44 100) (40 100)) (:12 (44 100) (40 100))
(:14 (44 100))) (:14 (44 100)))
(phrase { :name "Trapping" :beats 8 :steps 96 } (phrase { :name "Trap Pinging" :beats 8 :steps 96 }
(:00 (42 100) (36 100) (34 120) (49 100)) (:00 (42 100) (36 100) (34 120) (49 100))
(:01 (42 100)) (:01 (42 100))
(:02 (42 100)) (:02 (42 100))

1
shell.nix Normal file → Executable file
View file

@ -1,3 +1,4 @@
#!/usr/bin/env nix-shell
{pkgs?import<nixpkgs>{}}:pkgs.mkShell{ {pkgs?import<nixpkgs>{}}:pkgs.mkShell{
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
pkg-config pkg-config

View file

@ -6,13 +6,15 @@ pub(crate) use ratatui::buffer::{Buffer, Cell};
use ratatui::widgets::WidgetRef; use ratatui::widgets::WidgetRef;
pub fn buffer_update ( pub fn buffer_update (
buffer: &mut Buffer, area: Rect, callback: &impl Fn(&mut Cell, u16, u16) buf: &mut Buffer, area: Rect, callback: &impl Fn(&mut Cell, u16, u16)
) { ) {
for row in 0..area.height { for row in 0..area.height {
let y = area.y + row; let y = area.y + row;
for col in 0..area.width { for col in 0..area.width {
let x = area.x + col; let x = area.x + col;
callback(buffer.get_mut(x, y), col, row); if x < buf.area.width && y < buf.area.height {
callback(buf.get_mut(x, y), col, row);
}
} }
} }
} }
@ -63,7 +65,7 @@ pub trait Render: Send {
($T:ty) => { ($T:ty) => {
impl Render for $T {} impl Render for $T {}
}; };
($T:ty |$self:ident, $buf:ident, $area:ident|$block:tt) => { ($T:ty |$self:ident, $buf:ident, $area:ident|$block:expr) => {
impl Render for $T { impl Render for $T {
fn render (&$self, $buf: &mut Buffer, $area: Rect) -> Usually<Rect> { fn render (&$self, $buf: &mut Buffer, $area: Rect) -> Usually<Rect> {
$block $block

File diff suppressed because it is too large Load diff

View file

@ -44,6 +44,87 @@ pub const KEYMAP_SEQUENCER: &'static [KeyBinding<App>] = keymap!(App {
*/ */
}); });
pub type PhraseData = Vec<Vec<MidiMessage>>;
#[derive(Debug)]
/// A MIDI sequence.
pub struct Phrase {
pub name: String,
pub length: usize,
pub notes: PhraseData,
pub looped: Option<(usize, usize)>,
/// Immediate note-offs in view
pub percussive: bool
}
impl Default for Phrase {
fn default () -> Self {
Self::new("", 0, None)
}
}
impl Phrase {
pub fn new (name: &str, length: usize, notes: Option<PhraseData>) -> Self {
Self {
name: name.to_string(),
length,
notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]),
looped: Some((0, length)),
percussive: true,
}
}
pub fn record_event (&mut self, pulse: usize, message: MidiMessage) {
if pulse >= self.length {
panic!("extend phrase first")
}
self.notes[pulse].push(message);
}
/// Check if a range `start..end` contains MIDI Note On `k`
pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool {
//panic!("{:?} {start} {end}", &self);
for events in self.notes[start.max(0)..end.min(self.notes.len())].iter() {
for event in events.iter() {
match event {
MidiMessage::NoteOn {key,..} => {
if *key == k {
return true
}
}
_ => {}
}
}
}
return false
}
/// Write a chunk of MIDI events to an output port.
pub fn process_out (
&self,
output: &mut MIDIChunk,
notes_on: &mut [bool;128],
timebase: &Arc<Timebase>,
(frame0, frames, _): (usize, usize, f64),
) {
let mut buf = Vec::with_capacity(8);
for (time, tick) in Ticks(timebase.pulse_per_frame()).between_frames(
frame0, frame0 + frames
) {
let tick = tick % self.length;
for message in self.notes[tick].iter() {
buf.clear();
let channel = 0.into();
let message = *message;
LiveEvent::Midi { channel, message }.write(&mut buf).unwrap();
output[time as usize].push(buf.clone());
match message {
MidiMessage::NoteOn { key, .. } => notes_on[key.as_int() as usize] = true,
MidiMessage::NoteOff { key, .. } => notes_on[key.as_int() as usize] = false,
_ => {}
}
}
}
}
}
/// Phrase editor. /// Phrase editor.
pub struct Sequencer { pub struct Sequencer {
pub mode: bool, pub mode: bool,

View file

@ -14,7 +14,11 @@
//! * [LV2Plugin::load_edn] //! * [LV2Plugin::load_edn]
use crate::{core::*, model::*, App}; use crate::{core::*, model::*, App};
use crate::devices::sampler::{Sampler, Sample, read_sample_data}; use crate::devices::{
arranger::Scene,
sequencer::Phrase,
sampler::{Sampler, Sample, read_sample_data}
};
use crate::devices::plugin::{Plugin, LV2Plugin}; use crate::devices::plugin::{Plugin, LV2Plugin};
use clojure_reader::{edn::{read, Edn}, error::Error as EdnError}; use clojure_reader::{edn::{read, Edn}, error::Error as EdnError};

View file

@ -12,6 +12,11 @@ pub struct JackDevice {
/// The "real" readable/writable `Port`s are owned by the `state`. /// The "real" readable/writable `Port`s are owned by the `state`.
pub ports: UnownedJackPorts, pub ports: UnownedJackPorts,
} }
impl std::fmt::Debug for JackDevice {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("JackDevice").field("ports", &self.ports).finish()
}
}
render!(JackDevice |self, buf, area| { render!(JackDevice |self, buf, area| {
self.state.read().unwrap().render(buf, area) self.state.read().unwrap().render(buf, area)
}); });

View file

@ -12,6 +12,7 @@ pub struct JackPorts {
#[derive(Default)] #[derive(Default)]
/// Collection of JACK ports as [Unowned]. /// Collection of JACK ports as [Unowned].
#[derive(Debug)]
pub struct UnownedJackPorts { pub struct UnownedJackPorts {
pub audio_ins: BTreeMap<String, Port<Unowned>>, pub audio_ins: BTreeMap<String, Port<Unowned>>,
pub midi_ins: BTreeMap<String, Port<Unowned>>, pub midi_ins: BTreeMap<String, Port<Unowned>>,

View file

@ -123,7 +123,7 @@ impl App {
#[derive(PartialEq, Clone, Copy)] #[derive(PartialEq, Clone, Copy)]
pub enum AppFocus { pub enum AppFocus {
/// The transport is selected. /// The transport is selected.
Transport, Transport,
/// The arranger is selected. /// The arranger is selected.
Arranger, Arranger,
/// The sequencer is selected. /// The sequencer is selected.
@ -156,6 +156,7 @@ impl AppFocus {
} }
/// A sequencer track. /// A sequencer track.
#[derive(Debug)]
pub struct Track { pub struct Track {
pub name: String, pub name: String,
/// Play input through output. /// Play input through output.
@ -366,102 +367,6 @@ impl Track {
}} }}
} }
pub type PhraseData = Vec<Vec<MidiMessage>>;
#[derive(Debug)]
/// A MIDI sequence.
pub struct Phrase {
pub name: String,
pub length: usize,
pub notes: PhraseData,
pub looped: Option<(usize, usize)>,
/// Immediate note-offs in view
pub percussive: bool
}
impl Default for Phrase {
fn default () -> Self {
Self::new("", 0, None)
}
}
impl Phrase {
pub fn new (name: &str, length: usize, notes: Option<PhraseData>) -> Self {
Self {
name: name.to_string(),
length,
notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]),
looped: Some((0, length)),
percussive: true,
}
}
pub fn record_event (&mut self, pulse: usize, message: MidiMessage) {
if pulse >= self.length {
panic!("extend phrase first")
}
self.notes[pulse].push(message);
}
/// Check if a range `start..end` contains MIDI Note On `k`
pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool {
//panic!("{:?} {start} {end}", &self);
for events in self.notes[start.max(0)..end.min(self.notes.len())].iter() {
for event in events.iter() {
match event {
MidiMessage::NoteOn {key,..} => {
if *key == k {
return true
}
}
_ => {}
}
}
}
return false
}
/// Write a chunk of MIDI events to an output port.
pub fn process_out (
&self,
output: &mut MIDIChunk,
notes_on: &mut [bool;128],
timebase: &Arc<Timebase>,
(frame0, frames, _): (usize, usize, f64),
) {
let mut buf = Vec::with_capacity(8);
for (time, tick) in Ticks(timebase.pulse_per_frame()).between_frames(
frame0, frame0 + frames
) {
let tick = tick % self.length;
for message in self.notes[tick].iter() {
buf.clear();
let channel = 0.into();
let message = *message;
LiveEvent::Midi { channel, message }.write(&mut buf).unwrap();
output[time as usize].push(buf.clone());
match message {
MidiMessage::NoteOn { key, .. } => notes_on[key.as_int() as usize] = true,
MidiMessage::NoteOff { key, .. } => notes_on[key.as_int() as usize] = false,
_ => {}
}
}
}
}
}
/// A collection of phrases to play on each track.
pub struct Scene {
pub name: String,
pub clips: Vec<Option<usize>>,
}
impl Scene {
pub fn new (name: impl AsRef<str>, clips: impl AsRef<[Option<usize>]>) -> Self {
Self {
name: name.as_ref().into(),
clips: clips.as_ref().iter().map(|x|x.clone()).collect()
}
}
}
macro_rules! impl_axis_common { ($A:ident $T:ty) => { macro_rules! impl_axis_common { ($A:ident $T:ty) => {
impl $A<$T> { impl $A<$T> {
pub fn start_inc (&mut self) -> $T { pub fn start_inc (&mut self) -> $T {