wip: long awaited fixes to main sequencer

This commit is contained in:
🪞👃🪞 2024-07-01 04:20:41 +03:00
parent 2837ffff4a
commit 78e5469b32
17 changed files with 391 additions and 563 deletions

View file

@ -16,5 +16,7 @@
# for ChowKick.lv2: # for ChowKick.lv2:
freetype freetype
libgcc.lib libgcc.lib
# for Panagement
xorg.libX11
]); ]);
} }

View file

@ -12,13 +12,8 @@ pub use std::thread::{spawn, JoinHandle};
pub use std::time::Duration; pub use std::time::Duration;
pub use std::collections::BTreeMap; pub use std::collections::BTreeMap;
pub use std::sync::{ pub use std::sync::{
Arc, Arc, Mutex, MutexGuard,
Mutex, MutexGuard, atomic::{Ordering, AtomicBool, AtomicUsize},
atomic::{
Ordering,
AtomicBool,
AtomicUsize
},
mpsc::{self, channel, Sender, Receiver} mpsc::{self, channel, Sender, Receiver}
}; };
@ -33,21 +28,8 @@ pub use ::crossterm::{
}, },
}; };
pub use ::ratatui::{ pub use ::ratatui::prelude::*;
prelude::{ pub use ::midly::{MidiMessage, live::LiveEvent, num::u7};
Buffer, Rect, Style, Color, CrosstermBackend, Layout, Stylize, Direction,
Line, Constraint
},
widgets::{Widget, WidgetRef},
//style::Stylize,
};
pub use ::midly::{
MidiMessage,
live::LiveEvent,
num::u7
};
pub use crate::{key, keymap}; pub use crate::{key, keymap};
/// Run a device as the root of the app. /// Run a device as the root of the app.

View file

@ -28,13 +28,13 @@ impl Render for Box<dyn Device> {
} }
} }
impl WidgetRef for &dyn Render { impl ratatui::widgets::WidgetRef for &dyn Render {
fn render_ref (&self, area: Rect, buf: &mut Buffer) { fn render_ref (&self, area: Rect, buf: &mut Buffer) {
Render::render(*self, buf, area).expect("Failed to render device."); Render::render(*self, buf, area).expect("Failed to render device.");
} }
} }
impl WidgetRef for dyn Render { impl ratatui::widgets::WidgetRef for dyn Render {
fn render_ref (&self, area: Rect, buf: &mut Buffer) { fn render_ref (&self, area: Rect, buf: &mut Buffer) {
Render::render(self, buf, area).expect("Failed to render device."); Render::render(self, buf, area).expect("Failed to render device.");
} }

View file

@ -4,7 +4,7 @@ pub struct Timebase {
/// Frames per second /// Frames per second
pub rate: AtomicUsize, pub rate: AtomicUsize,
/// Beats per minute /// Beats per minute
pub tempo: AtomicUsize, pub bpm: AtomicUsize,
/// Ticks per beat /// Ticks per beat
pub ppq: AtomicUsize, pub ppq: AtomicUsize,
} }
@ -35,14 +35,26 @@ impl Timebase {
#[inline] pub fn rate (&self) -> usize { #[inline] pub fn rate (&self) -> usize {
self.rate.load(Ordering::Relaxed) self.rate.load(Ordering::Relaxed)
} }
#[inline] pub fn tempo (&self) -> usize { #[inline] pub fn bpm (&self) -> usize {
self.tempo.load(Ordering::Relaxed) self.bpm.load(Ordering::Relaxed)
} }
#[inline] pub fn ppq (&self) -> usize { #[inline] pub fn ppq (&self) -> usize {
self.ppq.load(Ordering::Relaxed) self.ppq.load(Ordering::Relaxed)
} }
#[inline] pub fn pulse_to_frame (&self, pulses: usize) -> usize {
let beat = self.bpm() / 60;
let quaver = beat / 4;
let pulse = quaver / self.ppq();
pulse * pulses
}
#[inline] pub fn frame_to_pulse (&self, frames: usize) -> usize {
let beat = self.bpm() / 60;
let quaver = beat / 4;
let pulse = quaver / self.ppq();
frames / pulse
}
#[inline] fn beats_per_second (&self) -> f64 { #[inline] fn beats_per_second (&self) -> f64 {
self.tempo() as f64 / 60000.0 self.bpm() as f64 / 60000.0
} }
#[inline] fn frames_per_second (&self) -> usize { #[inline] fn frames_per_second (&self) -> usize {
self.rate() self.rate()
@ -72,7 +84,7 @@ impl Timebase {
self.usec_per_beat() * beats_per_bar self.usec_per_beat() * beats_per_bar
} }
#[inline] pub fn usec_per_beat (&self) -> usize { #[inline] pub fn usec_per_beat (&self) -> usize {
60_000_000_000 / self.tempo() 60_000_000_000 / self.bpm()
} }
#[inline] pub fn usec_per_step (&self, divisor: usize) -> usize { #[inline] pub fn usec_per_step (&self, divisor: usize) -> usize {
self.usec_per_beat() / divisor self.usec_per_beat() / divisor
@ -93,9 +105,9 @@ impl Timebase {
#[inline] pub fn note_to_frame (&self, note: &NoteDuration) -> usize { #[inline] pub fn note_to_frame (&self, note: &NoteDuration) -> usize {
self.usec_to_frame(self.note_to_usec(note)) self.usec_to_frame(self.note_to_usec(note))
} }
pub fn frames_to_ticks (&self, start: usize, end: usize, fpl: usize) -> Vec<(usize, usize)> { pub fn frames_to_ticks (&self, start: usize, end: usize, quant: usize) -> Vec<(usize, usize)> {
let start_frame = start % fpl; let start_frame = start % quant;
let end_frame = end % fpl; let end_frame = end % quant;
let fpt = self.frames_per_tick(); let fpt = self.frames_per_tick();
let mut ticks = vec![]; let mut ticks = vec![];
let mut add_frame = |frame: f64|{ let mut add_frame = |frame: f64|{
@ -115,7 +127,7 @@ impl Timebase {
loop { loop {
add_frame(frame as f64); add_frame(frame as f64);
frame = frame + 1; frame = frame + 1;
if frame >= fpl { if frame >= quant {
frame = 0; frame = 0;
loop { loop {
add_frame(frame as f64); add_frame(frame as f64);

View file

@ -1,6 +1,6 @@
use crate::core::*; use crate::core::*;
mod lv2; mod lv2; pub use lv2::*;
mod vst2; mod vst2;
mod vst3; mod vst3;
@ -8,7 +8,6 @@ pub struct Plugin {
name: String, name: String,
path: Option<String>, path: Option<String>,
plugin: Option<PluginKind>, plugin: Option<PluginKind>,
offset: usize,
selected: usize, selected: usize,
mapping: bool, mapping: bool,
midi_ins: Vec<Port<MidiIn>>, midi_ins: Vec<Port<MidiIn>>,
@ -18,12 +17,7 @@ pub struct Plugin {
} }
enum PluginKind { enum PluginKind {
LV2 { LV2(LV2Plugin),
world: ::livi::World,
features: Arc<::livi::Features>,
port_list: Vec<::livi::Port>,
instance: ::livi::Instance,
},
VST2 { VST2 {
instance: ::vst::host::PluginInstance instance: ::vst::host::PluginInstance
}, },
@ -34,57 +28,66 @@ const HELM: &'static str = "file:///nix/store/ij3sz7nqg5l7v2dygdvzy3w6cj62bd6r-h
impl Plugin { impl Plugin {
/// Load a LV2 plugin. /// Load a LV2 plugin.
pub fn lv2 (name: &str, path: &str, ports: &[usize;4]) -> Usually<DynamicDevice<Self>> { pub fn lv2 (name: &str, path: &str) -> Usually<DynamicDevice<Self>> {
let plugin = Self::new(name, ports)?; let host = Self::new(name)?;
let mut state = plugin.state(); let plugin = LV2Plugin::new(path)?;
state.plugin = Some(self::lv2::plug(path)?); let mut state = host.state();
let client = host.client.as_ref().unwrap().as_client();
let (midi_ins, midi_outs, audio_ins, audio_outs) = (
plugin.plugin.port_counts().atom_sequence_inputs,
plugin.plugin.port_counts().atom_sequence_outputs,
plugin.plugin.port_counts().audio_inputs,
plugin.plugin.port_counts().audio_outputs,
);
state.midi_ins = {
let mut ports = vec![];
for i in 0..midi_ins {
ports.push(client.register_port(&format!("midi-in-{i}"), MidiIn::default())?)
}
ports
};
state.midi_outs = {
let mut ports = vec![];
for i in 0..midi_outs {
ports.push(client.register_port(&format!("midi-out-{i}"), MidiOut::default())?)
}
ports
};
state.audio_ins = {
let mut ports = vec![];
for i in 0..audio_ins {
ports.push(client.register_port(&format!("audio-in-{i}"), AudioIn::default())?)
}
ports
};
state.audio_outs = {
let mut ports = vec![];
for i in 0..audio_outs {
ports.push(client.register_port(&format!("audio-out-{i}"), AudioOut::default())?)
}
ports
};
state.plugin = Some(PluginKind::LV2(plugin));
state.path = Some(String::from(path)); state.path = Some(String::from(path));
std::mem::drop(state); std::mem::drop(state);
Ok(plugin) Ok(host)
} }
pub fn new (name: &str, ports: &[usize;4]) -> Usually<DynamicDevice<Self>> { pub fn new (name: &str) -> Usually<DynamicDevice<Self>> {
let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?;
let [midi_ins, midi_outs, audio_ins, audio_outs] = ports;
DynamicDevice::new(render, handle, Self::process, Self { DynamicDevice::new(render, handle, Self::process, Self {
name: name.into(), name: name.into(),
path: None, path: None,
plugin: None, plugin: None,
offset: 0,
selected: 0, selected: 0,
mapping: false, mapping: false,
midi_ins: { midi_ins: vec![],
let mut ports = vec![]; midi_outs: vec![],
for i in 0..*midi_ins { audio_ins: vec![],
ports.push(client.register_port(&format!("midi-in-{i}"), MidiIn::default())?) audio_outs: vec![],
} }).activate(Client::new(name, ClientOptions::NO_START_SERVER)?.0)
ports
},
midi_outs: {
let mut ports = vec![];
for i in 0..*midi_outs {
ports.push(client.register_port(&format!("midi-out-{i}"), MidiOut::default())?)
}
ports
},
audio_ins: {
let mut ports = vec![];
for i in 0..*audio_ins {
ports.push(client.register_port(&format!("audio-in-{i}"), AudioIn::default())?)
}
ports
},
audio_outs: {
let mut ports = vec![];
for i in 0..*audio_outs {
ports.push(client.register_port(&format!("audio-out-{i}"), AudioOut::default())?)
}
ports
},
}).activate(client)
} }
pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
match self.plugin.as_mut() { match self.plugin.as_mut() {
Some(PluginKind::LV2 { features, ref mut instance, .. }) => { Some(PluginKind::LV2(LV2Plugin { features, ref mut instance, .. })) => {
let urid = features.midi_urid(); let urid = features.midi_urid();
let mut inputs = vec![]; let mut inputs = vec![];
for port in self.midi_ins.iter() { for port in self.midi_ins.iter() {
@ -171,7 +174,7 @@ pub fn render (state: &Plugin, buf: &mut Buffer, area: Rect)
let Rect { x, y, height, .. } = area; let Rect { x, y, height, .. } = area;
let mut width = 40u16; let mut width = 40u16;
match &state.plugin { match &state.plugin {
Some(PluginKind::LV2 { port_list, instance, .. }) => { Some(PluginKind::LV2(LV2Plugin { port_list, instance, .. })) => {
let start = state.selected.saturating_sub((height as usize / 2).saturating_sub(1)); let start = state.selected.saturating_sub((height as usize / 2).saturating_sub(1));
let end = start + height as usize - 2; let end = start + height as usize - 2;
//draw_box(buf, Rect { x, y, width, height }); //draw_box(buf, Rect { x, y, width, height });
@ -218,7 +221,7 @@ pub fn handle (s: &mut Plugin, event: &AppEvent) -> Usually<bool> {
s.selected = s.selected - 1 s.selected = s.selected - 1
} else { } else {
s.selected = match &s.plugin { s.selected = match &s.plugin {
Some(PluginKind::LV2 { port_list, .. }) => port_list.len() - 1, Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
_ => 0 _ => 0
} }
} }
@ -229,7 +232,7 @@ pub fn handle (s: &mut Plugin, event: &AppEvent) -> Usually<bool> {
|s: &mut Plugin|{ |s: &mut Plugin|{
s.selected = s.selected + 1; s.selected = s.selected + 1;
match &s.plugin { match &s.plugin {
Some(PluginKind::LV2 { port_list, .. }) => { Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => {
if s.selected >= port_list.len() { if s.selected >= port_list.len() {
s.selected = 0; s.selected = 0;
} }
@ -242,7 +245,7 @@ pub fn handle (s: &mut Plugin, event: &AppEvent) -> Usually<bool> {
[Char(','), NONE, "decrement", "decrement value", [Char(','), NONE, "decrement", "decrement value",
|s: &mut Plugin|{ |s: &mut Plugin|{
match s.plugin.as_mut() { match s.plugin.as_mut() {
Some(PluginKind::LV2 { port_list, ref mut instance, .. }) => { Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
let index = port_list[s.selected].index; let index = port_list[s.selected].index;
if let Some(value) = instance.control_input(index) { if let Some(value) = instance.control_input(index) {
instance.set_control_input(index, value - 0.01); instance.set_control_input(index, value - 0.01);
@ -256,7 +259,7 @@ pub fn handle (s: &mut Plugin, event: &AppEvent) -> Usually<bool> {
[Char('.'), NONE, "increment", "increment value", [Char('.'), NONE, "increment", "increment value",
|s: &mut Plugin|{ |s: &mut Plugin|{
match s.plugin.as_mut() { match s.plugin.as_mut() {
Some(PluginKind::LV2 { port_list, ref mut instance, .. }) => { Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
let index = port_list[s.selected].index; let index = port_list[s.selected].index;
if let Some(value) = instance.control_input(index) { if let Some(value) = instance.control_input(index) {
instance.set_control_input(index, value + 0.01); instance.set_control_input(index, value + 0.01);

View file

@ -1,28 +1,41 @@
use crate::core::*; use crate::core::*;
use super::*;
pub fn plug (uri: &str) -> Usually<PluginKind> { pub struct LV2Plugin {
pub world: ::livi::World,
pub instance: ::livi::Instance,
pub plugin: ::livi::Plugin,
pub features: Arc<::livi::Features>,
pub port_list: Vec<::livi::Port>,
}
impl LV2Plugin {
pub fn new (uri: &str) -> Usually<Self> {
// Get 1st plugin at URI
let world = ::livi::World::with_load_bundle(&uri); let world = ::livi::World::with_load_bundle(&uri);
let features = world.build_features(::livi::FeaturesBuilder { let features = ::livi::FeaturesBuilder { min_block_length: 1, max_block_length: 65536 };
min_block_length: 1, let features = world.build_features(features);
max_block_length: 65536,
});
let mut plugin = None; let mut plugin = None;
for p in world.iter_plugins() { for p in world.iter_plugins() {
plugin = Some(p); plugin = Some(p);
break break
} }
let plugin = plugin.unwrap(); let plugin = plugin.unwrap();
// Instantiate
Ok(Self {
world,
instance: unsafe {
plugin.instantiate(features.clone(), 48000.0).expect("boop")
},
port_list: {
let mut port_list = vec![]; let mut port_list = vec![];
for port in plugin.ports() { for port in plugin.ports() {
port_list.push(port); port_list.push(port);
} }
Ok(PluginKind::LV2 { port_list
instance: unsafe {
plugin.instantiate(features.clone(), 48000.0).expect("boop")
}, },
port_list, plugin,
features, features,
world,
}) })
} }
}

View file

@ -1,22 +1,16 @@
use crate::core::*; use crate::core::*;
use super::*; use super::*;
pub struct LauncherGridView<'a> { pub struct LauncherGrid<'a> {
state: &'a Launcher, state: &'a Launcher,
buf: &'a mut Buffer, buf: &'a mut Buffer,
area: Rect, area: Rect,
focused: bool, focused: bool,
separator: String
} }
impl<'a> LauncherGridView<'a> { impl<'a> LauncherGrid<'a> {
pub fn new (state: &'a Launcher, buf: &'a mut Buffer, area: Rect, focused: bool) -> Self { 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, focused }
Self { state, buf, area, separator, focused }
} }
pub fn draw (&mut self) -> Usually<Rect> { 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);
self.area.height = self.state.scenes.len() as u16 + 2; self.area.height = self.state.scenes.len() as u16 + 2;
let style = Some(Style::default().green().dim()); let style = Some(Style::default().green().dim());
if self.focused { if self.focused {
@ -24,9 +18,7 @@ impl<'a> LauncherGridView<'a> {
lozenge_left(self.buf, x, y, height, style); lozenge_left(self.buf, x, y, height, style);
lozenge_right(self.buf, x + width - 1, y, height, style); lozenge_right(self.buf, x + width - 1, y, height, style);
} }
let columns = self.column_names(); let columns = self.column_names();
let mut x = self.area.x; let mut x = self.area.x;
for (i, w) in self.column_widths().iter().enumerate() { for (i, w) in self.column_widths().iter().enumerate() {
if x >= self.area.x + self.area.width { if x >= self.area.x + self.area.width {
@ -36,7 +28,6 @@ impl<'a> LauncherGridView<'a> {
x = x + w; x = x + w;
self.separator_v(x, i == self.state.cursor.0); self.separator_v(x, i == self.state.cursor.0);
} }
let (mut x, y) = (self.area.x, self.area.y); let (mut x, y) = (self.area.x, self.area.y);
for (i, title) in columns.iter().enumerate() { for (i, title) in columns.iter().enumerate() {
if x >= self.area.x + self.area.width { if x >= self.area.x + self.area.width {
@ -53,17 +44,16 @@ impl<'a> LauncherGridView<'a> {
let w = (title.len() as u16).max(10) + 3; let w = (title.len() as u16).max(10) + 3;
x = x + w; x = x + w;
} }
"Add track…".blit(self.buf, x + 2, y, Some(Style::default().dim())); "Add track…".blit(self.buf, x + 2, y, Some(Style::default().dim()));
Ok(self.area) Ok(self.area)
} }
fn column_names (&self) -> Vec<&'a str> { fn column_names (&self) -> Vec<&'a str> {
let mut columns = vec![self.state.name.as_str()]; let mut column_names = vec![self.state.name.as_str()];
for track in self.state.tracks.iter() { for track in self.state.tracks.iter() {
columns.push(track.name.as_str()); column_names.push(track.name.as_str());
} }
columns column_names
} }
fn column_widths (&self) -> Vec<u16> { fn column_widths (&self) -> Vec<u16> {
@ -150,18 +140,11 @@ impl<'a> LauncherGridView<'a> {
} }
} }
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) { fn separator_v (&mut self, x: u16, highlight: bool) {
let style = Some(self.highlight(highlight)); let style = Some(self.highlight(highlight));
//"┬".blit(self.buf, x, self.area.y + 0, style);
for y in self.area.y+1..self.area.y+self.area.height-1 { for y in self.area.y+1..self.area.y+self.area.height-1 {
"".blit(self.buf, x, y, style); "".blit(self.buf, x, y, style);
} }
//"┴".blit(self.buf, x, self.area.y+self.area.height-1, style);
} }
} }

View file

@ -57,6 +57,10 @@ fn activate (state: &mut Launcher) -> Usually<bool> {
if let Some(phrase_id) = scene.clips.get(track_id) { if let Some(phrase_id) = scene.clips.get(track_id) {
track.sequencer.state().sequence = *phrase_id; track.sequencer.state().sequence = *phrase_id;
} }
if state.playing == TransportState::Stopped {
state.transport.start()?;
state.playing = TransportState::Starting;
}
} else if let Some((scene_id, scene)) = state.scene() { } else if let Some((scene_id, scene)) = state.scene() {
// Launch scene // Launch scene
for (track_id, track) in state.tracks.iter().enumerate() { for (track_id, track) in state.tracks.iter().enumerate() {
@ -64,6 +68,10 @@ fn activate (state: &mut Launcher) -> Usually<bool> {
track.sequencer.state().sequence = *phrase_id; track.sequencer.state().sequence = *phrase_id;
} }
} }
if state.playing == TransportState::Stopped {
state.transport.start()?;
state.playing = TransportState::Starting;
}
} else if let Some((track_id, track)) = state.track() { } else if let Some((track_id, track)) = state.track() {
// Rename track? // Rename track?
} }

View file

@ -131,10 +131,10 @@ impl Launcher {
} } } }
} }
fn sequencer <'a> (&'a self) -> Option<MutexGuard<Sequencer>> { fn sequencer <'a> (&'a self) -> Option<MutexGuard<Sequencer>> {
self.track().map(|t|t.1.sequencer.state()) Some(self.track()?.1.sequencer.state())
} }
fn chain <'a> (&'a self) -> Option<MutexGuard<Chain>> { fn chain <'a> (&'a self) -> Option<MutexGuard<Chain>> {
self.track().map(|t|t.1.chain.state()) Some(self.track()?.1.chain.state())
} }
fn phrase_id (&self) -> Option<usize> { fn phrase_id (&self) -> Option<usize> {
let (track_id, _) = self.track()?; let (track_id, _) = self.track()?;
@ -190,10 +190,10 @@ pub fn render (state: &Launcher, buf: &mut Buffer, mut area: Rect) -> Usually<Re
crate::device::transport::draw_rec(buf, x + 12, y, state.recording); crate::device::transport::draw_rec(buf, x + 12, y, state.recording);
crate::device::transport::draw_mon(buf, x + 19, y, state.monitoring); crate::device::transport::draw_mon(buf, x + 19, y, state.monitoring);
crate::device::transport::draw_dub(buf, x + 26, y, state.overdub); crate::device::transport::draw_dub(buf, x + 26, y, state.overdub);
draw_bpm(buf, x + 33, y, state.timebase.tempo()); crate::device::transport::draw_bpm(buf, x + 33, y, state.timebase.bpm());
draw_timer(buf, x + width - 1, y, &state.timebase, state.position); crate::device::transport::draw_timer(buf, x + width - 1, y, &state.timebase, state.position);
let mut y = y + 1; let mut y = y + 1;
y = y + LauncherGridView::new( y = y + LauncherGrid::new(
state, buf, Rect { x, y, width, height: height / 3 }, state.view.is_tracks() state, buf, Rect { x, y, width, height: height / 3 }, state.view.is_tracks()
).draw()?.height; ).draw()?.height;
y = y + draw_section_sequencer(state, buf, Rect { x, y, width, height: height / 3 })?.height; y = y + draw_section_sequencer(state, buf, Rect { x, y, width, height: height / 3 })?.height;
@ -206,28 +206,6 @@ pub fn render (state: &Launcher, buf: &mut Buffer, mut area: Rect) -> Usually<Re
} }
Ok(area) Ok(area)
} }
fn draw_bpm (buf: &mut Buffer, x: u16, y: u16, tempo: usize) {
let style = Style::default().not_dim();
"BPM"
.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;
let (beats, ticks) = (tick / timebase.ppq(), tick % timebase.ppq());
let (bars, beats) = (beats / 4, beats % 4);
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_section_sequencer (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> { fn draw_section_sequencer (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Rect { x, y, width, height } = area; let Rect { x, y, width, height } = area;
let style = Some(Style::default().green().dim()); let style = Some(Style::default().green().dim());
@ -239,29 +217,31 @@ fn draw_section_sequencer (state: &Launcher, buf: &mut Buffer, area: Rect) -> Us
}, },
_ => {}, _ => {},
}; };
if let Some((_, track)) = state.track() { let track = state.track();
let frame = state.position; if track.is_none() {
let timebase = &state.timebase; return Ok(area);
let tick = (frame as f64 / timebase.frames_per_tick()) as usize; }
let track = track.unwrap().1;
let sequencer = track.sequencer.state(); let sequencer = track.sequencer.state();
let zoom = sequencer.resolution; crate::device::sequencer::horizontal::timer(
buf, x+5, y,
let steps = if let Some(_phrase) = sequencer.phrase() { 0 } else { 0 } / 4; // TODO sequencer.time_axis.0,
sequencer.time_axis.0 + area.width,
crate::device::sequencer::horizontal::timer(buf, x+5, y, 0
steps, sequencer.steps * zoom, sequencer.time_axis.0, sequencer.time_axis.1
); );
crate::device::sequencer::horizontal::keys(buf, Rect { x, y: y + 1, width, height }, crate::device::sequencer::horizontal::keys(
buf, Rect { x, y: y + 1, width, height },
sequencer.note_axis.1 sequencer.note_axis.1
)?; )?;
if let Some(id) = state.phrase_id() { if let Some(id) = state.phrase_id() {
if let Some(phrase) = sequencer.phrases.get(id) { if let Some(phrase) = sequencer.phrases.get(id) {
crate::device::sequencer::horizontal::lanes(buf, x, y + 1, crate::device::sequencer::horizontal::lanes(
buf, x, y + 1,
&phrase, &phrase,
sequencer.timebase.ppq() as u32, sequencer.timebase.ppq() as u32,
sequencer.resolution as u32, sequencer.time_zoom as u32,
sequencer.time_axis.0 as u32, sequencer.time_axis.0 as u32,
sequencer.time_axis.1 as u32, sequencer.time_axis.0 as u32 + area.width as u32,
sequencer.note_axis.0 as u32, sequencer.note_axis.0 as u32,
sequencer.note_axis.1 as u32, sequencer.note_axis.1 as u32,
); );
@ -275,14 +255,8 @@ fn draw_section_sequencer (state: &Launcher, buf: &mut Buffer, area: Rect) -> Us
sequencer.time_cursor, sequencer.time_cursor,
sequencer.note_cursor sequencer.note_cursor
); );
}
Ok(area) Ok(area)
} }
fn draw_highlight (buf: &mut Buffer, highlight: &Option<Rect>, style: Style) {
if let Some(area) = highlight {
draw_box_styled(buf, *area, Some(style));
}
}
fn draw_section_chains (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> { fn draw_section_chains (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let style = Some(Style::default().green().dim()); let style = Some(Style::default().green().dim());
match state.view { match state.view {

View file

@ -51,8 +51,8 @@ fn note_add (s: &mut Sequencer) -> Usually<bool> {
return Ok(false) return Ok(false)
} }
let step = (s.time_axis.0 + s.time_cursor) as u32; let step = (s.time_axis.0 + s.time_cursor) as u32;
let start = (step as usize * s.timebase.ppq() / s.resolution) as u32; let start = (step as usize * s.timebase.ppq() / s.time_zoom) as u32;
let end = ((step + 1) as usize * s.timebase.ppq() / s.resolution) as u32; let end = ((step + 1) as usize * s.timebase.ppq() / s.time_zoom) as u32;
let key = u7::from_int_lossy((s.note_cursor + s.note_axis.0) as u8); 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_on = MidiMessage::NoteOn { key, vel: 100.into() };
let note_off = MidiMessage::NoteOff { key, vel: 100.into() }; let note_off = MidiMessage::NoteOff { key, vel: 100.into() };
@ -179,14 +179,14 @@ fn toggle_monitor (s: &mut Sequencer) -> Usually<bool> {
Ok(true) Ok(true)
} }
fn quantize_next (s: &mut Sequencer) -> Usually<bool> { fn quantize_next (s: &mut Sequencer) -> Usually<bool> {
if s.resolution < 64 { if s.time_zoom < 64 {
s.resolution = s.resolution * 2; s.time_zoom = s.time_zoom * 2;
} }
Ok(true) Ok(true)
} }
fn quantize_prev (s: &mut Sequencer) -> Usually<bool> { fn quantize_prev (s: &mut Sequencer) -> Usually<bool> {
if s.resolution > 1 { if s.time_zoom > 1 {
s.resolution = s.resolution / 2; s.time_zoom = s.time_zoom / 2;
} }
Ok(true) Ok(true)
} }

View file

@ -10,13 +10,13 @@ pub fn draw (
area.x = area.x + 13; area.x = area.x + 13;
let Rect { x, y, width, .. } = area; let Rect { x, y, width, .. } = area;
keys(buf, area, s.note_axis.1)?; keys(buf, area, s.note_axis.1)?;
timer(buf, x + 6, y - 1, beat, s.steps, s.time_axis.0, s.time_axis.1); timer(buf, x+6, y-1, s.time_axis.0, s.time_axis.1, beat as u16);
let height = 32.max(s.note_axis.1 - s.note_axis.0) / 2; let height = 32.max(s.note_axis.1 - s.note_axis.0) / 2;
if let Some(phrase) = s.phrase() { if let Some(phrase) = s.phrase() {
lanes(buf, x, y, lanes(buf, x, y,
phrase, phrase,
s.timebase.ppq() as u32, s.timebase.ppq() as u32,
s.resolution as u32, s.time_zoom as u32,
s.time_axis.0 as u32, s.time_axis.0 as u32,
s.time_axis.1 as u32, s.time_axis.1 as u32,
s.note_axis.0 as u32, s.note_axis.0 as u32,
@ -36,9 +36,9 @@ pub fn draw (
}) })
} }
pub fn timer (buf: &mut Buffer, x: u16, y: u16, beat: usize, steps: usize, time0: u16, time1: u16) { pub fn timer (buf: &mut Buffer, x: u16, y: u16, time0: u16, time1: u16, now: u16) {
for step in time0..time1 { for step in time0..time1 {
buf.set_string(x + step, y, &"-", if beat % steps == step as usize { buf.set_string(x + step, y, &"-", if step == now {
Style::default().yellow().bold().not_dim() Style::default().yellow().bold().not_dim()
} else { } else {
Style::default() Style::default()
@ -123,7 +123,7 @@ pub fn footer (s: &Sequencer, buf: &mut Buffer, mut x: u16, y: u16, width: u16,
{ {
for (_, [letter, title, value]) in [ for (_, [letter, title, value]) in [
["S", &format!("ync"), &format!("<4/4>")], ["S", &format!("ync"), &format!("<4/4>")],
["Q", &format!("uant"), &format!("<1/{}>", 4 * s.resolution)], ["Q", &format!("uant"), &format!("<1/{}>", 4 * s.time_zoom)],
["N", &format!("ote"), &format!("{} ({}-{})", ["N", &format!("ote"), &format!("{} ({}-{})",
s.note_axis.0 + s.note_cursor, s.note_axis.0 + s.note_cursor,
s.note_axis.0, s.note_axis.0,

View file

@ -19,9 +19,6 @@ pub struct Sequencer {
pub midi_out: Port<MidiOut>, pub midi_out: Port<MidiOut>,
/// Holds info about tempo /// Holds info about tempo
pub timebase: Arc<Timebase>, pub timebase: Arc<Timebase>,
/// Steps in sequence, e.g. 64 16ths = 4 beat loop.
/// FIXME: play start / end / loop in ppm
pub steps: usize,
/// Phrase selector /// Phrase selector
pub sequence: Option<usize>, pub sequence: Option<usize>,
/// Map: tick -> MIDI events at tick /// Map: tick -> MIDI events at tick
@ -42,9 +39,8 @@ pub struct Sequencer {
pub note_axis: (u16, u16), pub note_axis: (u16, u16),
/// Position of cursor within note range /// Position of cursor within note range
pub note_cursor: u16, pub note_cursor: u16,
/// Sequencer resolution, e.g. 16 steps per beat. /// PPM per display unit
/// FIXME: grid in ppm will simplify calculations pub time_zoom: usize,
pub resolution: usize,
/// Range of time steps to display /// Range of time steps to display
pub time_axis: (u16, u16), pub time_axis: (u16, u16),
/// Position of cursor within time range /// Position of cursor within time range
@ -68,8 +64,6 @@ impl Sequencer {
midi_out: client.register_port("out", MidiOut::default())?, midi_out: client.register_port("out", MidiOut::default())?,
timebase: timebase.clone(), timebase: timebase.clone(),
steps: 16,
resolution: 4,
sequence: Some(0), sequence: Some(0),
phrases: phrases.unwrap_or(vec![ phrases: phrases.unwrap_or(vec![
Phrase::new("Phrase0", 4*timebase.ppq() as u32, None) Phrase::new("Phrase0", 4*timebase.ppq() as u32, None)
@ -85,13 +79,14 @@ impl Sequencer {
mode: SequencerView::Horizontal, mode: SequencerView::Horizontal,
note_axis: (36, 68), note_axis: (36, 68),
note_cursor: 0, note_cursor: 0,
time_zoom: 24,
time_axis: (0, 64), time_axis: (0, 64),
time_cursor: 0, time_cursor: 0,
}).activate(client) }).activate(client)
} }
pub fn phrase <'a> (&'a self) -> Option<&'a Phrase> { pub fn phrase <'a> (&'a self) -> Option<&'a Phrase> {
self.sequence.map(|s|self.phrases.get(s))? self.phrases.get(self.sequence?)
} }
} }
@ -111,7 +106,7 @@ fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let pos = s.transport.query().unwrap().pos; let pos = s.transport.query().unwrap().pos;
let frame = pos.frame(); let frame = pos.frame();
let usecs = s.timebase.frame_to_usec(frame as usize); let usecs = s.timebase.frame_to_usec(frame as usize);
let ustep = s.timebase.usec_per_step(s.resolution as usize); let ustep = s.timebase.usec_per_step(s.time_zoom as usize);
let steps = usecs / ustep; let steps = usecs / ustep;
let header = draw_header(s, buf, area, steps)?; let header = draw_header(s, buf, area, steps)?;
let piano = match s.mode { let piano = match s.mode {
@ -141,12 +136,12 @@ fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
} }
pub fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) -> Usually<Rect> { pub fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) -> Usually<Rect> {
let Rect { x, y, width, .. } = area; let Rect { x, y, .. } = area;
let rep = beat / s.steps; //let rep = beat / s.steps;
let step = beat % s.steps; //let step = beat % s.steps;
let reps = s.steps / s.resolution; //let reps = s.steps / s.time_zoom;
let steps = s.steps % s.resolution; //let steps = s.steps % s.time_zoom;
draw_timer(buf, x + width - 2, y + 1, &format!("{rep}.{step:02} / {reps}.{steps}")); //draw_timer(buf, x + width - 2, y + 1, &format!("{rep}.{step:02} / {reps}.{steps}"));
let style = Style::default().gray(); let style = Style::default().gray();
crate::device::transport::draw_play_stop(buf, x + 2, y + 1, &s.playing); crate::device::transport::draw_play_stop(buf, x + 2, y + 1, &s.playing);
let separator = format!("{}", "-".repeat((area.width - 2).into())); let separator = format!("{}", "-".repeat((area.width - 2).into()));

View file

@ -14,24 +14,37 @@ impl Phrase {
pub fn new (name: &str, length: u32, notes: Option<PhraseData>) -> Self { pub fn new (name: &str, length: u32, notes: Option<PhraseData>) -> Self {
Self { name: name.to_string(), length, notes: notes.unwrap_or(BTreeMap::new()) } Self { name: name.to_string(), length, notes: notes.unwrap_or(BTreeMap::new()) }
} }
pub fn frames (&self, timebase: &Arc<Timebase>) -> usize {
timebase.pulse_to_frame(self.length as usize)
}
pub fn frame_to_pulse (&self, timebase: &Arc<Timebase>, frame: usize) -> usize {
timebase.frame_to_pulse(frame) % self.length as usize
}
/** Write a chunk of MIDI events to an output port. */ /** Write a chunk of MIDI events to an output port. */
pub fn chunk_out ( pub fn chunk_out (
&self, &self,
output: &mut MIDIChunk, output: &mut MIDIChunk,
start: usize, notes_on: &mut Vec<bool>,
length: usize,
timebase: &Arc<Timebase>, timebase: &Arc<Timebase>,
steps: usize, frame0: usize,
resolution: usize, frames: usize,
) { ) {
let quant = timebase.frames_per_beat() as usize * steps / resolution; let quant = self.frames(timebase);
let ticks = timebase.frames_to_ticks(start, start + length, quant); let ticks = timebase.frames_to_ticks(frame0, frame0 + frames, quant);
for (time, tick) in ticks.iter() { for (time, tick) in ticks.iter() {
if let Some(events) = self.notes.get(&(*tick as u32)) { let events = self.notes.get(&(*tick as u32));
for message in events.iter() { if events.is_none() {
continue
}
for message in events.unwrap().iter() {
let mut buf = vec![]; let mut buf = vec![];
let channel = 0.into(); let channel = 0.into();
let message = *message; let message = *message;
match message {
MidiMessage::NoteOn { key, .. } => notes_on[key.as_int() as usize] = true,
MidiMessage::NoteOff { key, .. } => notes_on[key.as_int() as usize] = false,
_ => {}
}
LiveEvent::Midi { channel, message }.write(&mut buf).unwrap(); LiveEvent::Midi { channel, message }.write(&mut buf).unwrap();
let t = *time as usize; let t = *time as usize;
if output[t].is_none() { if output[t].is_none() {
@ -43,64 +56,57 @@ impl Phrase {
} }
} }
} }
}
/** React a chunk of MIDI events from an input port. */ /** React a chunk of MIDI events from an input port. */
pub fn chunk_in ( pub fn chunk_in (
&mut self, &mut self,
port: &Port<MidiIn>, input: ::jack::MidiIter,
scope: &ProcessScope, notes_on: &mut Vec<bool>,
mut monitor: Option<&mut MIDIChunk>, mut monitor: Option<&mut MIDIChunk>,
record: bool, record: bool,
notes_on: &mut Vec<bool>, timebase: &Arc<Timebase>,
tick: u32, frame0: usize,
) { ) {
for event in port.iter(scope) { for RawMidi { time, bytes } in input {
let msg = LiveEvent::parse(event.bytes).unwrap(); let time = time as usize;
match msg { let pulse = timebase.frame_to_pulse(frame0 + time) as u32;
LiveEvent::Midi { message, .. } => match message { if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() {
MidiMessage::NoteOn { key, vel: _ } => { if let MidiMessage::NoteOn { key, vel: _ } = message {
notes_on[key.as_int() as usize] = true; notes_on[key.as_int() as usize] = true;
if let Some(ref mut monitor) = monitor { if let Some(ref mut monitor) = monitor {
let t = event.time as usize; if monitor[time].is_none() {
if monitor[t].is_none() { monitor[time] = Some(vec![]);
monitor[t] = Some(vec![]);
} }
if let Some(Some(frame)) = monitor.get_mut(t) { if let Some(Some(frame)) = monitor.get_mut(time) {
frame.push(event.bytes.into()) frame.push(bytes.into())
} }
} }
if record { if record {
let contains = self.notes.contains_key(&tick); let contains = self.notes.contains_key(&pulse);
if contains { if contains {
self.notes.get_mut(&tick).unwrap().push(message.clone()); self.notes.get_mut(&pulse).unwrap().push(message.clone());
} else { } else {
self.notes.insert(tick, vec![message.clone()]); self.notes.insert(pulse, vec![message.clone()]);
} }
} }
}, } else if let midly::MidiMessage::NoteOff { key, vel: _ } = message {
midly::MidiMessage::NoteOff { key, vel: _ } => {
notes_on[key.as_int() as usize] = false; notes_on[key.as_int() as usize] = false;
if let Some(ref mut monitor) = monitor { if let Some(ref mut monitor) = monitor {
let t = event.time as usize; if monitor[time].is_none() {
if monitor[t].is_none() { monitor[time] = Some(vec![]);
monitor[t] = Some(vec![]);
} }
if let Some(Some(frame)) = monitor.get_mut(t) { if let Some(Some(frame)) = monitor.get_mut(time) {
frame.push(event.bytes.into()) frame.push(bytes.into())
} }
} }
if record { if record {
let contains = self.notes.contains_key(&tick); let contains = self.notes.contains_key(&pulse);
if contains { if contains {
self.notes.get_mut(&tick).unwrap().push(message.clone()); self.notes.get_mut(&pulse).unwrap().push(message.clone());
} else { } else {
self.notes.insert(tick, vec![message.clone()]); self.notes.insert(pulse, vec![message.clone()]);
}
} }
} }
},
_ => {}
},
_ => {}
} }
} }
} }

View file

@ -2,15 +2,15 @@ use crate::core::*;
use super::*; use super::*;
pub fn process (state: &mut Sequencer, _: &Client, scope: &ProcessScope) -> Control { pub fn process (state: &mut Sequencer, _: &Client, scope: &ProcessScope) -> Control {
// Get currently playing sequence // Get currently playing phrase
if state.sequence.is_none() { if state.sequence.is_none() {
return Control::Continue return Control::Continue
} }
let sequence = state.phrases.get_mut(state.sequence.unwrap()); let phrase = state.phrases.get_mut(state.sequence.unwrap());
if sequence.is_none() { if phrase.is_none() {
return Control::Continue return Control::Continue
} }
let sequence = sequence.unwrap(); let phrase = phrase.unwrap();
// Prepare output buffer and transport // Prepare output buffer and transport
let frame = scope.last_frame_time() as usize;//transport.pos.frame() as usize; let frame = scope.last_frame_time() as usize;//transport.pos.frame() as usize;
let frames = scope.n_frames() as usize; let frames = scope.n_frames() as usize;
@ -21,29 +21,28 @@ pub fn process (state: &mut Sequencer, _: &Client, scope: &ProcessScope) -> Cont
all_notes_off(&mut output); all_notes_off(&mut output);
} }
state.playing = transport.state; state.playing = transport.state;
// Play from sequence into output buffer // Play from phrase into output buffer
if state.playing == TransportState::Rolling { if state.playing == TransportState::Rolling {
sequence.chunk_out( phrase.chunk_out(
&mut output, &mut output,
frame, &mut state.notes_on,
frames,
&state.timebase, &state.timebase,
state.steps, frame,
state.resolution, frames
); );
} }
// Play from input to monitor, and record into sequence. // Play from input to monitor, and record into phrase.
let usecs = state.timebase.frame_to_usec(frame); //let usecs = state.timebase.frame_to_usec(frame);
let steps = usecs / state.timebase.usec_per_step(state.resolution as usize); //let steps = usecs / state.timebase.usec_per_step(state.time_zoom as usize);
let step = steps % state.steps; //let step = steps % state.steps;
let tick = (step * state.timebase.ppq() / state.resolution) as u32; //let tick = (step * state.timebase.ppq() / state.time_zoom) as u32;
sequence.chunk_in( phrase.chunk_in(
&state.midi_in, state.midi_in.iter(scope),
&scope, &mut state.notes_on,
Some(&mut output), Some(&mut output),
state.recording && state.playing == TransportState::Rolling, state.recording && state.playing == TransportState::Rolling,
&mut state.notes_on, &state.timebase,
tick frame,
); );
// Write to port from output buffer // Write to port from output buffer
// (containing notes from sequence and/or monitor) // (containing notes from sequence and/or monitor)

View file

@ -32,16 +32,16 @@ pub fn steps (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
let y = y - time0 + step / 2; let y = y - time0 + step / 2;
let step = step as usize; let step = step as usize;
//buf.set_string(x + 5, y, &" ".repeat(32.max(note1-note0)as usize), bg); //buf.set_string(x + 5, y, &" ".repeat(32.max(note1-note0)as usize), bg);
if step % s.resolution == 0 { if step % s.time_zoom == 0 {
buf.set_string(x + 2, y, &format!("{:2} ", step + 1), Style::default()); buf.set_string(x + 2, y, &format!("{:2} ", step + 1), Style::default());
} }
for k in note0..note1 { for k in note0..note1 {
let key = ::midly::num::u7::from_int_lossy(k as u8); let key = ::midly::num::u7::from_int_lossy(k as u8);
if step % 2 == 0 { if step % 2 == 0 {
let (a, b, c) = ( let (a, b, c) = (
(step + 0) as u32 * ppq / s.resolution as u32, (step + 0) as u32 * ppq / s.time_zoom as u32,
(step + 1) as u32 * ppq / s.resolution as u32, (step + 1) as u32 * ppq / s.time_zoom as u32,
(step + 2) as u32 * ppq / s.resolution as u32, (step + 2) as u32 * ppq / s.time_zoom as u32,
); );
let (character, style) = match ( let (character, style) = match (
contains_note_on(&s.phrases[s.sequence.unwrap()], key, a, b), contains_note_on(&s.phrases[s.sequence.unwrap()], key, a, b),
@ -55,7 +55,7 @@ pub fn steps (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
buf.set_string(x + 5 + k - note0, y, character, style); buf.set_string(x + 5 + k - note0, y, character, style);
} }
} }
if beat % s.steps == step as usize { if beat == step as usize {
buf.set_string(x + 4, y, if beat % 2 == 0 { "" } else { "" }, Style::default().yellow()); buf.set_string(x + 4, y, if beat % 2 == 0 { "" } else { "" }, Style::default().yellow());
for key in note0..note1 { for key in note0..note1 {
let _color = if s.notes_on[key as usize] { let _color = if s.notes_on[key as usize] {
@ -71,7 +71,7 @@ pub fn steps (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
pub fn footer (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16, height: u16) { pub fn footer (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16, height: u16) {
buf.set_string(x + 2, y + height + 1, format!( buf.set_string(x + 2, y + height + 1, format!(
"Q 1/{} | N {} ({}-{}) | T {} ({}-{})", "Q 1/{} | N {} ({}-{}) | T {} ({}-{})",
4 * s.resolution, 4 * s.time_zoom,
s.note_axis.0 + s.note_cursor, s.note_axis.0 + s.note_cursor,
s.note_axis.0, s.note_axis.0,
s.note_axis.1 - 1, s.note_axis.1 - 1,
@ -102,11 +102,11 @@ pub fn keys (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
} }
let mut color = KEY_STYLE[key as usize % 12]; let mut color = KEY_STYLE[key as usize % 12];
let mut is_on = s.notes_on[key as usize]; let mut is_on = s.notes_on[key as usize];
let step = beat % s.steps; let step = beat;
let (a, b, c) = ( let (a, b, c) = (
(step + 0) as u32 * ppq / s.resolution as u32, (step + 0) as u32 * ppq / s.time_zoom as u32,
(step + 1) as u32 * ppq / s.resolution as u32, (step + 1) as u32 * ppq / s.time_zoom as u32,
(step + 2) as u32 * ppq / s.resolution as u32, (step + 2) as u32 * ppq / s.time_zoom as u32,
); );
let key = ::midly::num::u7::from(key as u8); let key = ::midly::num::u7::from(key as u8);
is_on = is_on || contains_note_on(&s.phrases[s.sequence.unwrap()], key, a, b); is_on = is_on || contains_note_on(&s.phrases[s.sequence.unwrap()], key, a, b);

View file

@ -1,6 +1,13 @@
use crate::core::*; use crate::core::*;
use crate::layout::*; use crate::layout::*;
pub 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;
let (beats, ticks) = (tick / timebase.ppq(), tick % timebase.ppq());
let (bars, beats) = (beats / 4, beats % 4);
let timer = format!("{}.{}.{ticks:02}", bars + 1, beats + 1);
timer.blit(buf, x - timer.len() as u16, y, Some(Style::default().not_dim()));
}
pub fn draw_play_stop (buf: &mut Buffer, x: u16, y: u16, state: &TransportState) { pub fn draw_play_stop (buf: &mut Buffer, x: u16, y: u16, state: &TransportState) {
let style = Style::default().gray(); let style = Style::default().gray();
match state { match state {
@ -34,10 +41,25 @@ pub fn draw_mon (buf: &mut Buffer, x: u16, y: u16, on: bool) {
Style::default().bold().dim() Style::default().bold().dim()
})) }))
} }
pub fn draw_bpm (buf: &mut Buffer, x: u16, y: u16, bpm: usize) {
let style = Style::default().not_dim();
"BPM"
.blit(buf, x, y, Some(style));
format!("{:03}.{:03}", bpm / 1000, bpm % 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()));
}
pub struct Transport { pub struct Transport {
name: String, name: String,
/// Holds info about tempo /// Holds info about bpm
timebase: Arc<Timebase>, timebase: Arc<Timebase>,
transport: ::jack::Transport, transport: ::jack::Transport,
@ -51,7 +73,7 @@ impl Transport {
name: name.into(), name: name.into(),
timebase: Arc::new(Timebase { timebase: Arc::new(Timebase {
rate: AtomicUsize::new(client.sample_rate()), rate: AtomicUsize::new(client.sample_rate()),
tempo: AtomicUsize::new(113000), bpm: AtomicUsize::new(113000),
ppq: AtomicUsize::new(96), ppq: AtomicUsize::new(96),
}), }),
transport transport
@ -86,6 +108,10 @@ pub fn process (_: &mut Transport, _: &Client, _: &ProcessScope) -> Control {
Control::Continue Control::Continue
} }
pub fn handle (_: &mut Transport, _: &AppEvent) -> Usually<bool> {
Ok(false)
}
pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect) pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect)
-> Usually<Rect> -> Usually<Rect>
{ {
@ -102,8 +128,8 @@ pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect)
"REC", "REC",
"DUB", "DUB",
&format!("BPM {:03}.{:03}", &format!("BPM {:03}.{:03}",
state.timebase.tempo() / 1000, state.timebase.bpm() / 1000,
state.timebase.tempo() % 1000, state.timebase.bpm() % 1000,
), ),
"0.0+00", "0.0+00",
"0:00.000", "0:00.000",
@ -114,163 +140,4 @@ pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect)
x = x + 2; x = x + 2;
} }
Ok(area) Ok(area)
//buf.set_string(area.x, area.y + 5, "Witty Gerbil - Sha Na Na", label.bold());
//&format!(" │ 00:00.00 / 00:00.00"), style);
//draw_leaf(buf, area, 1, 0, "REC");
//draw_leaf(buf, area, 1, 5, "DUB");
//draw_leaf(buf, area, 1, 10, "STOP");
//draw_leaf(buf, area, 1, 16, "PLAY/PAUSE");
//draw_leaf(buf, area, 1, 28, "START");
//draw_leaf(buf, area, 1, 35, "Project: Witty Gerbil - Sha Na Na ");
//draw_leaf(buf, area, 3, 0, &format!("BPM {:03}.{:03}",
//state.bpm as u64,
//((state.bpm % 1.0) * 1000.0) as u64
//));
//let position = state.transport.as_ref().map(|t|t.query());
//if let Some(Ok(position)) = position {
//let rate = position.pos.frame_rate().unwrap();
//let frame = position.pos.frame();
//let second = (frame as f64) / (rate as f64);
//let minute = second / 60f64;
//let bpm = 120f64;
//let div = 4;
//let beats = minute * bpm;
//let bars = beats as u32 / div as u32;
//let beat = beats as u32 % div as u32 + 1;
//let beat_sub = beats % 1.0;
////buf.set_string(
////area.x - 18, area.y + area.height,
////format!("BBT {bars:04}:{beat:02}.{:02}", (beat_sub * 16.0) as u32),
////Style::default()
////);
//draw_leaf(buf, area, 3, 13, &format!("BBT {bars:04}:{beat:02}.{:02}",
//(beat_sub * 16.0) as u32
//));
//let time = frame as f64 / rate as f64;
//let seconds = time % 60.0;
//let msec = seconds % 1.0;
//let minutes = (time / 60.0) % 60.0;
//let hours = time / 3600.0;
//draw_leaf(buf, area, 3, 29, &format!("Time {:02}:{:02}:{:02}.{:03}",
//hours as u64,
//minutes as u64,
//seconds as u64,
//(msec * 1000.0) as u64
//));
//draw_leaf(buf, area, 3, 48, &format!("Rate {:>6}Hz", rate));
//draw_leaf(buf, area, 3, 63, &format!("Frame {:>10}", frame));
//}
//let bbt = position.pos.bbt().map(|mut bbt|*bbt
//.with_bpm(state.bpm)
//.with_timesig(state.timesig.0, state.timesig.1));
//.unwrap();
//Line::from("Project:").render(area, buf);
//if let Ok(position) = state.transport.query() {
//let frame = position.pos.frame();
//let rate = position.pos.frame_rate();
//let bbt = position.pos.bbt().map(|mut bbt|*bbt
//.with_bpm(state.bpm)
//.with_timesig(state.timesig.0, state.timesig.1));
//Line::from("Frame:").render(area.clone().offset(Offset { x: 0, y: 1 }), buf);
//Line::from(format!("{frame}")).render(area.clone().offset(Offset { x: 0, y: 2 }), buf);
//Line::from("Rate:").render(area.clone().offset(Offset { x: 10, y: 1 }), buf);
//Line::from(match rate {
//Some(rate) => format!("{rate}Hz"),
//None => String::from("(none)"),
//}).render(area.clone().offset(Offset { x: 10, y: 2 }), buf);
//Line::from("Time:").render(area.clone().offset(Offset { x: 20, y: 1 }), buf);
//Line::from(match rate {
//Some(rate) => format!("{:.03}", frame as f64 / rate as f64),
//None => String::from("(none)")
//}).render(area.clone().offset(Offset { x: 20, y: 2 }), buf);
//Line::from("BPM:").render(area.clone().offset(Offset { x: 30, y: 1 }), buf);
//Line::from(match bbt {
//Some(bbt) => format!("{:.01}", bbt.bpm),
//None => String::from("(none)")
//}).render(area.clone().offset(Offset { x: 30, y: 2 }), buf);
//Line::from("TimeSig:").render(area.clone().offset(Offset { x: 40, y: 1 }), buf);
//Line::from(match bbt {
//Some(bbt) => format!("{}/{}", bbt.sig_num, bbt.sig_denom),
//None => String::from("(none)")
//}).render(area.clone().offset(Offset { x: 40, y: 2 }), buf);
//Line::from("Beat:").render(area.clone().offset(Offset { x: 50, y: 1 }), buf);
//Line::from(match bbt {
//Some(bbt) => format!("{}.{}.{}", bbt.bar, bbt.beat, bbt.tick),
//None => String::from("(none)")
//}).render(area.clone().offset(Offset { x: 50, y: 2 }), buf);
//}
} }
//pub fn render (
//state: &mut Transport,
//stdout: &mut Stdout,
//mut offset: (u16, u16)
//) -> Result<(), Box<dyn Error>> {
//let move_to = |col, row| MoveTo(offset.0 + col, offset.1 + row);
//stdout.queue(move_to( 1, 0))?.queue(
//Print("Project: ")
//)?.queue(move_to(10, 0))?.queue(
//PrintStyledContent(state.title.clone().white().bold())
//)?;
//if let Ok(position) = state.transport.query() {
//let frame = position.pos.frame();
//let rate = position.pos.frame_rate();
//let bbt = position.pos.bbt().map(|mut bbt|*bbt
//.with_bpm(state.bpm)
//.with_timesig(state.timesig.0, state.timesig.1));
//stdout
//.queue(move_to( 1, 1))?.queue(Print("Frame: "))?
//.queue(move_to( 1, 2))?.queue(
//PrintStyledContent(
//format!("{frame}").white().bold(),
//))?
//.queue(move_to(11, 1))?.queue(Print("Rate: "))?
//.queue(move_to(11, 2))?.queue(
//PrintStyledContent(match rate {
//Some(rate) => format!("{rate}Hz"),
//None => String::from("(none)"),
//}.white().bold())
//)?
//.queue(move_to(20, 1))?.queue(Print("Time: "))?
//.queue(move_to(20, 2))?.queue(
//PrintStyledContent(match rate {
//Some(rate) => format!("{:.03}", frame as f64 / rate as f64),
//None => String::from("(none)")
//}.white().bold())
//)?
//.queue(move_to(30, 1))?.queue(Print("BPM: "))?
//.queue(move_to(30, 2))?.queue(
//PrintStyledContent(match bbt {
//Some(bbt) => format!("{:.01}", bbt.bpm),
//None => String::from("(none)")
//}.white().bold())
//)?
//.queue(move_to(39, 1))?.queue(Print("Timesig: "))?
//.queue(move_to(39, 2))?.queue(
//PrintStyledContent(match bbt {
//Some(bbt) => format!("{}/{}", bbt.sig_num, bbt.sig_denom),
//None => String::from("(none)")
//}.white().bold())
//)?
//.queue(move_to(50, 1))?.queue(Print("Beat: "))?
//.queue(move_to(50, 2))?.queue(
//PrintStyledContent(match bbt {
//Some(bbt) => format!("{}.{}.{}", bbt.bar, bbt.beat, bbt.tick),
//None => String::from("(none)")
//}.white().bold())
//)?;
//}
//Ok(())
//}
pub fn handle (_: &mut Transport, _: &AppEvent) -> Usually<bool> {
Ok(false)
}
pub const ACTIONS: [(&'static str, &'static str);4] = [
("?", "Toggle help"),
("(Shift-)Tab", "Switch pane"),
("Arrows", "Navigate"),
("(Shift-)Space", "⯈ Play/pause"),
];

View file

@ -45,7 +45,7 @@ fn main () -> Result<(), Box<dyn Error>> {
let (client, _) = Client::new("init", ClientOptions::NO_START_SERVER)?; let (client, _) = Client::new("init", ClientOptions::NO_START_SERVER)?;
let timebase = Arc::new(Timebase { let timebase = Arc::new(Timebase {
rate: AtomicUsize::new(client.sample_rate()), rate: AtomicUsize::new(client.sample_rate()),
tempo: AtomicUsize::new(150000), bpm: AtomicUsize::new(99000),
ppq: AtomicUsize::new(96), ppq: AtomicUsize::new(96),
}); });
let ppq = timebase.ppq() as u32; let ppq = timebase.ppq() as u32;
@ -54,84 +54,67 @@ fn main () -> Result<(), Box<dyn Error>> {
( $t1 * ppq / 4, vec![ $($msg),* ] ) ( $t1 * ppq / 4, vec![ $($msg),* ] )
} }
} }
macro_rules! phrase {
($($t:expr => $msg:expr),* $(,)?) => {{
let mut phrase = BTreeMap::new();
$(phrase.insert($t, vec![]);)*
$(phrase.get_mut(&$t).unwrap().push($msg);)*
phrase
}}
}
let app = Launcher::new("Launcher#0", &timebase, let app = Launcher::new("Launcher#0", &timebase,
Some(vec![ Some(vec![
Track::new("Drums", &timebase, Some(vec![ Track::new("Drums", &timebase, Some(vec![
//Plugin::lv2("Kick/ChowKick", "file:///home/user/.lv2/ChowKick.lv2", &[1, 1, 0, 2])?.boxed(),
Sampler::new("Sampler", Some(BTreeMap::from([ Sampler::new("Sampler", Some(BTreeMap::from([
sample!(36, "Kick", "/home/user/Lab/Music/pak/kik.wav"), sample!(36, "Kick", "/home/user/Lab/Music/pak/kik.wav"),
sample!(40, "Snare", "/home/user/Lab/Music/pak/sna.wav"), sample!(40, "Snare", "/home/user/Lab/Music/pak/sna.wav"),
sample!(44, "Hihat", "/home/user/Lab/Music/pak/chh.wav"), sample!(44, "Hihat", "/home/user/Lab/Music/pak/chh.wav"),
])))?.boxed(), ])))?.boxed(),
Plugin::lv2("Panagement", "file:///home/user/.lv2/Auburn Sounds Panagement 2.lv2")?.boxed(),
]), Some(vec![ ]), Some(vec![
Phrase::new("KSH", ppq * 4, Some(BTreeMap::from([
play!(0 => [ Phrase::new("KSH", ppq * 4, Some(phrase! {
MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, 00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, 00 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
] ), 01 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
play!(1 => [ 02 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, 04 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
]), 04 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
play!(2 => [ 06 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, 08 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
]), 10 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
play!(4 => [ 10 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, 11 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, 12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
]), 12 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
play!(6 => [ 14 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, 14 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }
]), })),
play!(8 => [
MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, Phrase::new("4K", ppq * 4, Some(phrase! {
]), 00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
play!(10 => [ 04 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, 08 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, 12 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
]), })),
play!(12 => [
MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, Phrase::new("KS", ppq * 4, Some(phrase! {
MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, 00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
]), 04 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
play!(14 => [ 10 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, 12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, })),
]),
]))),
Phrase::new("4K", ppq * 4, Some(BTreeMap::from([
play!(0 => [
MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
] ),
play!(4 => [
MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
]),
play!(8 => [
MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
]),
play!(12 => [
MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
]),
]))),
Phrase::new("KS", ppq * 4, Some(BTreeMap::from([
play!(0 => [
MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
] ),
play!(4 => [
MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
]),
play!(10 => [
MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
]),
play!(12 => [
MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
]),
]))),
]))?, ]))?,
Track::new("Odin2", &timebase, Some(vec![ Track::new("Odin2", &timebase, Some(vec![
//Plugin::lv2("Helm", "file:///home/user/.lv2/Helm.lv2", &[1, 0, 0, 2])?.boxed(),
Plugin::lv2("Odin2", "file:///home/user/.lv2/Odin2.lv2", &[1, 0, 0, 2])?.boxed(), Plugin::lv2("Odin2", "file:///home/user/.lv2/Odin2.lv2")?.boxed(),
]), Some(vec![ ]), Some(vec![
Phrase::new("E G A Bb", ppq * 4, Some(BTreeMap::from([ Phrase::new("E G A Bb", ppq * 4, Some(BTreeMap::from([
play!(2 => [ play!(2 => [
@ -197,6 +180,7 @@ fn main () -> Result<(), Box<dyn Error>> {
]))) ])))
]))?, ]))?,
//Plugin::lv2("Helm", "file:///home/user/.lv2/Helm.lv2", &[1, 0, 0, 2])?.boxed(),
//Plugin::lv2("Kick/ChowKick", "file:///home/user/.lv2/ChowKick.lv2", &[1, 1, 0, 2])?.boxed(), //Plugin::lv2("Kick/ChowKick", "file:///home/user/.lv2/ChowKick.lv2", &[1, 1, 0, 2])?.boxed(),
//Plugin::lv2("Bass/Helm", "file:///home/user/.lv2/Helm.lv2", &[1, 0, 0, 2])?.boxed(), //Plugin::lv2("Bass/Helm", "file:///home/user/.lv2/Helm.lv2", &[1, 0, 0, 2])?.boxed(),
//Plugin::lv2("Pads/Odin2", "file:///home/user/.lv2/Odin2.lv2", &[1, 0, 0, 2])?.boxed(), //Plugin::lv2("Pads/Odin2", "file:///home/user/.lv2/Odin2.lv2", &[1, 0, 0, 2])?.boxed(),