mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
wip: improvements to arranger view
This commit is contained in:
parent
2bb8979058
commit
a6a8552996
11 changed files with 762 additions and 577 deletions
|
|
@ -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
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>>,
|
||||
|
|
|
|||
99
src/model.rs
99
src/model.rs
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue