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:
freetype
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::collections::BTreeMap;
pub use std::sync::{
Arc,
Mutex, MutexGuard,
atomic::{
Ordering,
AtomicBool,
AtomicUsize
},
Arc, Mutex, MutexGuard,
atomic::{Ordering, AtomicBool, AtomicUsize},
mpsc::{self, channel, Sender, Receiver}
};
@ -33,21 +28,8 @@ pub use ::crossterm::{
},
};
pub use ::ratatui::{
prelude::{
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 ::ratatui::prelude::*;
pub use ::midly::{MidiMessage, live::LiveEvent, num::u7};
pub use crate::{key, keymap};
/// 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) {
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) {
Render::render(self, buf, area).expect("Failed to render device.");
}

View file

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

View file

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

View file

@ -1,28 +1,41 @@
use crate::core::*;
use super::*;
pub fn plug (uri: &str) -> Usually<PluginKind> {
let world = ::livi::World::with_load_bundle(&uri);
let features = world.build_features(::livi::FeaturesBuilder {
min_block_length: 1,
max_block_length: 65536,
});
let mut plugin = None;
for p in world.iter_plugins() {
plugin = Some(p);
break
}
let plugin = plugin.unwrap();
let mut port_list = vec![];
for port in plugin.ports() {
port_list.push(port);
}
Ok(PluginKind::LV2 {
instance: unsafe {
plugin.instantiate(features.clone(), 48000.0).expect("boop")
},
port_list,
features,
world,
})
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 features = ::livi::FeaturesBuilder { min_block_length: 1, max_block_length: 65536 };
let features = world.build_features(features);
let mut plugin = None;
for p in world.iter_plugins() {
plugin = Some(p);
break
}
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![];
for port in plugin.ports() {
port_list.push(port);
}
port_list
},
plugin,
features,
})
}
}

View file

@ -1,22 +1,16 @@
use crate::core::*;
use super::*;
pub struct LauncherGridView<'a> {
pub struct LauncherGrid<'a> {
state: &'a Launcher,
buf: &'a mut Buffer,
area: Rect,
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 {
let separator = format!("{}", "-".repeat((area.width - 2).into()));
Self { state, buf, area, separator, focused }
Self { state, buf, area, 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);
self.area.height = self.state.scenes.len() as u16 + 2;
let style = Some(Style::default().green().dim());
if self.focused {
@ -24,9 +18,7 @@ impl<'a> LauncherGridView<'a> {
lozenge_left(self.buf, x, y, height, style);
lozenge_right(self.buf, x + width - 1, y, height, style);
}
let columns = self.column_names();
let mut x = self.area.x;
for (i, w) in self.column_widths().iter().enumerate() {
if x >= self.area.x + self.area.width {
@ -36,7 +28,6 @@ impl<'a> LauncherGridView<'a> {
x = x + w;
self.separator_v(x, i == self.state.cursor.0);
}
let (mut x, y) = (self.area.x, self.area.y);
for (i, title) in columns.iter().enumerate() {
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;
x = x + w;
}
"Add track…".blit(self.buf, x + 2, y, Some(Style::default().dim()));
Ok(self.area)
}
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() {
columns.push(track.name.as_str());
column_names.push(track.name.as_str());
}
columns
column_names
}
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) {
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 {
"".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) {
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() {
// Launch scene
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;
}
}
if state.playing == TransportState::Stopped {
state.transport.start()?;
state.playing = TransportState::Starting;
}
} else if let Some((track_id, track)) = state.track() {
// Rename track?
}

View file

@ -131,10 +131,10 @@ impl Launcher {
} }
}
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>> {
self.track().map(|t|t.1.chain.state())
Some(self.track()?.1.chain.state())
}
fn phrase_id (&self) -> Option<usize> {
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_mon(buf, x + 19, y, state.monitoring);
crate::device::transport::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);
crate::device::transport::draw_bpm(buf, x + 33, y, state.timebase.bpm());
crate::device::transport::draw_timer(buf, x + width - 1, y, &state.timebase, state.position);
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()
).draw()?.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)
}
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> {
let Rect { x, y, width, height } = area;
let style = Some(Style::default().green().dim());
@ -239,50 +217,46 @@ fn draw_section_sequencer (state: &Launcher, buf: &mut Buffer, area: Rect) -> Us
},
_ => {},
};
if let Some((_, track)) = state.track() {
let frame = state.position;
let timebase = &state.timebase;
let tick = (frame as f64 / timebase.frames_per_tick()) as usize;
let sequencer = track.sequencer.state();
let zoom = sequencer.resolution;
let steps = if let Some(_phrase) = sequencer.phrase() { 0 } else { 0 } / 4; // TODO
crate::device::sequencer::horizontal::timer(buf, x+5, y,
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 },
sequencer.note_axis.1
)?;
if let Some(id) = state.phrase_id() {
if let Some(phrase) = sequencer.phrases.get(id) {
crate::device::sequencer::horizontal::lanes(buf, x, y + 1,
&phrase,
sequencer.timebase.ppq() as u32,
sequencer.resolution as u32,
sequencer.time_axis.0 as u32,
sequencer.time_axis.1 as u32,
sequencer.note_axis.0 as u32,
sequencer.note_axis.1 as u32,
);
}
let track = state.track();
if track.is_none() {
return Ok(area);
}
let track = track.unwrap().1;
let sequencer = track.sequencer.state();
crate::device::sequencer::horizontal::timer(
buf, x+5, y,
sequencer.time_axis.0,
sequencer.time_axis.0 + area.width,
0
);
crate::device::sequencer::horizontal::keys(
buf, Rect { x, y: y + 1, width, height },
sequencer.note_axis.1
)?;
if let Some(id) = state.phrase_id() {
if let Some(phrase) = sequencer.phrases.get(id) {
crate::device::sequencer::horizontal::lanes(
buf, x, y + 1,
&phrase,
sequencer.timebase.ppq() as u32,
sequencer.time_zoom as u32,
sequencer.time_axis.0 as u32,
sequencer.time_axis.0 as u32 + area.width as u32,
sequencer.note_axis.0 as u32,
sequencer.note_axis.1 as u32,
);
}
let cursor_style = match view {
LauncherView::Sequencer => Style::default().green().not_dim(),
_ => Style::default().green().dim(),
};
crate::device::sequencer::horizontal::cursor(buf, x, y + 1, cursor_style,
sequencer.time_cursor,
sequencer.note_cursor
);
}
let cursor_style = match view {
LauncherView::Sequencer => Style::default().green().not_dim(),
_ => Style::default().green().dim(),
};
crate::device::sequencer::horizontal::cursor(buf, x, y + 1, cursor_style,
sequencer.time_cursor,
sequencer.note_cursor
);
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> {
let style = Some(Style::default().green().dim());
match state.view {

View file

@ -51,8 +51,8 @@ fn note_add (s: &mut Sequencer) -> Usually<bool> {
return Ok(false)
}
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 start = (step as usize * s.timebase.ppq() / s.time_zoom) 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 note_on = MidiMessage::NoteOn { 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)
}
fn quantize_next (s: &mut Sequencer) -> Usually<bool> {
if s.resolution < 64 {
s.resolution = s.resolution * 2;
if s.time_zoom < 64 {
s.time_zoom = s.time_zoom * 2;
}
Ok(true)
}
fn quantize_prev (s: &mut Sequencer) -> Usually<bool> {
if s.resolution > 1 {
s.resolution = s.resolution / 2;
if s.time_zoom > 1 {
s.time_zoom = s.time_zoom / 2;
}
Ok(true)
}

View file

@ -10,13 +10,13 @@ pub fn draw (
area.x = area.x + 13;
let Rect { x, y, width, .. } = area;
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;
if let Some(phrase) = s.phrase() {
lanes(buf, x, y,
phrase,
s.timebase.ppq() as u32,
s.resolution as u32,
s.time_zoom as u32,
s.time_axis.0 as u32,
s.time_axis.1 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 {
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()
} else {
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 [
["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!("{} ({}-{})",
s.note_axis.0 + s.note_cursor,
s.note_axis.0,

View file

@ -19,9 +19,6 @@ pub struct Sequencer {
pub midi_out: Port<MidiOut>,
/// Holds info about tempo
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
pub sequence: Option<usize>,
/// Map: tick -> MIDI events at tick
@ -42,9 +39,8 @@ pub struct Sequencer {
pub note_axis: (u16, u16),
/// Position of cursor within note range
pub note_cursor: u16,
/// Sequencer resolution, e.g. 16 steps per beat.
/// FIXME: grid in ppm will simplify calculations
pub resolution: usize,
/// PPM per display unit
pub time_zoom: usize,
/// Range of time steps to display
pub time_axis: (u16, u16),
/// Position of cursor within time range
@ -68,8 +64,6 @@ impl Sequencer {
midi_out: client.register_port("out", MidiOut::default())?,
timebase: timebase.clone(),
steps: 16,
resolution: 4,
sequence: Some(0),
phrases: phrases.unwrap_or(vec![
Phrase::new("Phrase0", 4*timebase.ppq() as u32, None)
@ -85,13 +79,14 @@ impl Sequencer {
mode: SequencerView::Horizontal,
note_axis: (36, 68),
note_cursor: 0,
time_zoom: 24,
time_axis: (0, 64),
time_cursor: 0,
}).activate(client)
}
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 frame = pos.frame();
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 header = draw_header(s, buf, area, steps)?;
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> {
let Rect { x, y, width, .. } = area;
let rep = beat / s.steps;
let step = beat % s.steps;
let reps = s.steps / s.resolution;
let steps = s.steps % s.resolution;
draw_timer(buf, x + width - 2, y + 1, &format!("{rep}.{step:02} / {reps}.{steps}"));
let Rect { x, y, .. } = area;
//let rep = beat / s.steps;
//let step = beat % s.steps;
//let reps = s.steps / s.time_zoom;
//let steps = s.steps % s.time_zoom;
//draw_timer(buf, x + width - 2, y + 1, &format!("{rep}.{step:02} / {reps}.{steps}"));
let style = Style::default().gray();
crate::device::transport::draw_play_stop(buf, x + 2, y + 1, &s.playing);
let separator = format!("{}", "-".repeat((area.width - 2).into()));

View file

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

View file

@ -2,15 +2,15 @@ use crate::core::*;
use super::*;
pub fn process (state: &mut Sequencer, _: &Client, scope: &ProcessScope) -> Control {
// Get currently playing sequence
// Get currently playing phrase
if state.sequence.is_none() {
return Control::Continue
}
let sequence = state.phrases.get_mut(state.sequence.unwrap());
if sequence.is_none() {
let phrase = state.phrases.get_mut(state.sequence.unwrap());
if phrase.is_none() {
return Control::Continue
}
let sequence = sequence.unwrap();
let phrase = phrase.unwrap();
// Prepare output buffer and transport
let frame = scope.last_frame_time() as usize;//transport.pos.frame() 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);
}
state.playing = transport.state;
// Play from sequence into output buffer
// Play from phrase into output buffer
if state.playing == TransportState::Rolling {
sequence.chunk_out(
phrase.chunk_out(
&mut output,
frame,
frames,
&mut state.notes_on,
&state.timebase,
state.steps,
state.resolution,
frame,
frames
);
}
// Play from input to monitor, and record into sequence.
let usecs = state.timebase.frame_to_usec(frame);
let steps = usecs / state.timebase.usec_per_step(state.resolution as usize);
let step = steps % state.steps;
let tick = (step * state.timebase.ppq() / state.resolution) as u32;
sequence.chunk_in(
&state.midi_in,
&scope,
// Play from input to monitor, and record into phrase.
//let usecs = state.timebase.frame_to_usec(frame);
//let steps = usecs / state.timebase.usec_per_step(state.time_zoom as usize);
//let step = steps % state.steps;
//let tick = (step * state.timebase.ppq() / state.time_zoom) as u32;
phrase.chunk_in(
state.midi_in.iter(scope),
&mut state.notes_on,
Some(&mut output),
state.recording && state.playing == TransportState::Rolling,
&mut state.notes_on,
tick
&state.timebase,
frame,
);
// Write to port from output buffer
// (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 step = step as usize;
//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());
}
for k in note0..note1 {
let key = ::midly::num::u7::from_int_lossy(k as u8);
if step % 2 == 0 {
let (a, b, c) = (
(step + 0) as u32 * ppq / s.resolution as u32,
(step + 1) as u32 * ppq / s.resolution as u32,
(step + 2) as u32 * ppq / s.resolution as u32,
(step + 0) as u32 * ppq / s.time_zoom as u32,
(step + 1) as u32 * ppq / s.time_zoom as u32,
(step + 2) as u32 * ppq / s.time_zoom as u32,
);
let (character, style) = match (
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);
}
}
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());
for key in note0..note1 {
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) {
buf.set_string(x + 2, y + height + 1, format!(
"Q 1/{} | N {} ({}-{}) | T {} ({}-{})",
4 * s.resolution,
4 * s.time_zoom,
s.note_axis.0 + s.note_cursor,
s.note_axis.0,
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 is_on = s.notes_on[key as usize];
let step = beat % s.steps;
let step = beat;
let (a, b, c) = (
(step + 0) as u32 * ppq / s.resolution as u32,
(step + 1) as u32 * ppq / s.resolution as u32,
(step + 2) as u32 * ppq / s.resolution as u32,
(step + 0) as u32 * ppq / s.time_zoom as u32,
(step + 1) as u32 * ppq / s.time_zoom as u32,
(step + 2) as u32 * ppq / s.time_zoom as u32,
);
let key = ::midly::num::u7::from(key as u8);
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::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) {
let style = Style::default().gray();
match state {
@ -34,10 +41,25 @@ pub fn draw_mon (buf: &mut Buffer, x: u16, y: u16, on: bool) {
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 {
name: String,
/// Holds info about tempo
/// Holds info about bpm
timebase: Arc<Timebase>,
transport: ::jack::Transport,
@ -51,7 +73,7 @@ impl Transport {
name: name.into(),
timebase: Arc::new(Timebase {
rate: AtomicUsize::new(client.sample_rate()),
tempo: AtomicUsize::new(113000),
bpm: AtomicUsize::new(113000),
ppq: AtomicUsize::new(96),
}),
transport
@ -86,6 +108,10 @@ pub fn process (_: &mut Transport, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
pub fn handle (_: &mut Transport, _: &AppEvent) -> Usually<bool> {
Ok(false)
}
pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect)
-> Usually<Rect>
{
@ -102,8 +128,8 @@ pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect)
"REC",
"DUB",
&format!("BPM {:03}.{:03}",
state.timebase.tempo() / 1000,
state.timebase.tempo() % 1000,
state.timebase.bpm() / 1000,
state.timebase.bpm() % 1000,
),
"0.0+00",
"0:00.000",
@ -114,163 +140,4 @@ pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect)
x = x + 2;
}
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

@ -44,9 +44,9 @@ fn main () -> Result<(), Box<dyn Error>> {
let output = ["Komplete.*:playback_FL", "Komplete.*:playback_FR"];
let (client, _) = Client::new("init", ClientOptions::NO_START_SERVER)?;
let timebase = Arc::new(Timebase {
rate: AtomicUsize::new(client.sample_rate()),
tempo: AtomicUsize::new(150000),
ppq: AtomicUsize::new(96),
rate: AtomicUsize::new(client.sample_rate()),
bpm: AtomicUsize::new(99000),
ppq: AtomicUsize::new(96),
});
let ppq = timebase.ppq() as u32;
macro_rules! play {
@ -54,84 +54,67 @@ fn main () -> Result<(), Box<dyn Error>> {
( $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,
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([
sample!(36, "Kick", "/home/user/Lab/Music/pak/kik.wav"),
sample!(40, "Snare", "/home/user/Lab/Music/pak/sna.wav"),
sample!(44, "Hihat", "/home/user/Lab/Music/pak/chh.wav"),
])))?.boxed(),
Plugin::lv2("Panagement", "file:///home/user/.lv2/Auburn Sounds Panagement 2.lv2")?.boxed(),
]), Some(vec![
Phrase::new("KSH", ppq * 4, Some(BTreeMap::from([
play!(0 => [
MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
] ),
play!(1 => [
MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
]),
play!(2 => [
MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
]),
play!(4 => [
MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
]),
play!(6 => [
MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
]),
play!(8 => [
MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
]),
play!(10 => [
MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
]),
play!(12 => [
MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
]),
play!(14 => [
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() },
]),
]))),
Phrase::new("KSH", ppq * 4, Some(phrase! {
00 * ppq/4 => MidiMessage::NoteOn { key: 36.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() },
02 * ppq/4 => 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() },
06 * ppq/4 => 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() },
10 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
11 * ppq/4 => 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() },
14 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
14 * ppq/4 => 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() },
04 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
08 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
12 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
})),
Phrase::new("KS", ppq * 4, Some(phrase! {
00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
04 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
10 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
})),
]))?,
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![
Phrase::new("E G A Bb", ppq * 4, Some(BTreeMap::from([
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("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(),