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

View file

@ -6,13 +6,15 @@ pub(crate) use ratatui::buffer::{Buffer, Cell};
use ratatui::widgets::WidgetRef;
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 {
let y = area.y + row;
for col in 0..area.width {
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) => {
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 {
fn render (&$self, $buf: &mut Buffer, $area: Rect) -> Usually<Rect> {
$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.
pub struct Sequencer {
pub mode: bool,

View file

@ -14,7 +14,11 @@
//! * [LV2Plugin::load_edn]
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 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`.
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| {
self.state.read().unwrap().render(buf, area)
});

View file

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

View file

@ -123,7 +123,7 @@ impl App {
#[derive(PartialEq, Clone, Copy)]
pub enum AppFocus {
/// The transport is selected.
Transport,
Transport,
/// The arranger is selected.
Arranger,
/// The sequencer is selected.
@ -156,6 +156,7 @@ impl AppFocus {
}
/// A sequencer track.
#[derive(Debug)]
pub struct Track {
pub name: String,
/// 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) => {
impl $A<$T> {
pub fn start_inc (&mut self) -> $T {