big worky on sequencer and launcher

This commit is contained in:
🪞👃🪞 2024-06-28 23:19:25 +03:00
parent a4c3593840
commit 8a8d7b8704
14 changed files with 852 additions and 680 deletions

View file

@ -16,7 +16,7 @@ pub use self::looper::Looper;
pub use self::mixer::Mixer;
pub use self::plugin::Plugin;
pub use self::sampler::Sampler;
pub use self::sequencer::Sequencer;
pub use self::sequencer::{Sequencer, Phrase};
pub use self::track::Track;
pub use self::transport::Transport;

View file

@ -16,12 +16,12 @@ pub enum ChainView {
}
impl Chain {
pub fn new (name: &str, items: Vec<Box<dyn Device>>) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
pub fn new (name: &str, items: Option<Vec<Box<dyn Device>>>) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
Ok(DynamicDevice::new(render, handle, process, Self {
name: name.into(),
focused: false,
focus: 0,
items,
items: items.unwrap_or_else(||vec![]),
view: ChainView::Column
}))
}
@ -116,16 +116,35 @@ pub fn draw_as_row (
state: &Chain, buf: &mut Buffer, area: Rect, selected: Option<Style>
) -> Usually<(Rect, Vec<Rect>)> {
let Rect { mut x, mut y, width, height } = area;
let mut w = 0u16;
x = x + 1;
let mut h = 0u16;
let mut frames = vec![];
for (i, device) in state.items.iter().enumerate() {
let midi_ins = device.midi_ins()?;
let midi_outs = device.midi_outs()?;
let audio_ins = device.audio_ins()?;
let audio_outs = device.audio_outs()?;
let width = width.saturating_sub(x);
let frame = device.render(buf, Rect { x, y, width, height })?;
let mut x2 = 1u16;
let mut y2 = 1u16;
for port in device.midi_ins()?.iter() {
port.blit(buf, x, y + y2, Some(Style::default()));
x2 = x2.max(port.len() as u16);
y2 = y2 + 1;
}
for port in device.audio_ins()?.iter() {
port.blit(buf, x, y + y2, Some(Style::default()));
x2 = x2.max(port.len() as u16);
y2 = y2 + 1;
}
let width = width.saturating_sub(x).saturating_sub(x2);
let frame = device.render(buf, Rect { x: x + x2, y, width, height })?;
let mut y2 = 1u16;
for port in device.midi_outs()?.iter() {
port.blit(buf, x + x2 + frame.width, y + y2, Some(Style::default()));
x2 = x2.max(port.len() as u16);
y2 = y2 + 1;
}
for port in device.audio_outs()?.iter() {
port.blit(buf, x + x2 + frame.width, y + y2, Some(Style::default()));
x2 = x2.max(port.len() as u16);
y2 = y2 + 1;
}
frames.push(frame);
h = h.max(frame.height);
x = x + frame.width;

View file

@ -1,4 +1,8 @@
use crate::prelude::*;
mod grid;
pub use self::grid::*;
mod handle;
pub use self::handle::*;
pub struct Launcher {
name: String,
timebase: Arc<Timebase>,
@ -20,6 +24,11 @@ pub enum LauncherView {
Chains,
Modal(Box<dyn Device>),
}
impl LauncherView {
fn is_tracks (&self) -> bool {
match self { Self::Tracks => true, _ => false }
}
}
pub struct Scene {
name: String,
clips: Vec<Option<usize>>,
@ -92,6 +101,7 @@ impl Launcher {
tempo: AtomicUsize::new(113000),
ppq: AtomicUsize::new(96),
});
let ppq = timebase.ppq() as u32;
DynamicDevice::new(render, handle, process, Self {
name: name.into(),
view: LauncherView::Tracks,
@ -100,38 +110,54 @@ impl Launcher {
recording: false,
overdub: true,
transport,
cursor: (1, 2),
cursor: (1, 1),
position: 0,
scenes: vec![
Scene::new(&"Scene#01", &[Some(0), None, None, None]),
Scene::new(&"Scene#02", &[None, None, None, None]),
Scene::new(&"Scene#03", &[None, None, None, None]),
Scene::new(&"Scene#02", &[Some(0), Some(0), None, None]),
Scene::new(&"Scene#03", &[None, Some(0), None, None]),
Scene::new(&"Scene#04", &[None, None, None, None]),
Scene::new(&"Scene#05", &[None, None, None, None]),
Scene::new(&"Scene#06", &[None, None, None, None]),
Scene::new(&"Scene#07", &[None, None, None, None]),
Scene::new(&"Scene#08", &[None, None, None, None]),
],
tracks: vec![
Track::new("Samples", &timebase, vec![
Sampler::new("Samples")?.boxed(),
])?,
Track::new("Kick", &timebase, vec![
Track::new("Kick", &timebase, Some(vec![
//Sampler::new("Sampler")?.boxed(),
Plugin::lv2("Kick/ChowKick", "file:///home/user/.lv2/ChowKick.lv2", &[1, 1, 0, 2])?.boxed(),
]), Some(vec![
Phrase::new("HelloKick", ppq * 4, Some(BTreeMap::from([
( ppq * 0, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
( ppq * 1, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
( ppq * 2, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
( ppq * 3, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
])))
]))?,
Track::new("Helm", &timebase, Some(vec![
Plugin::lv2("Helm", "file:///home/user/.lv2/Helm.lv2", &[1, 0, 0, 2])?.boxed(),
]), Some(vec![
Phrase::new("HelloBass", ppq * 4, Some(BTreeMap::from([
( ppq / 2 + ppq * 0, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
( ppq / 2 + ppq * 1 / 2, vec![MidiMessage::NoteOff { key: 36.into(), vel: 100.into() }] ),
( ppq / 2 + ppq * 1, vec![MidiMessage::NoteOn { key: 39.into(), vel: 100.into() }] ),
( ppq / 2 + ppq * 3 / 2, vec![MidiMessage::NoteOff { key: 39.into(), vel: 100.into() }] ),
( ppq / 2 + ppq * 2, vec![MidiMessage::NoteOn { key: 41.into(), vel: 100.into() }] ),
( ppq / 2 + ppq * 5 / 2, vec![MidiMessage::NoteOff { key: 41.into(), vel: 100.into() }] ),
( ppq / 2 + ppq * 3, vec![MidiMessage::NoteOn { key: 42.into(), vel: 100.into() }] ),
( ppq / 2 + ppq * 7 / 2 - 1, vec![MidiMessage::NoteOff { key: 42.into(), vel: 100.into() }] ),
])))
]))?,
//Plugin::lv2("Kick/ChowKick", "file:///home/user/.lv2/ChowKick.lv2", &[1, 1, 0, 2])?.boxed(),
])?,
//Track::new("Bass", &timebase, vec![
//Plugin::lv2("Bass/Helm", "file:///home/user/.lv2/Helm.lv2", &[1, 0, 0, 2])?.boxed(),
//])?,
//Track::new("Pads", &timebase, vec![
//Plugin::lv2("Pads/Odin2", "file:///home/user/.lv2/Odin2.lv2", &[1, 0, 0, 2])?.boxed(),
//])?,
],
timebase,
show_help: true
}).activate(client)
}
fn cols (&self) -> usize {
(self.tracks.len() + 1) as usize
(self.tracks.len() + 2) as usize
}
fn col (&self) -> usize {
self.cursor.0 as usize
@ -200,66 +226,15 @@ pub fn render (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect>
crate::device::sequencer::draw_dub(buf, x + 26, y, state.overdub);
draw_bpm(buf, x + 33, y, state.timebase.tempo());
draw_timer(buf, x + width - 1, y, &state.timebase, state.position);
let track_area = Rect { x: x, y: y+1, width, height: 22 };
let seq_area = Rect { x: x, y: y+22, width, height: 20 };
let chain_area = Rect { x: x, y: y+41, width, height: 21 };
let separator = format!("{}", "-".repeat((width - 2).into()));
let scenes = draw_scenes(state, buf, x, y + 1);
separator.blit(buf, x, y + 3, Some(Style::default().dim()));
separator.blit(buf, x, y + 5, Some(Style::default().dim()));
separator.blit(buf, x, y + 22, Some(Style::default().dim()));
separator.blit(buf, x, y + 41, Some(Style::default().dim()));
let (w, mut track_highlight) = draw_tracks(state, buf, track_area.x, track_area.y);
if state.col() == 0 {
track_highlight = Some(scenes);
}
draw_crossings(state, buf, x + w - 2, y + 1);
draw_box(buf, Rect { x, y: y + 1, width, height: height - 1 });
let style = Some(Style::default().green().dim());
let chain = state.active_chain();
let plugins = if let Some(chain) = &chain {
let (_, plugins) = crate::device::chain::draw_as_row(
&*chain, buf, chain_area, style
)?;
plugins
} else {
vec![]
};
match state.view {
LauncherView::Tracks => {
draw_box_styled(buf, track_area, style);
},
_ => {},
};
draw_highlight(state, buf, &track_highlight, match state.view {
LauncherView::Tracks => Style::default().green().not_dim(),
_ => Style::default().green().dim()
});
match state.view {
LauncherView::Chains => {
draw_box_styled(buf, Rect { height: 18, ..chain_area }, style);
},
_ => {},
};
if let Some(chain) = &chain {
if let Some(plugin) = plugins.get(chain.focus) {
draw_highlight(state, buf, &Some(*plugin), match state.view {
LauncherView::Chains => Style::default().green().not_dim(),
_ => Style::default().green().dim()
});
}
}
match state.view {
LauncherView::Sequencer => {
draw_box_styled(buf, seq_area, style);
},
_ => {},
};
draw_sequencer(state, buf, seq_area.x, seq_area.y + 1, seq_area.width, seq_area.height - 2)?;
//let separator = format!("├{}┤", "-".repeat((width - 2).into()));
//separator.blit(buf, x, y + 22, Some(Style::default().dim()));
//separator.blit(buf, x, y + 41, Some(Style::default().dim()));
let mut y = y + 1;
y = y + LauncherGridView
::new(state, buf, Rect { x, y, width, height: 22 }, state.view.is_tracks())
.draw()?.height + 1;
y = y + draw_section_sequencer(state, buf, Rect { x, y, width, height: 28 })?.height + 1;
y = y + draw_section_chains(state, buf, Rect { x, y, width, height: 21 })?.height;
if state.show_help {
let style = Some(Style::default().bold().white().not_dim().on_black().italic());
let hide = "[Left Right] Track [Up Down] Scene [, .] Value [F1] Toggle help ";
@ -273,6 +248,14 @@ fn draw_bpm (buf: &mut Buffer, x: u16, y: u16, tempo: usize) {
.blit(buf, x, y, Some(style));
format!("{:03}.{:03}", tempo / 1000, tempo % 1000)
.blit(buf, x + 4, y, Some(style.bold()));
"SYNC"
.blit(buf, x + 13, y, Some(style));
"4/4"
.blit(buf, x + 18, y, Some(style.bold()));
"QUANT"
.blit(buf, x + 23, y, Some(style));
"1/16"
.blit(buf, x + 29, y, Some(style.bold()));
}
fn draw_timer (buf: &mut Buffer, x: u16, y: u16, timebase: &Arc<Timebase>, frame: usize) {
let tick = (frame as f64 / timebase.frames_per_tick()) as usize;
@ -281,298 +264,59 @@ fn draw_timer (buf: &mut Buffer, x: u16, y: u16, timebase: &Arc<Timebase>, frame
let timer = format!("{}.{}.{ticks:02}", bars + 1, beats + 1);
timer.blit(buf, x - timer.len() as u16, y, Some(Style::default().not_dim()));
}
fn draw_scenes (
state: &Launcher, buf: &mut Buffer, x: u16, y: u16,
) -> Rect {
let style = Style::default().not_dim().bold();
let row = state.row() as u16;
let col = state.col() as u16;
let green = |r: u16, c: u16| if row == r && col == c {
Style::default().green()
} else {
Style::default()
fn draw_section_sequencer (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Rect { x, y, width, height } = area;
let style = Some(Style::default().green().dim());
let view = &state.view;
match state.view {
LauncherView::Sequencer => {
draw_box_styled(buf, area, style);
},
_ => {},
};
let mut width = 8u16;
let mut height = 6u16;
format!("{} | ", state.name).blit(buf, x+2, y+1, Some(green(0, 0)));
format!("Sync 1/1").blit(buf, x+2, y+3, Some(green(1, 0)));
for (scene_index, scene) in state.scenes.iter().enumerate() {
let y = y + 5 + scene_index as u16 * 2;
let label = format!("{}", &scene.name);
width = width.max(label.len() as u16 + 2);
label.blit(buf, x + 2, y, Some(green(scene_index as u16 + 2, 0)));
height = height + 2;
}
Rect { x, y, width, height }
}
fn draw_tracks (
state: &Launcher, buf: &mut Buffer, x: u16, y: u16
) -> (u16, Option<Rect>) {
let mut w = 15;
let mut highlight = None;
for (i, track) in state.tracks.iter().enumerate() {
let track = track.sequencer.state();
draw_crossings(state, buf, x + w - 2, y);
let width = draw_track(state, buf, x + w, y, i as u16, &track);
if i + 1 == state.col() {
highlight = Some(Rect { x: x + w - 2, y, width: width + 1, height: 22 });
}
w = w + width;
}
(w, highlight)
}
fn draw_track (
state: &Launcher, buf: &mut Buffer, x: u16, y: u16, i: u16, track: &Sequencer
) -> u16 {
let mut width = 11.max(track.name.len() as u16 + 3);
let row = state.row() as u16;
let col = state.col() as u16;
track.name.blit(buf, x, y + 1, Some(
if row == 0 && col == i + 1 { Style::default().green() } else { Style::default() }
));
"(global)".blit(buf, x, y + 3, Some(
if row == 1 && col == i + 1 { Style::default().green() } else { Style::default().dim() }
));
"".blit(buf, x - 2, y + 3, Some(
Style::default().dim()
));
let green = |r: u16, c: u16| if row == r && col == c {
Style::default().green()
} else {
Style::default()
};
for (_, scene) in state.scenes.iter().enumerate() {
if let Some(Some(sequence_index)) = scene.clips.get(i as usize) {
if let Some(sequence) = track.sequences.get(*sequence_index) {
width = width.max(sequence.name.len() as u16 + 5);
}
}
}
for (scene_index, scene) in state.scenes.iter().enumerate() {
let y = y + 5 + scene_index as u16 * 2;
let style = if scene_index + 2 == row as usize && i + 1 == col {
Style::default().green().bold()
} else {
Style::default().dim()
};
if let Some(Some(sequence_index)) = scene.clips.get(i as usize) {
if let Some(sequence) = track.sequences.get(*sequence_index) {
let label = format!("{}", &sequence.name);
label.blit(buf, x, y, Some(style));
} else {
format!("{}", &"?".repeat(track.name.len()))
.blit(buf, x - 2, y as u16, Some(Style::default().dim()));
}
} else {
"····".blit(buf, x, y, Some(style.dim()));
}
let style = Some(style.dim());
"".blit(buf, x - 2, y + 0, style);
"".blit(buf, x - 2, y + 1, style);
"".blit(buf, x + width - 2, y + 0, style);
"".blit(buf, x + width - 2, y + 1, style);
}
width
}
fn draw_crossings (state: &Launcher, buf: &mut Buffer, x: u16, y: u16) {
"".blit(buf, x, y + 0, None);
"".blit(buf, x, y + 1, Some(Style::default().dim()));
"".blit(buf, x, y + 2, Some(Style::default().dim()));
"".blit(buf, x, y + 3, Some(Style::default().dim()));
"".blit(buf, x, y + 4, Some(Style::default().dim()));
}
fn draw_sequencer (
state: &Launcher, buf: &mut Buffer, x: u16, y: u16, width: u16, height: u16
) -> Usually<()> {
if let Some(track) = state.tracks.get(state.col().saturating_sub(1)) {
//crate::device::sequencer::horizontal::footer(
//&sequencer.state(), buf, 0, y, width, 0
//);
crate::device::sequencer::horizontal::keys(
&track.sequencer.state(), buf, Rect { x, y: y + 1, width, height }
)?;
crate::device::sequencer::horizontal::lanes(
&track.sequencer.state(), buf, x, y + 1, width,
);
let state = track.sequencer.state();
crate::device::sequencer::horizontal::keys(&state, buf, Rect { x, y: y + 1, width, height })?;
crate::device::sequencer::horizontal::lanes(&state, buf, x, y + 1, width);
crate::device::sequencer::horizontal::cursor(
&track.sequencer.state(), buf, x, y + 1, match state.view {
&state, buf, x, y + 1, match view {
LauncherView::Sequencer => Style::default().green().not_dim(),
_ => Style::default().green().dim(),
}
);
}
Ok(())
Ok(area)
}
fn draw_highlight (
state: &Launcher, buf: &mut Buffer, highlight: &Option<Rect>, style: Style
) {
fn draw_highlight (buf: &mut Buffer, highlight: &Option<Rect>, style: Style) {
if let Some(area) = highlight {
draw_box_styled(buf, *area, Some(style));
}
}
pub fn handle (state: &mut Launcher, event: &AppEvent) -> Usually<bool> {
Ok(handle_keymap(state, event, KEYMAP)? || match state.view {
LauncherView::Modal(ref mut device) => {
device.handle(event)?
},
LauncherView::Tracks => {
handle_keymap(state, event, KEYMAP_TRACKS)?
},
LauncherView::Sequencer => {
let i = state.col().saturating_sub(1);
if let Some(track) = state.tracks.get_mut(i) {
crate::device::sequencer::handle(&mut *track.sequencer.state(), event)?
fn draw_section_chains (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Rect { x, y, width, height } = area;
let style = Some(Style::default().green().dim());
let chain = state.active_chain();
let plugins = if let Some(chain) = &chain {
let (_, plugins) = crate::device::chain::draw_as_row(
&*chain, buf, area, style
)?;
plugins
} else {
true
}
},
vec![]
};
match state.view {
LauncherView::Chains => {
true
}
})
}
pub const KEYMAP: &'static [KeyBinding<Launcher>] = keymap!(Launcher {
[F(1), NONE, "toggle_help", "toggle help", toggle_help],
[Tab, SHIFT, "focus_prev", "focus previous area", focus_prev],
[Tab, NONE, "focus_next", "focus next area", focus_next],
[Char(' '), NONE, "play_toggle", "play or pause", play_toggle],
[Char('r'), NONE, "record_toggle", "toggle recording", record_toggle],
[Char('d'), NONE, "overdub_toggle", "toggle overdub", overdub_toggle],
[Char('m'), NONE, "monitor_toggle", "toggle input monitoring", monitor_toggle],
[Char('r'), CONTROL, "rename", "rename current element", rename],
[Char('t'), CONTROL, "add_track", "add a new track", add_track],
//[Char(' '), SHIFT, "play_start", "play from start", play_start],
});
pub const KEYMAP_TRACKS: &'static [KeyBinding<Launcher>] = keymap!(Launcher {
[Up, NONE, "cursor_up", "move cursor up", cursor_up],
[Down, NONE, "cursor_down", "move cursor down", cursor_down],
[Left, NONE, "cursor_left", "move cursor left", cursor_left],
[Right, NONE, "cursor_right", "move cursor right", cursor_right],
[Char('.'), NONE, "clip_next", "set clip to next phrase", clip_next],
[Char(','), NONE, "clip_prev", "set clip to last phrase", clip_prev],
[Delete, CONTROL, "delete_track", "delete track", delete_track],
});
fn rename (state: &mut Launcher) -> Usually<bool> {
Ok(true)
}
fn add_track (state: &mut Launcher) -> Usually<bool> {
state.tracks.push(Track::new("", &state.timebase, vec![])?);
state.cursor.0 = state.tracks.len();
Ok(true)
}
fn delete_track (state: &mut Launcher) -> Usually<bool> {
if state.cursor.0 >= 1 {
state.tracks.remove(state.cursor.0 - 1);
state.cursor.0 = state.cursor.0.min(state.tracks.len());
}
Ok(true)
}
fn cursor_up (state: &mut Launcher) -> Usually<bool> {
state.dec_row();
Ok(true)
}
fn cursor_down (state: &mut Launcher) -> Usually<bool> {
state.inc_row();
Ok(true)
}
fn cursor_left (state: &mut Launcher) -> Usually<bool> {
state.dec_col();
Ok(true)
}
fn cursor_right (state: &mut Launcher) -> Usually<bool> {
state.inc_col();
Ok(true)
}
fn toggle_help (state: &mut Launcher) -> Usually<bool> {
state.show_help = !state.show_help;
Ok(true)
}
fn focus_next (state: &mut Launcher) -> Usually<bool> {
match state.view {
LauncherView::Tracks => { state.view = LauncherView::Sequencer; },
LauncherView::Sequencer => { state.view = LauncherView::Chains; },
LauncherView::Chains => { state.view = LauncherView::Tracks; },
draw_box_styled(buf, Rect { height: 18, ..area }, style);
},
_ => {},
};
Ok(true)
}
fn focus_prev (state: &mut Launcher) -> Usually<bool> {
match state.view {
LauncherView::Tracks => { state.view = LauncherView::Chains; },
LauncherView::Chains => { state.view = LauncherView::Sequencer; },
LauncherView::Sequencer => { state.view = LauncherView::Tracks; },
_ => {},
};
Ok(true)
}
fn clip_next (state: &mut Launcher) -> Usually<bool> {
if state.cursor.0 >= 1 && state.cursor.1 >= 2 {
let scene_id = state.cursor.1 - 2;
let clip_id = state.cursor.0 - 1;
let scene = &mut state.scenes[scene_id];
scene.clips[clip_id] = match scene.clips[clip_id] {
None => Some(0),
Some(i) => if i >= state.tracks[clip_id].sequencer.state().sequences.len().saturating_sub(1) {
None
} else {
Some(i + 1)
if let Some(chain) = &chain {
if let Some(plugin) = plugins.get(chain.focus) {
draw_highlight(buf, &Some(*plugin), match state.view {
LauncherView::Chains => Style::default().green().not_dim(),
_ => Style::default().green().dim()
});
}
};
}
Ok(true)
}
fn clip_prev (state: &mut Launcher) -> Usually<bool> {
if state.cursor.0 >= 1 && state.cursor.1 >= 2 {
let scene_id = state.cursor.1 - 2;
let clip_id = state.cursor.0 - 1;
let scene = &mut state.scenes[scene_id];
scene.clips[clip_id] = match scene.clips[clip_id] {
None => Some(state.tracks[clip_id].sequencer.state().sequences.len().saturating_sub(1)),
Some(i) => if i == 0 {
None
} else {
Some(i - 1)
}
};
}
Ok(true)
}
fn play_toggle (s: &mut Launcher) -> Usually<bool> {
s.playing = match s.playing {
TransportState::Stopped => {
s.transport.start()?;
TransportState::Starting
},
_ => {
s.transport.stop()?;
s.transport.locate(0)?;
TransportState::Stopped
},
};
Ok(true)
}
fn play_start (_: &mut Launcher) -> Usually<bool> {
unimplemented!()
}
fn record_toggle (s: &mut Launcher) -> Usually<bool> {
s.recording = !s.recording;
for track in s.tracks.iter() {
track.sequencer.state().recording = s.recording;
}
Ok(true)
}
fn overdub_toggle (s: &mut Launcher) -> Usually<bool> {
s.overdub = !s.overdub;
for track in s.tracks.iter() {
track.sequencer.state().overdub = s.overdub;
}
Ok(true)
}
fn monitor_toggle (s: &mut Launcher) -> Usually<bool> {
s.monitoring = !s.monitoring;
for track in s.tracks.iter() {
track.sequencer.state().monitoring = s.monitoring;
}
Ok(true)
Ok(area)
}

157
src/device/launcher/grid.rs Normal file
View file

@ -0,0 +1,157 @@
use crate::prelude::*;
use super::*;
pub struct LauncherGridView<'a> {
state: &'a Launcher,
buf: &'a mut Buffer,
area: Rect,
focused: bool,
separator: String
}
impl<'a> LauncherGridView<'a> {
pub fn new (state: &'a Launcher, buf: &'a mut Buffer, area: Rect, focused: bool) -> Self {
let separator = format!("{}", "-".repeat((area.width - 2).into()));
Self { state, buf, area, separator, focused }
}
pub fn draw (&mut self) -> Usually<Rect> {
self.separator_h(0, false);
self.separator_h(2, false);
self.separator_h((self.state.cursor.1 * 2) as u16, true);
self.separator_h(((self.state.cursor.1 + 1) * 2) as u16, true);
let columns = self.column_names();
let (mut x, y) = (self.area.x, self.area.y);
for (i, w) in self.column_widths().iter().enumerate() {
if x >= self.area.x + self.area.width {
break
}
self.separator_v(x, i == self.state.cursor.0);
x = x + w;
self.separator_v(x, i == self.state.cursor.0);
}
let (mut x, y) = (self.area.x, self.area.y);
for (i, column) in columns.iter().enumerate() {
if x >= self.area.x + self.area.width {
break
}
column.blit(
self.buf, x + 2, y + 1, Some(self.highlight(i == self.state.cursor.0).bold())
);
if i == 0 {
self.scenes(x + 2, y + 3);
} else if i < columns.len() {
self.clips(x + 2, y + 3, i - 1);
}
let w = (column.len() as u16).max(12) + 3;
x = x + w;
}
"Add track…".blit(self.buf, x + 2, y + 1, Some(Style::default().dim()));
draw_box_styled(self.buf, self.area, Some(self.highlight(self.focused)));
Ok(self.area)
}
fn column_names (&self) -> Vec<&'a str> {
let mut columns = vec![self.state.name.as_str()];
for track in self.state.tracks.iter() {
columns.push(track.name.as_str());
}
columns
}
fn column_widths (&self) -> Vec<u16> {
self.column_names().iter().map(|name|(name.len() as u16).max(12) + 3).collect()
}
fn scenes (&mut self, x: u16, y: u16) {
let mut y2 = 0;
loop {
if y2 >= self.area.height {
break
}
if y2 % 2 == 0 {
let index = (y2 / 2) as usize;
if let Some(scene) = self.state.scenes.get(index) {
format!("{}", scene.name).blit(
self.buf, x, y + y2,
Some(self.highlight(index + 1 == self.state.cursor.1))
)
} else {
break
}
}
y2 = y2 + 1;
}
"Add scene…".blit(self.buf, x, y + y2, Some(Style::default().dim()));
}
fn clips (&mut self, x: u16, y: u16, track: usize) {
let mut y2 = 0;
loop {
if y2 >= self.area.height {
break
}
if y2 % 2 == 0 {
let index = (y2 / 2) as usize;
if index >= self.state.scenes.len() {
break
}
if let Some(scene) = self.state.scenes.get(index) {
let hi = (track + 1 == self.state.cursor.0) &&
(index + 1 == self.state.cursor.1);
let style = Some(self.highlight(hi));
if let Some(Some(clip)) = scene.clips.get(track) {
if let Some(phrase) = self.state.tracks[track].sequencer.state().sequences.get(*clip) {
format!("{}", phrase.name).blit(self.buf, x, y + y2, style);
} else {
"????".blit(self.buf, x, y + y2, Some(Style::default().dim()))
}
} else {
"....".blit(self.buf, x, y + y2, Some(Style::default().dim()))
}
if hi {
draw_box_styled(self.buf, Rect {
x: x - 2,
y: y + y2 - 1,
width: 16,
height: 3
}, style);
}
}
}
y2 = y2 + 1;
}
"Add clip…".blit(self.buf, x, y + y2, Some(Style::default().dim()));
}
fn highlight (&self, highlight: bool) -> Style {
if highlight {
if self.focused {
Style::default().green().not_dim()
} else {
Style::default().green().dim()
}
} else {
Style::default()
}
}
fn separator_h (&mut self, y: u16, highlight: bool) {
let style = Some(self.highlight(highlight));
self.separator.blit(self.buf, self.area.x, self.area.y + y, style);
}
fn separator_v (&mut self, x: u16, highlight: bool) {
let style = Some(self.highlight(highlight));
"".blit(self.buf, x, self.area.y + 0, style);
"".blit(self.buf, x, self.area.y + 1, style);
"".blit(self.buf, x, self.area.y + 2, style);
"".blit(self.buf, x, self.area.y + 3, style);
"".blit(self.buf, x, self.area.y + 4, style);
for y in self.area.y+5..self.area.y+self.area.height-1 {
"".blit(self.buf, x, y, style);
}
"".blit(self.buf, x, self.area.y+self.area.height-1, style);
}
}

View file

@ -0,0 +1,194 @@
use crate::prelude::*;
use super::*;
pub fn handle (state: &mut Launcher, event: &AppEvent) -> Usually<bool> {
Ok(handle_keymap(state, event, KEYMAP)? || match state.view {
LauncherView::Modal(ref mut device) => {
device.handle(event)?
},
LauncherView::Tracks => {
handle_keymap(state, event, KEYMAP_TRACKS)?
},
LauncherView::Sequencer => {
let i = state.col().saturating_sub(1);
if let Some(track) = state.tracks.get_mut(i) {
crate::device::sequencer::handle(&mut *track.sequencer.state(), event)?
} else {
true
}
},
LauncherView::Chains => {
true
}
})
}
pub const KEYMAP: &'static [KeyBinding<Launcher>] = keymap!(Launcher {
[F(1), NONE, "toggle_help", "toggle help", toggle_help],
[Tab, SHIFT, "focus_prev", "focus previous area", focus_prev],
[Tab, NONE, "focus_next", "focus next area", focus_next],
[Char(' '), NONE, "play_toggle", "play or pause", play_toggle],
[Char('r'), NONE, "record_toggle", "toggle recording", record_toggle],
[Char('d'), NONE, "overdub_toggle", "toggle overdub", overdub_toggle],
[Char('m'), NONE, "monitor_toggle", "toggle input monitoring", monitor_toggle],
[Char('r'), CONTROL, "rename", "rename current element", rename],
[Char('t'), CONTROL, "add_track", "add a new track", add_track],
//[Char(' '), SHIFT, "play_start", "play from start", play_start],
});
pub const KEYMAP_TRACKS: &'static [KeyBinding<Launcher>] = keymap!(Launcher {
[Up, NONE, "cursor_up", "move cursor up", cursor_up],
[Down, NONE, "cursor_down", "move cursor down", cursor_down],
[Left, NONE, "cursor_left", "move cursor left", cursor_left],
[Right, NONE, "cursor_right", "move cursor right", cursor_right],
[Char('.'), NONE, "clip_next", "set clip to next phrase", clip_next],
[Char(','), NONE, "clip_prev", "set clip to last phrase", clip_prev],
[Delete, CONTROL, "delete_track", "delete track", delete_track],
[Enter, NONE, "clip_enter", "play or record clip or stop and advance", clip_enter],
});
fn clip_enter (_: &mut Launcher) -> Usually<bool> {
//let track = state.active_track().unwrap();
//let scene = state.active_scene();
//if state.cursor.1 >= 2 {
//if let Some(Some(index)) = scene.clips.get(state.cursor.1 - 2) {
//track.enqueue(index)
//} else {
//}
//}
//if state.cursor.0 >= 1 {
//let sequencer = state.tracks.get_mut(state.cursor.0 - 1);
//if state.cursor.1 >= 2 {
//let scene = state.scenes.get_mut(state.cursor.1 - 2);
//if let Some(index) = scene.get(state.cursor.0 - 1) {
//let phrase = sequencer.phrases.get(index);
//} else {
//let index = sequencer.phrases.len();
//let phrase = Phrase::new(&format!("Phrase#{index}"));
//sequencer.phrases.push(phrase);
//scene[state.cursor.0 - 1] = Some(index);
//}
//}
//}
Ok(true)
}
fn rename (_: &mut Launcher) -> Usually<bool> {
Ok(true)
}
fn add_track (state: &mut Launcher) -> Usually<bool> {
let name = format!("Track {}", state.tracks.len() + 1);
state.tracks.push(Track::new(&name, &state.timebase, None, None)?);
state.cursor.0 = state.tracks.len();
Ok(true)
}
fn delete_track (state: &mut Launcher) -> Usually<bool> {
if state.cursor.0 >= 1 {
state.tracks.remove(state.cursor.0 - 1);
state.cursor.0 = state.cursor.0.min(state.tracks.len());
}
Ok(true)
}
fn cursor_up (state: &mut Launcher) -> Usually<bool> {
state.dec_row();
Ok(true)
}
fn cursor_down (state: &mut Launcher) -> Usually<bool> {
state.inc_row();
Ok(true)
}
fn cursor_left (state: &mut Launcher) -> Usually<bool> {
state.dec_col();
Ok(true)
}
fn cursor_right (state: &mut Launcher) -> Usually<bool> {
state.inc_col();
Ok(true)
}
fn toggle_help (state: &mut Launcher) -> Usually<bool> {
state.show_help = !state.show_help;
Ok(true)
}
fn focus_next (state: &mut Launcher) -> Usually<bool> {
match state.view {
LauncherView::Tracks => { state.view = LauncherView::Chains; },
LauncherView::Chains => { state.view = LauncherView::Sequencer; },
LauncherView::Sequencer => { state.view = LauncherView::Tracks; },
_ => {},
};
Ok(true)
}
fn focus_prev (state: &mut Launcher) -> Usually<bool> {
match state.view {
LauncherView::Tracks => { state.view = LauncherView::Sequencer; },
LauncherView::Sequencer => { state.view = LauncherView::Chains; },
LauncherView::Chains => { state.view = LauncherView::Tracks; },
_ => {},
};
Ok(true)
}
fn clip_next (state: &mut Launcher) -> Usually<bool> {
if state.cursor.0 >= 1 && state.cursor.1 >= 1 {
let scene_id = state.cursor.1 - 1;
let clip_id = state.cursor.0 - 1;
let scene = &mut state.scenes[scene_id];
scene.clips[clip_id] = match scene.clips[clip_id] {
None => Some(0),
Some(i) => if i >= state.tracks[clip_id].sequencer.state().sequences.len().saturating_sub(1) {
None
} else {
Some(i + 1)
}
};
}
Ok(true)
}
fn clip_prev (state: &mut Launcher) -> Usually<bool> {
if state.cursor.0 >= 1 && state.cursor.1 >= 1 {
let scene_id = state.cursor.1 - 1;
let clip_id = state.cursor.0 - 1;
let scene = &mut state.scenes[scene_id];
scene.clips[clip_id] = match scene.clips[clip_id] {
None => Some(state.tracks[clip_id].sequencer.state().sequences.len().saturating_sub(1)),
Some(i) => if i == 0 {
None
} else {
Some(i - 1)
}
};
}
Ok(true)
}
fn play_toggle (s: &mut Launcher) -> Usually<bool> {
s.playing = match s.playing {
TransportState::Stopped => {
s.transport.start()?;
TransportState::Starting
},
_ => {
s.transport.stop()?;
s.transport.locate(0)?;
TransportState::Stopped
},
};
Ok(true)
}
//fn play_start (_: &mut Launcher) -> Usually<bool> {
//unimplemented!()
//}
fn record_toggle (s: &mut Launcher) -> Usually<bool> {
s.recording = !s.recording;
for track in s.tracks.iter() {
track.sequencer.state().recording = s.recording;
}
Ok(true)
}
fn overdub_toggle (s: &mut Launcher) -> Usually<bool> {
s.overdub = !s.overdub;
for track in s.tracks.iter() {
track.sequencer.state().overdub = s.overdub;
}
Ok(true)
}
fn monitor_toggle (s: &mut Launcher) -> Usually<bool> {
s.monitoring = !s.monitoring;
for track in s.tracks.iter() {
track.sequencer.state().monitoring = s.monitoring;
}
Ok(true)
}

View file

@ -1,82 +1,101 @@
use crate::prelude::*;
pub const ACTIONS: [(&'static str, &'static str);2] = [
("Enter", "Play sample"),
("Ins/Del", "Add/remove sample"),
];
pub struct Sampler {
name: String,
input: ::jack::Port<::jack::MidiIn>,
samples: Arc<Mutex<Vec<Sample>>>,
selected_sample: usize,
selected_column: usize,
midi_ins: Vec<Port<MidiIn>>,
cursor: (usize, usize),
samples: Vec<Arc<Sample>>,
voices: Vec<Voice>,
midi_in: Port<MidiIn>,
audio_ins: Vec<Port<AudioIn>>,
audio_outs: Vec<Port<AudioOut>>,
}
pub struct Sample {
port: Port<AudioOut>,
name: String,
rate: u32,
gain: f64,
channels: u8,
data: Vec<Vec<f32>>,
trigger: (u8, u8),
playing: Option<usize>,
channels: Vec<Vec<f32>>,
start: usize,
}
impl Sample {
fn new (name: &str) -> Arc<Self> {
Arc::new(Self { name: name.to_string(), channels: vec![], start: 0 })
}
fn play (self: &Arc<Self>) -> Voice {
Voice { sample: self.clone(), position: self.start }
}
}
pub struct Voice {
sample: Arc<Sample>,
position: usize,
}
impl Voice {
fn chunk (&mut self, frames: usize) -> Vec<Vec<f32>> {
let mut chunk = vec![];
for channel in self.sample.channels.iter() {
chunk.push(channel[self.position..self.position+frames].into());
};
self.position = self.position + frames;
chunk
}
}
impl Sampler {
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
let samples = vec![
Sample::new("Kick", &client, 1, 35)?,
Sample::new("Snare", &client, 1, 38)?,
];
let samples = Arc::new(Mutex::new(samples));
let input = client.register_port("trigger", ::jack::MidiIn::default())?;
DynamicDevice::new(render, handle, Self::process, Self {
name: name.into(),
input,
selected_sample: 0,
selected_column: 0,
samples,
midi_ins: vec![],
audio_ins: vec![],
audio_outs: vec![],
cursor: (0, 0),
samples: vec![
Sample::new("Kick"),
Sample::new("Snare"),
],
voices: vec![
],
midi_in: client.register_port("midi", ::jack::MidiIn::default())?,
audio_ins: vec![
client.register_port("recL", ::jack::AudioIn::default())?,
client.register_port("recR", ::jack::AudioIn::default())?,
],
audio_outs: vec![
client.register_port("outL", ::jack::AudioOut::default())?,
client.register_port("outR", ::jack::AudioOut::default())?,
],
}).activate(client)
}
pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
let mut samples = self.samples.lock().unwrap();
for event in self.input.iter(scope) {
let len = 3.min(event.bytes.len());
let mut data = [0; 3];
data[..len].copy_from_slice(&event.bytes[..len]);
if (data[0] >> 4) == 0b1001 { // note on
let channel = data[0] & 0b00001111;
let note = data[1];
let velocity = data[2];
for sample in samples.iter_mut() {
if sample.trigger.0 == channel && sample.trigger.1 == note {
sample.play(velocity);
}
}
}
for sample in samples.iter_mut() {
if let Some(playing) = sample.playing {
for (index, value) in sample.port.as_mut_slice(scope).iter_mut().enumerate() {
*value = *sample.data[0].get(playing + index).unwrap_or(&0f32);
}
if playing + scope.n_frames() as usize > sample.data[0].len() {
sample.playing = None
} else {
sample.playing = Some(playing + scope.n_frames() as usize)
}
}
}
}
// emit currently playing voices
// process midi in
// add new voices
// emit new voices starting from midi event frames
//for event in self.midi_in.iter(scope) {
//let len = 3.min(event.bytes.len());
//let mut data = [0; 3];
//data[..len].copy_from_slice(&event.bytes[..len]);
//if (data[0] >> 4) == 0b1001 { // note on
//let channel = data[0] & 0b00001111;
//let note = data[1];
//let velocity = data[2];
//for sample in self.samples.iter_mut() {
//if sample.trigger.0 == channel && sample.trigger.1 == note {
//sample.play(velocity);
//}
//}
//}
//for sample in self.samples.iter_mut() {
//if let Some(playing) = sample.playing {
//for (index, value) in sample.port.as_mut_slice(scope).iter_mut().enumerate() {
//*value = *sample.data[0].get(playing + index).unwrap_or(&0f32);
//}
//if playing + scope.n_frames() as usize > sample.data[0].len() {
//sample.playing = None
//} else {
//sample.playing = Some(playing + scope.n_frames() as usize)
//}
//}
//}
//}
Control::Continue
}
@ -84,6 +103,9 @@ impl Sampler {
}
impl PortList for Sampler {
fn midi_ins (&self) -> Usually<Vec<String>> {
Ok(vec![self.midi_in.name()?])
}
fn audio_ins (&self) -> Usually<Vec<String>> {
let mut ports = vec![];
for port in self.audio_ins.iter() {
@ -98,42 +120,14 @@ impl PortList for Sampler {
}
Ok(ports)
}
fn midi_ins (&self) -> Usually<Vec<String>> {
let mut ports = vec![];
for port in self.midi_ins.iter() {
ports.push(port.name()?);
}
Ok(ports)
}
}
impl Sample {
pub fn new (name: &str, client: &Client, channel: u8, note: u8) -> Result<Self, Box<dyn Error>> {
Ok(Self {
port: client.register_port(name, ::jack::AudioOut::default())?,
name: name.into(),
rate: 44100,
channels: 1,
gain: 0.0,
data: vec![vec![1.0, 0.0, 0.0, 0.0]],
trigger: (channel, note),
playing: None
})
}
fn play (&mut self, _velocity: u8) {
self.playing = Some(0)
}
}
pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, width, .. }: Rect)
pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, height, .. }: Rect)
-> Usually<Rect>
{
let width = 40;
let style = Style::default().gray();
draw_box(buf, Rect { x, y: y, width: 40, height: 12 });
//draw_box(buf, Rect { x, y: y, width: 40, height: 12 });
let separator = format!("{}", "-".repeat((width - 2).into()));
separator.blit(buf, x, y + 2, Some(style.dim()));
format!(" {}", state.name).blit(buf, x+1, y+1, Some(style.white().bold()));
@ -153,7 +147,7 @@ pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, width, .. }: Rect
format!(" {cut}")
.blit(buf, x+2, y+4+i*2, Some(style));
}
Ok(Rect { x, y, width, height: 11 })
Ok(Rect { x, y, width, height: height - 3 })
}
//fn render_table (
@ -202,6 +196,11 @@ pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, width, .. }: Rect
pub fn handle (_: &mut Sampler, _: &AppEvent) -> Usually<bool> {
Ok(false)
//pub const ACTIONS: [(&'static str, &'static str);2] = [
//("Enter", "Play sample"),
//("Ins/Del", "Add/remove sample"),
//];
//if let Event::Input(crossterm::event::Event::Key(event)) = event {
//match event.code {
//KeyCode::Char('c') => {

View file

@ -1,21 +1,25 @@
use crate::prelude::*;
use ratatui::style::Stylize;
mod keys;
use keys::*;
use self::keys::*;
mod handle;
pub use self::handle::*;
pub mod horizontal;
pub mod vertical;
pub struct Sequence {
type PhraseData = BTreeMap<u32, Vec<MidiMessage>>;
pub struct Phrase {
pub name: String,
pub notes: std::collections::BTreeMap<u32, Vec<::midly::MidiMessage>>
pub length: u32,
pub notes: PhraseData,
}
impl Sequence {
pub fn new (name: &str) -> Self {
Self { name: name.to_string(), notes: std::collections::BTreeMap::new() }
impl Phrase {
pub fn new (name: &str, length: u32, notes: Option<PhraseData>) -> Self {
Self { name: name.to_string(), length, notes: notes.unwrap_or(BTreeMap::new()) }
}
}
@ -36,10 +40,10 @@ pub struct Sequencer {
/// Steps in sequence, e.g. 64 16ths = 4 beat loop.
/// FIXME: play start / end / loop in ppm
steps: usize,
/// Sequence selector
/// Phrase selector
sequence: usize,
/// Map: tick -> MIDI events at tick
pub sequences: Vec<Sequence>,
pub sequences: Vec<Phrase>,
/// Red keys on piano roll.
notes_on: Vec<bool>,
@ -76,20 +80,21 @@ impl Sequencer {
pub fn new (
name: &str,
timebase: &Arc<Timebase>,
sequences: Option<Vec<Sequence>>,
sequences: Option<Vec<Phrase>>,
) -> Usually<DynamicDevice<Self>> {
let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?;
let transport = client.transport();
let ppq = timebase.ppq() as u32;
DynamicDevice::new(render, handle, Self::process, Self {
name: name.into(),
midi_in: client.register_port("in", MidiIn::default())?,
midi_out: client.register_port("out", MidiOut::default())?,
timebase: timebase.clone(),
steps: 64,
steps: 16,
resolution: 4,
sequence: 0,
sequences: sequences.unwrap_or(vec![Sequence::new("")]),
sequences: sequences.unwrap_or(vec![]),
notes_on: vec![false;128],
playing: TransportState::Starting,
@ -108,6 +113,10 @@ impl Sequencer {
pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
// Prepare output buffer
let frames = scope.n_frames() as usize;
let mut output: Vec<Option<Vec<Vec<u8>>>> = vec![None;frames];
// Update time
let sequence = self.sequences.get_mut(self.sequence);
if sequence.is_none() {
@ -115,6 +124,18 @@ impl Sequencer {
}
let sequence = &mut sequence.unwrap().notes;
let transport = self.transport.query().unwrap();
// If starting or stopping, send "all notes off":
if transport.state != self.playing {
output[0] = Some(vec![]);
if let Some(Some(frame)) = output.get_mut(0) {
let mut buf = vec![];
LiveEvent::Midi {
channel: 0.into(),
message: MidiMessage::Controller { controller: 123.into(), value: 0.into() }
}.write(&mut buf).unwrap();
frame.push(buf);
}
}
self.playing = transport.state;
let pos = &transport.pos;
let usecs = self.timebase.frame_to_usec(pos.frame() as usize);
@ -122,10 +143,6 @@ impl Sequencer {
let step = steps % self.steps;
let tick = step * self.timebase.ppq() / self.resolution;
// Prepare output buffer
let frames = scope.n_frames() as usize;
let mut output: Vec<Option<Vec<Vec<u8>>>> = vec![None;frames];
// Read from sequence into output buffer
if self.playing == TransportState::Rolling {
let frame = transport.pos.frame() as usize;
@ -137,9 +154,7 @@ impl Sequencer {
let mut buf = vec![];
let channel = 0.into();
let message = *message;
::midly::live::LiveEvent::Midi { channel, message }
.write(&mut buf)
.unwrap();
LiveEvent::Midi { channel, message }.write(&mut buf).unwrap();
let t = *time as usize;
if output[t].is_none() {
output[t] = Some(vec![]);
@ -337,11 +352,11 @@ fn draw_clips (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
}
Ok(Rect { x, y, width: 14, height: 14 })
}
pub fn contains_note_on (sequence: &Sequence, k: ::midly::num::u7, start: u32, end: u32) -> bool {
pub fn contains_note_on (sequence: &Phrase, k: u7, start: u32, end: u32) -> bool {
for (_, (_, events)) in sequence.notes.range(start..end).enumerate() {
for event in events.iter() {
match event {
::midly::MidiMessage::NoteOn {key,..} => {
MidiMessage::NoteOn {key,..} => {
if *key == k {
return true
}
@ -352,193 +367,6 @@ pub fn contains_note_on (sequence: &Sequence, k: ::midly::num::u7, start: u32, e
}
return false
}
pub fn handle (s: &mut Sequencer, event: &AppEvent) -> Usually<bool> {
handle_keymap(s, event, KEYMAP)
}
pub const KEYMAP: &'static [KeyBinding<Sequencer>] = keymap!(Sequencer {
[Up, NONE, "cursor_up", "move cursor up", cursor_up],
[Down, NONE, "cursor_down", "move cursor down", cursor_down],
[Left, NONE, "cursor_left", "move cursor left", cursor_left],
[Right, NONE, "cursor_right", "move cursor right", cursor_right],
[Char(']'), NONE, "cursor_inc", "increase note duration", cursor_duration_inc],
[Char('['), NONE, "cursor_dec", "decrease note duration", cursor_duration_dec],
[Char('`'), NONE, "mode_next", "Next view mode", mode_next],
[Char('+'), NONE, "zoom_in", "Zoom in", nop],
[Char('-'), NONE, "zoom_out", "Zoom out", nop],
[Char('a'), NONE, "note_add", "Add note", note_add],
[Char('z'), NONE, "note_del", "Delete note", note_del],
[CapsLock, NONE, "advance", "Toggle auto advance", nop],
[Char('w'), NONE, "rest", "Advance by note duration", nop],
[Char(' '), NONE, "toggle_play", "Toggle play/pause", toggle_play],
[Char('r'), NONE, "toggle_record", "Toggle recording", toggle_record],
[Char('d'), NONE, "toggle_overdub", "Toggle overdub", toggle_overdub],
[Char('m'), NONE, "toggle_monitor", "Toggle input monitoring", toggle_monitor],
[Char('s'), NONE, "stop_and_rewind", "Stop and rewind", stop_and_rewind],
[Char('q'), NONE, "quantize_next", "Next quantize value", quantize_next],
[Char('Q'), SHIFT, "quantize_prev", "Previous quantize value", quantize_prev],
[Char('n'), NONE, "note_axis", "Focus note axis", nop],
[Char('t'), NONE, "time_axis", "Focus time axis", nop],
[Char('v'), NONE, "variations", "Focus variation selector", nop],
[Char('s'), SHIFT, "sync", "Focus sync selector", nop],
[Char('1'), NONE, "seq_1", "Sequence 1", focus_seq(0)],
[Char('2'), NONE, "seq_2", "Sequence 2", focus_seq(1)],
[Char('3'), NONE, "seq_3", "Sequence 3", focus_seq(2)],
[Char('4'), NONE, "seq_4", "Sequence 4", focus_seq(3)],
[Char('5'), NONE, "seq_5", "Sequence 5", focus_seq(4)],
[Char('6'), NONE, "seq_6", "Sequence 6", focus_seq(5)],
[Char('7'), NONE, "seq_7", "Sequence 7", focus_seq(6)],
[Char('8'), NONE, "seq_8", "Sequence 8", focus_seq(7)],
});
const fn focus_seq (i: usize) -> impl Fn(&mut Sequencer)->Usually<bool> {
move |s: &mut Sequencer| {
s.sequence = i;
Ok(true)
}
}
fn nop (_: &mut Sequencer) -> Usually<bool> {
Ok(false)
}
fn note_add (s: &mut Sequencer) -> Usually<bool> {
let step = (s.time_axis.0 + s.time_cursor) as u32;
let start = (step as usize * s.timebase.ppq() / s.resolution) as u32;
let end = ((step + 1) as usize * s.timebase.ppq() / s.resolution) as u32;
let key = ::midly::num::u7::from_int_lossy((s.note_cursor + s.note_axis.0) as u8);
let note_on = ::midly::MidiMessage::NoteOn { key, vel: 100.into() };
let note_off = ::midly::MidiMessage::NoteOff { key, vel: 100.into() };
let sequence = &mut s.sequences[s.sequence].notes;
if sequence.contains_key(&start) {
sequence.get_mut(&start).unwrap().push(note_on.clone());
} else {
sequence.insert(start, vec![note_on]);
}
if sequence.contains_key(&end) {
sequence.get_mut(&end).unwrap().push(note_off.clone());
} else {
sequence.insert(end, vec![note_off]);
};
Ok(true)
}
fn note_del (_: &mut Sequencer) -> Usually<bool> {
Ok(true)
}
fn time_cursor_inc (s: &mut Sequencer) -> Usually<bool> {
let time = s.time_axis.1 - s.time_axis.0;
s.time_cursor = ((time + s.time_cursor) + 1) % time;
Ok(true)
}
fn time_cursor_dec (s: &mut Sequencer) -> Usually<bool> {
let time = s.time_axis.1 - s.time_axis.0;
s.time_cursor = ((time + s.time_cursor) - 1) % time;
Ok(true)
}
fn note_cursor_inc (s: &mut Sequencer) -> Usually<bool> {
let note = s.note_axis.1 - s.note_axis.0;
s.note_cursor = ((note + s.note_cursor) + 1) % note;
Ok(true)
}
fn note_cursor_dec (s: &mut Sequencer) -> Usually<bool> {
let note = s.note_axis.1 - s.note_axis.0;
s.note_cursor = ((note + s.note_cursor) - 1) % note;
Ok(true)
}
fn cursor_up (s: &mut Sequencer) -> Usually<bool> {
match s.mode {
SequencerView::Vertical => time_cursor_dec(s),
SequencerView::Horizontal => note_cursor_dec(s),
_ => Ok(false)
};
Ok(true)
}
fn cursor_down (s: &mut Sequencer) -> Usually<bool> {
match s.mode {
SequencerView::Vertical => time_cursor_inc(s),
SequencerView::Horizontal => note_cursor_inc(s),
_ => Ok(false)
};
Ok(true)
}
fn cursor_left (s: &mut Sequencer) -> Usually<bool> {
match s.mode {
SequencerView::Vertical => note_cursor_dec(s),
SequencerView::Horizontal => time_cursor_dec(s),
_ => Ok(false)
};
Ok(true)
}
fn cursor_right (s: &mut Sequencer) -> Usually<bool> {
match s.mode {
SequencerView::Vertical => note_cursor_inc(s),
SequencerView::Horizontal => time_cursor_inc(s),
_ => Ok(false)
};
Ok(true)
}
fn cursor_duration_inc (_: &mut Sequencer) -> Usually<bool> {
//s.cursor.2 = s.cursor.2 + 1
Ok(true)
}
fn cursor_duration_dec (_: &mut Sequencer) -> Usually<bool> {
//if s.cursor.2 > 0 { s.cursor.2 = s.cursor.2 - 1 }
Ok(true)
}
fn mode_next (s: &mut Sequencer) -> Usually<bool> {
s.mode = s.mode.next();
Ok(true)
}
impl SequencerView {
fn next (&self) -> Self {
match self {
Self::Horizontal => Self::Vertical,
Self::Vertical => Self::Tiny,
Self::Tiny => Self::Horizontal,
_ => self.clone()
}
}
}
fn stop_and_rewind (s: &mut Sequencer) -> Usually<bool> {
s.transport.stop()?;
s.transport.locate(0)?;
s.playing = TransportState::Stopped;
Ok(true)
}
fn toggle_play (s: &mut Sequencer) -> Usually<bool> {
s.playing = match s.playing {
TransportState::Stopped => {
s.transport.start()?;
TransportState::Starting
},
_ => {
s.transport.stop()?;
s.transport.locate(0)?;
TransportState::Stopped
},
};
Ok(true)
}
fn toggle_record (s: &mut Sequencer) -> Usually<bool> {
s.recording = !s.recording;
Ok(true)
}
fn toggle_overdub (s: &mut Sequencer) -> Usually<bool> {
s.overdub = !s.overdub;
Ok(true)
}
fn toggle_monitor (s: &mut Sequencer) -> Usually<bool> {
s.monitoring = !s.monitoring;
Ok(true)
}
fn quantize_next (s: &mut Sequencer) -> Usually<bool> {
if s.resolution < 64 {
s.resolution = s.resolution * 2;
}
Ok(true)
}
fn quantize_prev (s: &mut Sequencer) -> Usually<bool> {
if s.resolution > 1 {
s.resolution = s.resolution / 2;
}
Ok(true)
}
#[cfg(test)] mod test {
use super::*;

View file

@ -0,0 +1,190 @@
use crate::prelude::*;
use super::*;
pub fn handle (s: &mut Sequencer, event: &AppEvent) -> Usually<bool> {
handle_keymap(s, event, KEYMAP)
}
pub const KEYMAP: &'static [KeyBinding<Sequencer>] = keymap!(Sequencer {
[Up, NONE, "cursor_up", "move cursor up", cursor_up],
[Down, NONE, "cursor_down", "move cursor down", cursor_down],
[Left, NONE, "cursor_left", "move cursor left", cursor_left],
[Right, NONE, "cursor_right", "move cursor right", cursor_right],
[Char(']'), NONE, "cursor_inc", "increase note duration", cursor_duration_inc],
[Char('['), NONE, "cursor_dec", "decrease note duration", cursor_duration_dec],
[Char('`'), NONE, "mode_next", "Next view mode", mode_next],
[Char('+'), NONE, "zoom_in", "Zoom in", nop],
[Char('-'), NONE, "zoom_out", "Zoom out", nop],
[Char('a'), NONE, "note_add", "Add note", note_add],
[Char('z'), NONE, "note_del", "Delete note", note_del],
[CapsLock, NONE, "advance", "Toggle auto advance", nop],
[Char('w'), NONE, "rest", "Advance by note duration", nop],
[Char(' '), NONE, "toggle_play", "Toggle play/pause", toggle_play],
[Char('r'), NONE, "toggle_record", "Toggle recording", toggle_record],
[Char('d'), NONE, "toggle_overdub", "Toggle overdub", toggle_overdub],
[Char('m'), NONE, "toggle_monitor", "Toggle input monitoring", toggle_monitor],
[Char('s'), NONE, "stop_and_rewind", "Stop and rewind", stop_and_rewind],
[Char('q'), NONE, "quantize_next", "Next quantize value", quantize_next],
[Char('Q'), SHIFT, "quantize_prev", "Previous quantize value", quantize_prev],
[Char('n'), NONE, "note_axis", "Focus note axis", nop],
[Char('t'), NONE, "time_axis", "Focus time axis", nop],
[Char('v'), NONE, "variations", "Focus variation selector", nop],
[Char('s'), SHIFT, "sync", "Focus sync selector", nop],
[Char('1'), NONE, "seq_1", "Phrase 1", focus_seq(0)],
[Char('2'), NONE, "seq_2", "Phrase 2", focus_seq(1)],
[Char('3'), NONE, "seq_3", "Phrase 3", focus_seq(2)],
[Char('4'), NONE, "seq_4", "Phrase 4", focus_seq(3)],
[Char('5'), NONE, "seq_5", "Phrase 5", focus_seq(4)],
[Char('6'), NONE, "seq_6", "Phrase 6", focus_seq(5)],
[Char('7'), NONE, "seq_7", "Phrase 7", focus_seq(6)],
[Char('8'), NONE, "seq_8", "Phrase 8", focus_seq(7)],
});
const fn focus_seq (i: usize) -> impl Fn(&mut Sequencer)->Usually<bool> {
move |s: &mut Sequencer| {
s.sequence = i;
Ok(true)
}
}
fn nop (_: &mut Sequencer) -> Usually<bool> {
Ok(false)
}
fn note_add (s: &mut Sequencer) -> Usually<bool> {
let step = (s.time_axis.0 + s.time_cursor) as u32;
let start = (step as usize * s.timebase.ppq() / s.resolution) as u32;
let end = ((step + 1) as usize * s.timebase.ppq() / s.resolution) as u32;
let key = u7::from_int_lossy((s.note_cursor + s.note_axis.0) as u8);
let note_on = MidiMessage::NoteOn { key, vel: 100.into() };
let note_off = MidiMessage::NoteOff { key, vel: 100.into() };
let sequence = &mut s.sequences[s.sequence].notes;
if sequence.contains_key(&start) {
sequence.get_mut(&start).unwrap().push(note_on.clone());
} else {
sequence.insert(start, vec![note_on]);
}
if sequence.contains_key(&end) {
sequence.get_mut(&end).unwrap().push(note_off.clone());
} else {
sequence.insert(end, vec![note_off]);
};
Ok(true)
}
fn note_del (_: &mut Sequencer) -> Usually<bool> {
Ok(true)
}
fn time_cursor_inc (s: &mut Sequencer) -> Usually<bool> {
let time = s.time_axis.1 - s.time_axis.0;
s.time_cursor = ((time + s.time_cursor) + 1) % time;
Ok(true)
}
fn time_cursor_dec (s: &mut Sequencer) -> Usually<bool> {
let time = s.time_axis.1 - s.time_axis.0;
s.time_cursor = ((time + s.time_cursor) - 1) % time;
Ok(true)
}
fn note_cursor_inc (s: &mut Sequencer) -> Usually<bool> {
let note = s.note_axis.1 - s.note_axis.0;
s.note_cursor = ((note + s.note_cursor) + 1) % note;
Ok(true)
}
fn note_cursor_dec (s: &mut Sequencer) -> Usually<bool> {
let note = s.note_axis.1 - s.note_axis.0;
s.note_cursor = ((note + s.note_cursor) - 1) % note;
Ok(true)
}
fn cursor_up (s: &mut Sequencer) -> Usually<bool> {
match s.mode {
SequencerView::Vertical => time_cursor_dec(s),
SequencerView::Horizontal => note_cursor_dec(s),
_ => Ok(false)
}?;
Ok(true)
}
fn cursor_down (s: &mut Sequencer) -> Usually<bool> {
match s.mode {
SequencerView::Vertical => time_cursor_inc(s),
SequencerView::Horizontal => note_cursor_inc(s),
_ => Ok(false)
}?;
Ok(true)
}
fn cursor_left (s: &mut Sequencer) -> Usually<bool> {
match s.mode {
SequencerView::Vertical => note_cursor_dec(s),
SequencerView::Horizontal => time_cursor_dec(s),
_ => Ok(false)
}?;
Ok(true)
}
fn cursor_right (s: &mut Sequencer) -> Usually<bool> {
match s.mode {
SequencerView::Vertical => note_cursor_inc(s),
SequencerView::Horizontal => time_cursor_inc(s),
_ => Ok(false)
}?;
Ok(true)
}
fn cursor_duration_inc (_: &mut Sequencer) -> Usually<bool> {
//s.cursor.2 = s.cursor.2 + 1
Ok(true)
}
fn cursor_duration_dec (_: &mut Sequencer) -> Usually<bool> {
//if s.cursor.2 > 0 { s.cursor.2 = s.cursor.2 - 1 }
Ok(true)
}
fn mode_next (s: &mut Sequencer) -> Usually<bool> {
s.mode = s.mode.next();
Ok(true)
}
impl SequencerView {
fn next (&self) -> Self {
match self {
Self::Horizontal => Self::Vertical,
Self::Vertical => Self::Tiny,
Self::Tiny => Self::Horizontal,
_ => self.clone()
}
}
}
fn stop_and_rewind (s: &mut Sequencer) -> Usually<bool> {
s.transport.stop()?;
s.transport.locate(0)?;
s.playing = TransportState::Stopped;
Ok(true)
}
fn toggle_play (s: &mut Sequencer) -> Usually<bool> {
s.playing = match s.playing {
TransportState::Stopped => {
s.transport.start()?;
TransportState::Starting
},
_ => {
s.transport.stop()?;
s.transport.locate(0)?;
TransportState::Stopped
},
};
Ok(true)
}
fn toggle_record (s: &mut Sequencer) -> Usually<bool> {
s.recording = !s.recording;
Ok(true)
}
fn toggle_overdub (s: &mut Sequencer) -> Usually<bool> {
s.overdub = !s.overdub;
Ok(true)
}
fn toggle_monitor (s: &mut Sequencer) -> Usually<bool> {
s.monitoring = !s.monitoring;
Ok(true)
}
fn quantize_next (s: &mut Sequencer) -> Usually<bool> {
if s.resolution < 64 {
s.resolution = s.resolution * 2;
}
Ok(true)
}
fn quantize_prev (s: &mut Sequencer) -> Usually<bool> {
if s.resolution > 1 {
s.resolution = s.resolution / 2;
}
Ok(true)
}

View file

@ -35,16 +35,18 @@ pub fn timer (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16, beat: usize) {
}
}
pub fn keys (s: &Sequencer, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
pub fn keys (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let bw = Style::default().dim();
let Rect { x, y, .. } = area;
let Rect { x, y, width, height } = area;
let (note0, note1) = s.note_axis;
let (time0, time1) = s.time_axis;
for i in 0..32.max(note1-note0)/2 {
let h = 32.max(height.saturating_sub(2)*2)/2;
for i in 0..h {
let y = y + i;
buf.set_string(x + 1, y, KEYS_VERTICAL[(i % 6) as usize], bw);
buf.set_string(x + 2, y, "", bw);
buf.set_string(x + 5, y, &"·".repeat((time1 - time0) as usize), bw.black());
buf.set_string(x + 5, y, &"·".repeat(width.saturating_sub(6) as usize), bw.black());
//buf.set_string(x + 3, y, &format!("{i}"), Style::default());
if i % 6 == 0 {
let octave = format!("C{}", ((note1 - i) / 6) as i8 - 4);
buf.set_string(x + 3, y, &octave, Style::default());
@ -69,7 +71,8 @@ pub fn lanes (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16, width: u16) {
if step % s.resolution as u16 == 0 {
buf.set_string(x + 5 + step, y - 1, &format!("{}", step + 1), Style::default());
}
for k in 0..(note1 - note0)/2 {
let h = (note1 - note0)/2;
for k in 0..h {
let (character, style) = match (
contains_note_on(&s.sequences[s.sequence],
::midly::num::u7::from_int_lossy((note0 + k * 2 + 0) as u8),
@ -84,7 +87,7 @@ pub fn lanes (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16, width: u16) {
(false, false) => ("·", bw),
};
//let (character, style) = ("▄", bg);
buf.set_string(x + 5 + step, y + k, character, style);
buf.set_string(x + 5 + step, y + h - k, character, style);
}
//for (_, (_, events)) in notes.range(time_start..time_end).enumerate() {
//if events.len() > 0 {

View file

@ -1,4 +1,3 @@
use crate::prelude::*;
use super::*;
pub const KEY_WHITE: Style = Style {

View file

@ -8,11 +8,12 @@ impl Track {
pub fn new (
name: &str,
tempo: &Arc<Timebase>,
devices: Vec<Box<dyn Device>>
devices: Option<Vec<Box<dyn Device>>>,
phrases: Option<Vec<Phrase>>,
) -> Usually<Self> {
Ok(Self {
name: name.to_string(),
sequencer: Sequencer::new(&name, tempo, None)?,
sequencer: Sequencer::new(&name, tempo, phrases)?,
chain: Chain::new(&name, devices)?,
})
}

View file

@ -4,6 +4,8 @@ mod container;
pub use container::*;
mod scroll;
pub use scroll::*;
mod table;
pub use table::*;
use crate::prelude::*;

33
src/layout/table.rs Normal file
View file

@ -0,0 +1,33 @@
use crate::prelude::*;
pub struct Cell<T> {
text: String,
style: Option<Style>,
width: u16,
height: u16,
data: T
}
impl<T> Cell<T> {
pub fn new (text: &str, data: T) -> Self {
Self { text: text.to_string(), style: None, width: text.len() as u16, height: 1, data }
}
pub fn draw (&self, buf: &mut Buffer, x: u16, y: u16) {
self.text.blit(buf, x, y, self.style)
}
}
pub struct Table<T> {
columns: Vec<Vec<Cell<T>>>,
row: usize,
col: usize,
}
impl<T> Table<T> {
pub fn new (columns: Vec<Vec<Cell<T>>>) -> Self {
Self { columns, row: 0, col: 0 }
}
pub fn set (&mut self, col: usize, row: usize, cell: Cell<T>) {
self.columns[col][row] = cell;
}
}

View file

@ -9,6 +9,7 @@ pub use std::error::Error;
pub use std::io::{stdout, Stdout, Write};
pub use std::thread::{spawn, JoinHandle};
pub use std::time::Duration;
pub use std::collections::BTreeMap;
pub use std::sync::{
Arc,
Mutex,
@ -40,6 +41,8 @@ pub use ratatui::{
//style::Stylize,
};
pub use ::midly::{MidiMessage, live::LiveEvent, num::u7};
pub use jack::{
AsyncClient,
AudioIn,