mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
wip: long awaited fixes to main sequencer
This commit is contained in:
parent
2837ffff4a
commit
78e5469b32
17 changed files with 391 additions and 563 deletions
|
|
@ -16,5 +16,7 @@
|
||||||
# for ChowKick.lv2:
|
# for ChowKick.lv2:
|
||||||
freetype
|
freetype
|
||||||
libgcc.lib
|
libgcc.lib
|
||||||
|
# for Panagement
|
||||||
|
xorg.libX11
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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?
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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()));
|
||||||
|
|
|
||||||
|
|
@ -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()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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"),
|
|
||||||
];
|
|
||||||
|
|
|
||||||
116
src/main.rs
116
src/main.rs
|
|
@ -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(),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue