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
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -1044,7 +1044,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tek"
|
||||
version = "0.0.0"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"atomic_float",
|
||||
"backtrace",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
[package]
|
||||
name = "tek"
|
||||
edition = "2021"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
jack = "0.10"
|
||||
clap = { version = "4.5.4", features = [ "derive" ] }
|
||||
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"
|
||||
microxdg = "0.1.2"
|
||||
toml = "0.8.12"
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
(:12 (44 100) (40 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))
|
||||
(:01 (42 100))
|
||||
(:02 (42 100))
|
||||
|
|
|
|||
1
shell.nix
Normal file → Executable file
1
shell.nix
Normal file → Executable file
|
|
@ -1,3 +1,4 @@
|
|||
#!/usr/bin/env nix-shell
|
||||
{pkgs?import<nixpkgs>{}}:pkgs.mkShell{
|
||||
nativeBuildInputs = with pkgs; [
|
||||
pkg-config
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
//! Clip launcher and arrangement editor.
|
||||
|
||||
use crate::{core::*, model::*, view::*};
|
||||
use crate::{core::*, model::*};
|
||||
use self::focus::ArrangerFocus;
|
||||
pub use self::scene::Scene;
|
||||
|
||||
/// Key bindings for arranger section.
|
||||
pub const KEYMAP_ARRANGER: &'static [KeyBinding<App>] = keymap!(App {
|
||||
|
|
@ -69,28 +71,9 @@ pub struct Arranger {
|
|||
|
||||
pub focused: bool,
|
||||
pub entered: bool,
|
||||
pub fixed_height: bool,
|
||||
}
|
||||
|
||||
render!(Arranger |self, buf, area| {
|
||||
let mut area = area;
|
||||
area.height = area.height.min(1 + if self.mode {
|
||||
self.tracks.len() as u16 * 2
|
||||
} else {
|
||||
self.scenes.len() as u16 * 2
|
||||
});
|
||||
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
|
||||
area = if self.mode {
|
||||
self.draw_horizontal(buf, area)?
|
||||
} else {
|
||||
let area = self.draw_vertical(buf, area)?;
|
||||
if self.focused && self.entered && self.selected == ArrangerFocus::Mix {
|
||||
Corners(Style::default().green().not_dim()).draw(buf, area)?;
|
||||
};
|
||||
area
|
||||
};
|
||||
Ok(area)
|
||||
});
|
||||
|
||||
impl Arranger {
|
||||
|
||||
pub fn new () -> Self {
|
||||
|
|
@ -101,6 +84,7 @@ impl Arranger {
|
|||
tracks: vec![],
|
||||
entered: true,
|
||||
focused: true,
|
||||
fixed_height: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -119,138 +103,250 @@ impl Arranger {
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_vertical (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
let bg_hi = Nord::bg_hier(self.focused, self.entered);
|
||||
let bg_lo = Nord::bg_hi(self.focused, self.entered);
|
||||
|
||||
return Split::right([
|
||||
|
||||
// Scene column
|
||||
&|buf: &mut Buffer, area: Rect|{
|
||||
let Rect { x, y, width, height } = area;
|
||||
for (scene_index, scene) in self.scenes.iter().enumerate() {
|
||||
if y + 2 * scene_index as u16 >= height {
|
||||
break
|
||||
}
|
||||
let active = self.selected == ArrangerFocus::Scene(scene_index);
|
||||
let style = Some(Nord::style_hi(self.focused, active).bold());
|
||||
let y = 1 + y + 2 * scene_index as u16;
|
||||
let playing = scene.clips.iter().enumerate()
|
||||
.all(|(track_index, phrase_index)|match phrase_index {
|
||||
Some(i) => self.tracks[track_index].sequence == Some(*i),
|
||||
None => true
|
||||
|
||||
render!(Arranger |self, buf, area| if self.mode {
|
||||
self::draw_horizontal::draw(self, buf, area)
|
||||
} else {
|
||||
self::draw_vertical::draw(self, buf, area)
|
||||
});
|
||||
if playing { "" } else { " " }.blit(buf, x+1, y, style)?;
|
||||
scene.name.blit(buf, x + 2, y, style)?;
|
||||
if self.selected.scene() == Some(scene_index) {
|
||||
let selected = self.selected == ArrangerFocus::Scene(scene_index);
|
||||
let area = Rect { x, y, width, height: 2 };
|
||||
if selected {
|
||||
fill_bg(buf, area, bg_hi);
|
||||
if self.focused && self.entered {
|
||||
Corners(Style::default().green().not_dim()).draw(buf, area)?;
|
||||
}
|
||||
} else {
|
||||
fill_bg(buf, area, bg_lo);
|
||||
}
|
||||
}
|
||||
}
|
||||
let width = 2 + self.scenes.iter()
|
||||
.map(|x|&x.name).fold(0, |x,y|x.max(y.len() as u16+1));
|
||||
Ok(Rect { width, ..area })
|
||||
},
|
||||
|
||||
// Track columns
|
||||
&|buf: &mut Buffer, area: Rect|{
|
||||
let Rect { mut x, y, width, height } = area;
|
||||
for (track_index, track) in self.tracks.iter().enumerate() {
|
||||
if x >= width {
|
||||
mod draw_vertical {
|
||||
use crate::{core::*, view::*, model::Track};
|
||||
use super::{Arranger, focus::ArrangerFocus, track::track_clip_name_lengths, scene::{Scene, scene_ppqs, scene_name_max_len}};
|
||||
|
||||
pub fn draw (state: &Arranger, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
|
||||
let track_cols = track_clip_name_lengths(state.tracks.as_slice());
|
||||
let scene_rows = scene_ppqs(state.tracks.as_slice(), state.scenes.as_slice());
|
||||
area.height = 2 + (scene_rows[scene_rows.len() - 1].1 / 96) as u16;
|
||||
let offset = 3 + scene_name_max_len(state.scenes.as_ref()) as u16;
|
||||
let style = Some(Style::default().fg(Color::Rgb(0,0,0)));
|
||||
for (_, x) in track_cols.iter() {
|
||||
let x = offset + area.x + *x as u16 - 1;
|
||||
for y in area.y..area.height+area.y {
|
||||
"▎".blit(buf, x, y, style)?;
|
||||
}
|
||||
}
|
||||
|
||||
fill_bg(buf, area, Nord::bg_lo(state.focused, state.entered));
|
||||
match state.selected {
|
||||
ArrangerFocus::Mix => if state.focused && state.entered && state.selected == ArrangerFocus::Mix {
|
||||
fill_bg(buf, area, Nord::bg_hi(state.focused, state.entered));
|
||||
Corners(Style::default().green().not_dim()).draw(buf, area)?;
|
||||
},
|
||||
ArrangerFocus::Track(t) => {
|
||||
let area = Rect {
|
||||
x: offset + area.x + track_cols[t].1 as u16 - 1,
|
||||
y: area.y,
|
||||
width: track_cols[t].0 as u16,
|
||||
height: area.height
|
||||
};
|
||||
fill_bg(buf, area, Nord::bg_hi(state.focused, state.entered));
|
||||
Corners(Style::default().green().not_dim()).draw(buf, area)?;
|
||||
},
|
||||
ArrangerFocus::Scene(s) => {
|
||||
let area = Rect {
|
||||
x: area.x,
|
||||
y: 2 + area.y + (scene_rows[s].1 / 96) as u16,
|
||||
width: area.width,
|
||||
height: (scene_rows[s].0 / 96) as u16
|
||||
};
|
||||
fill_bg(buf, area, Nord::bg_hi(state.focused, state.entered));
|
||||
Corners(Style::default().green().not_dim()).draw(buf, area)?;
|
||||
},
|
||||
ArrangerFocus::Clip(t, s) => {
|
||||
let track_area = Rect {
|
||||
x: offset + area.x + track_cols[t].1 as u16 - 1,
|
||||
y: area.y,
|
||||
width: track_cols[t].0 as u16,
|
||||
height: area.height
|
||||
};
|
||||
let scene_area = Rect {
|
||||
x: area.x,
|
||||
y: 2 + area.y + (scene_rows[s].1 / 96) as u16,
|
||||
width: area.width,
|
||||
height: (scene_rows[s].0 / 96) as u16
|
||||
};
|
||||
let area = Rect {
|
||||
x: offset + area.x + track_cols[t].1 as u16 - 1,
|
||||
y: 2 + area.y + (scene_rows[s].1 / 96) as u16,
|
||||
width: track_cols[t].0 as u16,
|
||||
height: (scene_rows[s].0 / 96) as u16
|
||||
};
|
||||
let lo = Nord::bg_hi(state.focused, state.entered);
|
||||
let hi = Nord::bg_hier(state.focused, state.entered);
|
||||
fill_bg(buf, track_area, lo);
|
||||
fill_bg(buf, scene_area, lo);
|
||||
fill_bg(buf, area, hi);
|
||||
Corners(Style::default().green().not_dim()).draw(buf, area)?;
|
||||
},
|
||||
}
|
||||
|
||||
Split::down([
|
||||
&|buf: &mut Buffer, area: Rect|state.draw_tracks_header(
|
||||
buf, area, track_cols.as_slice(), offset),
|
||||
&|buf: &mut Buffer, area: Rect|state.draw_scene_rows(
|
||||
buf, area, track_cols.as_slice(), scene_rows.as_slice(), offset),
|
||||
]).render(buf, area)?;
|
||||
for (_, y) in scene_rows.iter() {
|
||||
let y = area.y + (*y / 96) as u16 + 1;
|
||||
for x in area.x..area.width+area.y-2 {
|
||||
let cell = buf.get_mut(x, y);
|
||||
cell.modifier = Modifier::UNDERLINED;
|
||||
cell.underline_color = Color::Rgb(0, 0, 0);
|
||||
}
|
||||
}
|
||||
Ok(area)
|
||||
}
|
||||
|
||||
/// Rendering with vertical time
|
||||
impl Arranger {
|
||||
|
||||
pub fn draw_tracks_header (
|
||||
&self,
|
||||
buf: &mut Buffer,
|
||||
area: Rect,
|
||||
track_cols: &[(usize, usize)],
|
||||
offset: u16,
|
||||
) -> Usually<Rect> {
|
||||
let Rect { y, width, .. } = area;
|
||||
for (track, (_, x)) in self.tracks.iter().zip(track_cols) {
|
||||
let x = *x as u16;
|
||||
if x > width {
|
||||
break
|
||||
}
|
||||
let width = 16u16;
|
||||
track.name.blit(buf, x + 1, y, Some(Style::default().bold()))?;
|
||||
for (scene_index, scene) in self.scenes.iter().enumerate() {
|
||||
if y + 2 * scene_index as u16 >= height {
|
||||
track.name.blit(buf, offset + x, y, Some(Style::default()))?;
|
||||
}
|
||||
Ok(Rect { x: area.x, y, width, height: 2 })
|
||||
}
|
||||
|
||||
pub fn draw_scene_rows (
|
||||
&self,
|
||||
buf: &mut Buffer,
|
||||
area: Rect,
|
||||
track_cols: &[(usize, usize)],
|
||||
scene_rows: &[(usize, usize)],
|
||||
offset: u16,
|
||||
) -> Usually<Rect> {
|
||||
let black = Some(Style::default().fg(Color::Rgb(0, 0, 0)));
|
||||
let Rect { mut y, height, .. } = area;
|
||||
for (_, x) in track_cols.iter() {
|
||||
let x = *x as u16;
|
||||
if x > 0 {
|
||||
for y in area.y-2..y-2 {
|
||||
"▎".blit(buf, x - 1, y, black)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (scene, (pulses, _)) in self.scenes.iter().zip(scene_rows) {
|
||||
if y > height {
|
||||
break
|
||||
}
|
||||
let label = match scene.clips.get(track_index) {
|
||||
Some(Some(clip)) => if let Some(phrase) = track.phrases.get(*clip) {
|
||||
let icon = if track.sequence == Some(*clip) { "" } else { "┊" };
|
||||
format!("{icon} {}", phrase.read().unwrap().name)
|
||||
} else {
|
||||
format!(" ??? ")
|
||||
},
|
||||
_ => format!("┊ ········")
|
||||
};
|
||||
let hi = self.selected == ArrangerFocus::Clip(track_index, scene_index);
|
||||
let style = Some(Nord::style_hi(self.focused, hi));
|
||||
let y = 1 + y + 2 * scene_index as u16;
|
||||
"┊".blit(buf, x, y + 1, style)?;
|
||||
label.blit(buf, x, y, style)?;
|
||||
if self.selected == ArrangerFocus::Clip(track_index, scene_index) {
|
||||
fill_bg(buf, Rect { x: area.x, y, width: area.width, height: 2 }, bg_lo);
|
||||
fill_bg(buf, Rect { x, y: area.y, width, height: area.height }, bg_lo);
|
||||
let area = Rect { x, y, width, height: 2 };
|
||||
fill_bg(buf, area, bg_hi);
|
||||
if self.focused && self.entered {
|
||||
Corners(Style::default().green().not_dim()).draw(buf, area)?;
|
||||
};
|
||||
let h = (pulses / 96) as u16;
|
||||
let area = Rect { x: area.x, y, width: area.width, height: h.min(area.height - y) };
|
||||
self.draw_scene_row(buf, area, scene, track_cols, offset)?;
|
||||
y = y + h
|
||||
}
|
||||
}
|
||||
if self.selected == ArrangerFocus::Track(track_index) {
|
||||
fill_bg(buf, Rect { x, y: area.y, width, height: 1 }, bg_hi);
|
||||
if self.focused && self.entered {
|
||||
Corners(Style::default().green().not_dim())
|
||||
.draw(buf, Rect { x, y: area.y, width, height })?;
|
||||
};
|
||||
}
|
||||
x = x + width as u16;
|
||||
}
|
||||
Ok(Rect { x: area.x, y, width: x - area.x, height })
|
||||
}
|
||||
]).render(buf, area);
|
||||
Ok(area)
|
||||
}
|
||||
|
||||
fn draw_horizontal (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
let style3 = Some(Style::default().yellow().bold().not_dim());
|
||||
let style4 = Some(Style::default().dim());
|
||||
let style5 = Some(Style::default().white().bold().not_dim());
|
||||
Split::right([
|
||||
fn draw_scene_row (
|
||||
&self,
|
||||
buf: &mut Buffer,
|
||||
area: Rect,
|
||||
scene: &Scene,
|
||||
track_cols: &[(usize, usize)],
|
||||
offset: u16
|
||||
) -> Usually<u16> {
|
||||
let Rect { y, width, .. } = area;
|
||||
let tracks = self.tracks.as_ref();
|
||||
let playing = scene.is_playing(self.tracks.as_ref());
|
||||
(if playing { "▶" } else { " " }).blit(buf, area.x, y, None)?;
|
||||
scene.name.blit(buf, area.x + 1, y, None)?;
|
||||
let style = Some(Style::default().white());
|
||||
for (track, (w, x)) in track_cols.iter().enumerate() {
|
||||
let x = *x as u16 + offset;
|
||||
if x > width {
|
||||
break
|
||||
}
|
||||
let tracks: &[Track] = self.tracks.as_ref();
|
||||
if let (Some(track), Some(Some(clip))) = (
|
||||
tracks.get(track), scene.clips.get(track)
|
||||
) {
|
||||
if let Some(phrase) = track.phrases.get(*clip) {
|
||||
let phrase = phrase.read().unwrap();
|
||||
phrase.name.blit(buf, x, y, style)?;
|
||||
if track.sequence == Some(*clip) {
|
||||
fill_bg(buf, Rect {
|
||||
x: x - 1,
|
||||
y,
|
||||
width: *w as u16,
|
||||
height: area.height,
|
||||
}, Color::Rgb(40,80,50));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((scene.pulses(tracks) / 96) as u16)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
mod draw_horizontal {
|
||||
use crate::{core::*, view::*};
|
||||
use super::{Arranger, track::*};
|
||||
|
||||
pub fn draw (state: &Arranger, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
let area = Split::right([
|
||||
// Track name
|
||||
&|buf: &mut Buffer, mut area: Rect|{
|
||||
area.x = area.x + 1;
|
||||
let mut width = 0;
|
||||
&|buf: &mut Buffer, area: Rect|state.draw_track_name_column(buf, area),
|
||||
&|buf: &mut Buffer, area: Rect|state.draw_track_mon_column(buf, area),
|
||||
&|buf: &mut Buffer, area: Rect|state.draw_track_rec_column(buf, area),
|
||||
&|buf: &mut Buffer, area: Rect|state.draw_track_ovr_column(buf, area),
|
||||
&|buf: &mut Buffer, area: Rect|state.draw_track_del_column(buf, area),
|
||||
&|buf: &mut Buffer, area: Rect|state.draw_track_gain_column(buf, area),
|
||||
&|buf: &mut Buffer, area: Rect|state.draw_track_scenes_column(buf, area),
|
||||
]).render(buf, area)?;
|
||||
fill_bg(buf, area, Nord::bg_lo(state.focused, state.entered));
|
||||
Ok(area)
|
||||
}
|
||||
|
||||
/// Rendering with horizontal time
|
||||
impl Arranger {
|
||||
pub fn draw_track_name_column (&self, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
|
||||
area.width = 3 + 5.max(track_name_max_len(self.tracks.as_slice())) as u16;
|
||||
let dim = Some(Style::default().dim());
|
||||
let yellow = Some(Style::default().yellow().bold().not_dim());
|
||||
let white = Some(Style::default().white().bold().not_dim());
|
||||
let offset = 0; // track scroll offset
|
||||
for y in 0..area.height {
|
||||
if y == 0 {
|
||||
//"MIX".blit(buf, area.x + 1, area.y + y, style1)?;
|
||||
} else if y % 2 == 1 {
|
||||
let index = y as usize / 2;
|
||||
"Mixer".blit(buf, area.x + 1, area.y + y, dim)?;
|
||||
} else if y % 2 == 0 {
|
||||
let index = (y as usize - 2) / 2 + offset;
|
||||
if let Some(track) = self.tracks.get(index) {
|
||||
width = width.max(
|
||||
track.name.blit(buf, area.x + 1, area.y + y, style5)?.width
|
||||
);
|
||||
if self.selected == ArrangerFocus::Track(index) {
|
||||
"".blit(buf, area.x, area.y + y, style3)?;
|
||||
}
|
||||
let style = if self.selected.track() == Some(index) {
|
||||
yellow
|
||||
} else {
|
||||
area.height = y;
|
||||
break
|
||||
white
|
||||
};
|
||||
format!(" {index:>02} ").blit(buf, area.x, area.y + y, style)?;
|
||||
track.name.blit(buf, area.x + 4, area.y + y, style)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
area.width = width + 1;
|
||||
Ok(area)
|
||||
},
|
||||
}
|
||||
|
||||
&|buf: &mut Buffer, mut area: Rect|{
|
||||
pub fn draw_track_mon_column (&self, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
|
||||
let style4 = Some(Style::default().dim());
|
||||
area.x = area.x + 1;
|
||||
for y in 0..area.height {
|
||||
if y == 0 {
|
||||
//" MON ".blit(buf, area.x, area.y + y, style2)?;
|
||||
} else if y % 2 == 1 {
|
||||
let index = y as usize / 2;
|
||||
} else if y % 2 == 0 {
|
||||
let index = (y as usize - 2) / 2;
|
||||
if let Some(track) = self.tracks.get(index) {
|
||||
" MON ".blit(buf, area.x, area.y + y, if track.monitoring {
|
||||
Some(Style::default().not_dim().green().bold())
|
||||
|
|
@ -265,15 +361,16 @@ impl Arranger {
|
|||
}
|
||||
area.width = 4;
|
||||
Ok(area)
|
||||
},
|
||||
}
|
||||
|
||||
&|buf: &mut Buffer, mut area: Rect|{
|
||||
pub fn draw_track_rec_column (&self, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
|
||||
let style4 = Some(Style::default().dim());
|
||||
area.x = area.x + 1;
|
||||
for y in 0..area.height {
|
||||
if y == 0 {
|
||||
//" REC ".blit(buf, area.x, area.y + y, style2)?;
|
||||
} else if y % 2 == 1 {
|
||||
let index = y as usize / 2;
|
||||
} else if y % 2 == 0 {
|
||||
let index = (y as usize - 2) / 2;
|
||||
if let Some(track) = self.tracks.get(index) {
|
||||
" REC ".blit(buf, area.x, area.y + y, if track.recording {
|
||||
Some(Style::default().not_dim().red().bold())
|
||||
|
|
@ -288,15 +385,16 @@ impl Arranger {
|
|||
}
|
||||
area.width = 4;
|
||||
Ok(area)
|
||||
},
|
||||
}
|
||||
|
||||
&|buf: &mut Buffer, mut area: Rect|{
|
||||
pub fn draw_track_ovr_column (&self, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
|
||||
let style4 = Some(Style::default().dim());
|
||||
area.x = area.x + 1;
|
||||
for y in 0..area.height {
|
||||
if y == 0 {
|
||||
//" OVR ".blit(buf, area.x, area.y + y, style2)?;
|
||||
} else if y % 2 == 1 {
|
||||
let index = y as usize / 2;
|
||||
} else if y % 2 == 0 {
|
||||
let index = (y as usize - 2) / 2;
|
||||
if let Some(track) = self.tracks.get(index) {
|
||||
" OVR ".blit(buf, area.x, area.y + y, if track.overdub {
|
||||
Some(Style::default().not_dim().yellow().bold())
|
||||
|
|
@ -311,15 +409,16 @@ impl Arranger {
|
|||
}
|
||||
area.width = 4;
|
||||
Ok(area)
|
||||
},
|
||||
}
|
||||
|
||||
&|buf: &mut Buffer, mut area: Rect|{
|
||||
pub fn draw_track_del_column (&self, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
|
||||
let style4 = Some(Style::default().dim());
|
||||
area.x = area.x + 1;
|
||||
for y in 0..area.height {
|
||||
if y == 0 {
|
||||
//" DEL ".blit(buf, area.x, area.y + y, style2)?;
|
||||
} else if y % 2 == 1 {
|
||||
let index = y as usize / 2;
|
||||
} else if y % 2 == 0 {
|
||||
let index = (y as usize - 2) / 2;
|
||||
if let Some(_) = self.tracks.get(index) {
|
||||
" DEL ".blit(buf, area.x, area.y + y, style4)?;
|
||||
} else {
|
||||
|
|
@ -330,15 +429,16 @@ impl Arranger {
|
|||
}
|
||||
area.width = 4;
|
||||
Ok(area)
|
||||
},
|
||||
}
|
||||
|
||||
&|buf: &mut Buffer, mut area: Rect|{
|
||||
pub fn draw_track_gain_column (&self, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
|
||||
let style4 = Some(Style::default().dim());
|
||||
area.x = area.x + 1;
|
||||
for y in 0..area.height {
|
||||
if y == 0 {
|
||||
//" GAIN ".blit(buf, area.x, area.y + y, style2)?;
|
||||
} else if y % 2 == 1 {
|
||||
let index = y as usize / 2;
|
||||
} else if y % 2 == 0 {
|
||||
let index = (y as usize - 2) / 2;
|
||||
if let Some(_) = self.tracks.get(index) {
|
||||
" +0.0 ".blit(buf, area.x, area.y + y, style4)?;
|
||||
} else {
|
||||
|
|
@ -349,31 +449,27 @@ impl Arranger {
|
|||
}
|
||||
area.width = 7;
|
||||
Ok(area)
|
||||
},
|
||||
}
|
||||
|
||||
// Scene columns
|
||||
&|buf: &mut Buffer, area: Rect|{
|
||||
pub fn draw_track_scenes_column (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
let mut x2 = 0;
|
||||
let Rect { x, y, height, .. } = area;
|
||||
for (_scene_index, scene) in self.scenes.iter().enumerate() {
|
||||
let active_scene = false;//self.selected == ArrangerFocus::Scene(i) || cursor.1 > 0 && self.cursor.1 - 1 == i;
|
||||
for (scene_index, scene) in self.scenes.iter().enumerate() {
|
||||
let active_scene = self.selected.scene() == Some(scene_index);
|
||||
let sep = Some(if active_scene {
|
||||
Style::default().yellow().not_dim()
|
||||
} else {
|
||||
Style::default().dim()
|
||||
});
|
||||
"╷".blit(buf, x + x2, y, sep)?;
|
||||
for y in y+1..y+height-1 {
|
||||
"┊".blit(buf, x + x2, y, sep)?;
|
||||
for y in y+1..y+height {
|
||||
"│".blit(buf, x + x2, y, sep)?;
|
||||
}
|
||||
"╵".blit(buf, x + x2, y+height-1, sep)?;
|
||||
|
||||
let mut x3 = scene.name.len() as u16;
|
||||
scene.name.blit(buf, x + x2, y, Some(Style::default().bold().not_dim()))?;
|
||||
scene.name.blit(buf, x + x2, y, sep)?;
|
||||
for (i, clip) in scene.clips.iter().enumerate() {
|
||||
let active_track = false;//self.cursor.0 > 0 && self.cursor.0 - 1 == i;
|
||||
let active_track = self.selected.track() == Some(i);
|
||||
if let Some(clip) = clip {
|
||||
let y2 = y + 1 + i as u16 * 2;
|
||||
let y2 = y + 2 + i as u16 * 2;
|
||||
let label = match self.tracks[i].phrases.get(*clip) {
|
||||
Some(phrase) => &format!("{}", phrase.read().unwrap().name),
|
||||
None => "...."
|
||||
|
|
@ -389,124 +485,15 @@ impl Arranger {
|
|||
x2 = x2 + x3 + 1;
|
||||
}
|
||||
Ok(Rect { x, y, height, width: x2 })
|
||||
},
|
||||
]).render(buf, area)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Track management methods
|
||||
impl Arranger {
|
||||
pub fn track (&self) -> Option<&Track> {
|
||||
self.selected.track().map(|t|self.tracks.get(t)).flatten()
|
||||
}
|
||||
pub fn track_mut (&mut self) -> Option<&mut Track> {
|
||||
self.selected.track().map(|t|self.tracks.get_mut(t)).flatten()
|
||||
}
|
||||
pub fn track_next (&mut self) {
|
||||
self.selected.track_next(self.tracks.len() - 1)
|
||||
}
|
||||
pub fn track_prev (&mut self) {
|
||||
self.selected.track_prev()
|
||||
}
|
||||
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Track> {
|
||||
self.tracks.push(name.map_or_else(
|
||||
|| Track::new(&self.track_default_name()),
|
||||
|name| Track::new(name),
|
||||
)?);
|
||||
let index = self.tracks.len() - 1;
|
||||
Ok(&mut self.tracks[index])
|
||||
}
|
||||
pub fn track_del (&mut self) {
|
||||
unimplemented!("Arranger::track_del");
|
||||
}
|
||||
pub fn track_default_name (&self) -> String {
|
||||
format!("Track {}", self.tracks.len() + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Scene management methods
|
||||
impl Arranger {
|
||||
pub fn scene (&self) -> Option<&Scene> {
|
||||
self.selected.scene().map(|s|self.scenes.get(s)).flatten()
|
||||
}
|
||||
pub fn scene_mut (&mut self) -> Option<&mut Scene> {
|
||||
self.selected.scene().map(|s|self.scenes.get_mut(s)).flatten()
|
||||
}
|
||||
pub fn scene_next (&mut self) {
|
||||
self.selected.scene_next(self.scenes.len() - 1)
|
||||
}
|
||||
pub fn scene_prev (&mut self) {
|
||||
self.selected.scene_prev()
|
||||
}
|
||||
pub fn scene_add (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
|
||||
let clips = vec![None;self.tracks.len()];
|
||||
self.scenes.push(match name {
|
||||
Some(name) => Scene::new(name, clips),
|
||||
None => Scene::new(&self.track_default_name(), clips),
|
||||
});
|
||||
let index = self.scenes.len() - 1;
|
||||
Ok(&mut self.scenes[index])
|
||||
}
|
||||
pub fn scene_del (&mut self) {
|
||||
unimplemented!("Arranger::scene_del");
|
||||
}
|
||||
pub fn scene_default_name (&self) -> String {
|
||||
format!("Scene {}", self.scenes.len() + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Phrase management methods
|
||||
impl Arranger {
|
||||
pub fn phrase (&self) -> Option<&Arc<RwLock<Phrase>>> {
|
||||
let track_id = self.selected.track()?;
|
||||
self.tracks.get(track_id)?.phrases.get((*self.scene()?.clips.get(track_id)?)?)
|
||||
}
|
||||
//pub fn phrase_mut (&mut self) -> Option<&mut Phrase> {
|
||||
//let track_id = self.selected.track()?;
|
||||
//let clip = *self.scene()?.clips.get(track_id)?;
|
||||
//self.tracks.get_mut(track_id)?.phrases.get_mut(clip?)
|
||||
//}
|
||||
pub fn phrase_next (&mut self) {
|
||||
unimplemented!();
|
||||
//if let Some((track_index, track)) = self.track_mut() {
|
||||
//let phrases = track.phrases.len();
|
||||
//if let Some((_, scene)) = self.scene_mut() {
|
||||
//if let Some(phrase_index) = scene.clips[track_index] {
|
||||
//if phrase_index >= phrases - 1 {
|
||||
//scene.clips[track_index] = None;
|
||||
//} else {
|
||||
//scene.clips[track_index] = Some(phrase_index + 1);
|
||||
//}
|
||||
//} else if phrases > 0 {
|
||||
//scene.clips[track_index] = Some(0);
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
}
|
||||
pub fn phrase_prev (&mut self) {
|
||||
unimplemented!();
|
||||
//if let Some((track_index, track)) = self.track_mut() {
|
||||
//let phrases = track.phrases.len();
|
||||
//if let Some((_, scene)) = self.scene_mut() {
|
||||
//if let Some(phrase_index) = scene.clips[track_index] {
|
||||
//scene.clips[track_index] = if phrase_index == 0 {
|
||||
//None
|
||||
//} else {
|
||||
//Some(phrase_index - 1)
|
||||
//};
|
||||
//} else if phrases > 0 {
|
||||
//scene.clips[track_index] = Some(phrases - 1);
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
mod focus {
|
||||
#[derive(PartialEq)]
|
||||
/// Represents the current user selection in the arranger
|
||||
pub enum ArrangerFocus {
|
||||
/// The whole mix is selected
|
||||
/** The whole mix is selected */
|
||||
Mix,
|
||||
/// A track is selected.
|
||||
Track(usize),
|
||||
|
|
@ -598,3 +585,201 @@ impl ArrangerFocus {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod scene {
|
||||
use crate::{core::*, model::Track};
|
||||
use super::Arranger;
|
||||
|
||||
/// 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 {
|
||||
let name = name.as_ref().into();
|
||||
let clips = clips.as_ref().iter().map(|x|x.clone()).collect();
|
||||
Self { name, clips, }
|
||||
}
|
||||
/// Returns the pulse length of the longest phrase in the scene
|
||||
pub fn pulses (&self, tracks: &[Track]) -> usize {
|
||||
self.clips.iter().enumerate()
|
||||
.filter_map(|(i, c)|c.map(|c|tracks[i].phrases.get(c)))
|
||||
.filter_map(|p|p)
|
||||
.fold(0, |a, p|a.max(p.read().unwrap().length))
|
||||
}
|
||||
/// Returns true if all phrases in the scene are currently playing
|
||||
pub fn is_playing (&self, tracks: &[Track]) -> bool {
|
||||
self.clips.iter().enumerate()
|
||||
.all(|(track_index, phrase_index)|match phrase_index {
|
||||
Some(i) => tracks[track_index].sequence == Some(*i),
|
||||
None => true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scene_name_max_len (scenes: &[Scene]) -> usize {
|
||||
scenes.iter()
|
||||
.map(|s|s.name.len())
|
||||
.fold(0, usize::max)
|
||||
}
|
||||
|
||||
pub fn scene_ppqs (tracks: &[Track], scenes: &[Scene]) -> Vec<(usize, usize)> {
|
||||
let mut total = 0;
|
||||
let mut scenes: Vec<(usize, usize)> = scenes.iter().map(|scene|{
|
||||
let pulses = scene.pulses(tracks);
|
||||
total = total + pulses;
|
||||
(pulses, total - pulses)
|
||||
}).collect();
|
||||
scenes.push((0, total));
|
||||
scenes
|
||||
}
|
||||
|
||||
/// Scene management methods
|
||||
impl Arranger {
|
||||
pub fn scene (&self) -> Option<&Scene> {
|
||||
self.selected.scene().map(|s|self.scenes.get(s)).flatten()
|
||||
}
|
||||
pub fn scene_mut (&mut self) -> Option<&mut Scene> {
|
||||
self.selected.scene().map(|s|self.scenes.get_mut(s)).flatten()
|
||||
}
|
||||
pub fn scene_next (&mut self) {
|
||||
self.selected.scene_next(self.scenes.len() - 1)
|
||||
}
|
||||
pub fn scene_prev (&mut self) {
|
||||
self.selected.scene_prev()
|
||||
}
|
||||
pub fn scene_add (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
|
||||
let clips = vec![None;self.tracks.len()];
|
||||
self.scenes.push(match name {
|
||||
Some(name) => Scene::new(name, clips),
|
||||
None => Scene::new(&self.scene_default_name(), clips),
|
||||
});
|
||||
let index = self.scenes.len() - 1;
|
||||
Ok(&mut self.scenes[index])
|
||||
}
|
||||
pub fn scene_del (&mut self) {
|
||||
unimplemented!("Arranger::scene_del");
|
||||
}
|
||||
pub fn scene_default_name (&self) -> String {
|
||||
format!("Scene {}", self.scenes.len() + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod track {
|
||||
use crate::{core::*, model::Track};
|
||||
use super::Arranger;
|
||||
|
||||
/// Track management methods
|
||||
impl Arranger {
|
||||
pub fn track (&self) -> Option<&Track> {
|
||||
self.selected.track().map(|t|self.tracks.get(t)).flatten()
|
||||
}
|
||||
pub fn track_mut (&mut self) -> Option<&mut Track> {
|
||||
self.selected.track().map(|t|self.tracks.get_mut(t)).flatten()
|
||||
}
|
||||
pub fn track_next (&mut self) {
|
||||
self.selected.track_next(self.tracks.len() - 1)
|
||||
}
|
||||
pub fn track_prev (&mut self) {
|
||||
self.selected.track_prev()
|
||||
}
|
||||
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Track> {
|
||||
self.tracks.push(name.map_or_else(
|
||||
|| Track::new(&self.track_default_name()),
|
||||
|name| Track::new(name),
|
||||
)?);
|
||||
let index = self.tracks.len() - 1;
|
||||
Ok(&mut self.tracks[index])
|
||||
}
|
||||
pub fn track_del (&mut self) {
|
||||
unimplemented!("Arranger::track_del");
|
||||
}
|
||||
pub fn track_default_name (&self) -> String {
|
||||
format!("Track {}", self.tracks.len() + 1)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn track_name_max_len (tracks: &[Track]) -> usize {
|
||||
tracks.iter()
|
||||
.map(|s|s.name.len())
|
||||
.fold(0, usize::max)
|
||||
}
|
||||
|
||||
pub fn track_clip_name_lengths (tracks: &[Track]) -> Vec<(usize, usize)> {
|
||||
let mut total = 0;
|
||||
let mut lengths: Vec<(usize, usize)> = tracks.iter().map(|track|{
|
||||
let len = 2 + track.phrases
|
||||
.iter()
|
||||
.fold(track.name.len(), |len, phrase|{
|
||||
len.max(phrase.read().unwrap().name.len())
|
||||
});
|
||||
total = total + len;
|
||||
(len, total - len)
|
||||
}).collect();
|
||||
lengths.push((0, total));
|
||||
lengths
|
||||
}
|
||||
}
|
||||
|
||||
mod phrase {
|
||||
use crate::{core::*, devices::sequencer::Phrase};
|
||||
use super::Arranger;
|
||||
|
||||
/// Phrase management methods
|
||||
impl Arranger {
|
||||
pub fn phrase (&self) -> Option<&Arc<RwLock<Phrase>>> {
|
||||
let track_id = self.selected.track()?;
|
||||
self.tracks.get(track_id)?.phrases.get((*self.scene()?.clips.get(track_id)?)?)
|
||||
}
|
||||
pub fn phrase_next (&mut self) {
|
||||
let track_index = self.selected.track();
|
||||
let scene_index = self.selected.scene();
|
||||
track_index
|
||||
.and_then(|index|self.tracks.get_mut(index).map(|track|(index, track)))
|
||||
.and_then(|(track_index, track)|{
|
||||
let phrases = track.phrases.len();
|
||||
scene_index
|
||||
.and_then(|index|self.scenes.get_mut(index))
|
||||
.and_then(|scene|{
|
||||
if let Some(phrase_index) = scene.clips[track_index] {
|
||||
if phrase_index >= phrases - 1 {
|
||||
scene.clips[track_index] = None;
|
||||
} else {
|
||||
scene.clips[track_index] = Some(phrase_index + 1);
|
||||
}
|
||||
} else if phrases > 0 {
|
||||
scene.clips[track_index] = Some(0);
|
||||
}
|
||||
Some(())
|
||||
})
|
||||
});
|
||||
}
|
||||
pub fn phrase_prev (&mut self) {
|
||||
let track_index = self.selected.track();
|
||||
let scene_index = self.selected.scene();
|
||||
track_index
|
||||
.and_then(|index|self.tracks.get_mut(index).map(|track|(index, track)))
|
||||
.and_then(|(track_index, track)|{
|
||||
let phrases = track.phrases.len();
|
||||
scene_index
|
||||
.and_then(|index|self.scenes.get_mut(index))
|
||||
.and_then(|scene|{
|
||||
if let Some(phrase_index) = scene.clips[track_index] {
|
||||
scene.clips[track_index] = if phrase_index == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(phrase_index - 1)
|
||||
};
|
||||
} else if phrases > 0 {
|
||||
scene.clips[track_index] = Some(phrases - 1);
|
||||
}
|
||||
Some(())
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>>,
|
||||
|
|
|
|||
97
src/model.rs
97
src/model.rs
|
|
@ -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