big ass refactor (rip client)

This commit is contained in:
🪞👃🪞 2024-07-03 14:51:48 +03:00
parent 94c1f83ef2
commit 8c3cf53c67
56 changed files with 2232 additions and 1891 deletions

View file

@ -1,287 +0,0 @@
mod plugin; pub use self::plugin::*;
mod sampler; pub use self::sampler::*;
use crate::core::*;
use crate::layout::*;
pub struct Chain {
pub name: String,
pub focused: bool,
pub focus: usize,
pub items: Vec<Box<dyn Device>>,
pub view: ChainView,
pub adding: bool,
}
pub enum ChainView {
Hidden,
Compact,
Row,
Column,
}
impl Chain {
pub fn new (name: &str, items: Option<Vec<Box<dyn Device>>>) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
Ok(DynamicDevice::new(render, handle, process, Self {
name: name.into(),
focused: false,
focus: 0,
items: items.unwrap_or_else(||vec![]),
view: ChainView::Column,
adding: false
}))
}
}
impl PortList for Chain {
fn midi_ins (&self) -> Usually<Vec<String>> {
if let Some(device) = self.items.get(0) {
device.midi_ins()
} else {
Ok(vec![])
}
}
fn audio_outs (&self) -> Usually<Vec<String>> {
if let Some(device) = self.items.get(self.items.len().saturating_sub(1)) {
device.audio_outs()
} else {
Ok(vec![])
}
}
}
pub fn process (_: &mut Chain, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
pub fn render (state: &Chain, buf: &mut Buffer, area: Rect)
-> Usually<Rect>
{
let Rect { x, y, .. } = area;
let selected = Some(if state.focused {
Style::default().green().not_dim()
} else {
Style::default().green().dim()
});
let result = match state.view {
ChainView::Hidden => Rect { x, y, width: 0, height: 0 },
ChainView::Compact => {
let area = Rect { x, y, width: (state.name.len() + 4) as u16, height: 3 };
buf.set_string(area.x + 2, area.y + 1, &state.name, Style::default());
draw_box_styled(buf, area, selected)
},
ChainView::Row => {
draw_box_styled(buf, area, selected);
let (area, _) = Row::draw(buf, area, &state.items, 0)?;
area
},
ChainView::Column => {
draw_as_column(state, buf, area, selected)?
},
};
Ok(result)
}
pub fn draw_as_row (
state: &Chain, buf: &mut Buffer, area: Rect, _: Option<Style>
) -> Usually<(Rect, Vec<Rect>)> {
let Rect { mut x, y, width, height } = area;
let mut h = 0u16;
let mut frames = vec![];
for (i, device) in state.items.iter().enumerate() {
let mut x2 = 0u16;
let mut y2 = 1u16;
//for port in device.midi_ins()?.iter() {
//port.blit(buf, x, y + y2, Some(Style::default()));
//x2 = x2.max(port.len() as u16);
//y2 = y2 + 1;
//}
//for port in device.audio_ins()?.iter() {
//port.blit(buf, x, y + y2, Some(Style::default()));
//x2 = x2.max(port.len() as u16);
//y2 = y2 + 1;
//}
let width = width.saturating_sub(x).saturating_sub(x2);
let style = Some(if i == state.focus {
if state.focused {
Style::default().green().not_dim()
} else {
Style::default().green().dim()
}
} else {
Style::default().dim()
});
lozenge_left(buf, x + x2, y, height, style);
let frame = device.render(buf, Rect { x: x + x2, y, width, height })?;
lozenge_right(buf, x + x2 + frame.width - 1, y, height, style);
//let mut y2 = 1u16;
//for port in device.midi_outs()?.iter() {
//port.blit(buf, x + x2 + frame.width, y + y2, Some(Style::default()));
//x2 = x2.max(port.len() as u16);
//y2 = y2 + 1;
//}
//for port in device.audio_outs()?.iter() {
//port.blit(buf, x + x2 + frame.width, y + y2, Some(Style::default()));
//x2 = x2.max(port.len() as u16);
//y2 = y2 + 1;
//}
frames.push(frame);
h = h.max(frame.height);
x = x + frame.width;
}
Ok((area, frames))
}
pub fn draw_as_column (
state: &Chain, buf: &mut Buffer, area: Rect, selected: Option<Style>
) -> Usually<Rect> {
//let area = Rect { x, y, width: 40, height: 30 };
//let mut y = area.y;
//buf.set_string(area.x, y, "│", Style::default().black());
//buf.set_string(area.x + area.width - 1, y, "│", Style::default().black());
//buf.set_string(area.x + 2, y, "Input...", Style::default().dim());
//let mut x = 0u16;
//for (i, device) in state.items.iter().enumerate() {
//let result = device.render(buf, Rect {
//x: area.x,
//y,
//width: area.width,
//height: 21//area.height.saturating_sub(y)
//})?;
//if i == state.focus {
//if state.focused {
//draw_box_styled(buf, result, Some(Style::default().green().not_dim()))
//} else {
//draw_box_styled_dotted(buf, result, Some(Style::default().green().dim()))
//};
//};
////let result = Rect { x: 0, y: 0, width: result.width, height: 21 };
//x = x.max(result.width);
//y = y + result.height;
////y = y + 1;
//buf.set_string(area.x, y, "│", Style::default().black());
//buf.set_string(area.x + area.width - 1, y, "│", Style::default().black());
//buf.set_string(area.x + 2, y, " Patch in ┐ │ └ Patch out", Style::default().dim());
//y = y + 1;
////buf.set_string(area.x, y, format!("{y}---BOT---"), Style::default().red());
////buf.set_string(area.x + area.width - 1, area.y + 1, "│", Style::default().black());
////buf.set_string(area.x + 2, y, "Patch...", Style::default().dim());
//}
//Ok(draw_box(buf, Rect {
//x: area.x,
//y: area.y,
//width: area.width,
//height: y - area.y,
//}))
let Rect { x, mut y, width, height } = area;
//let (area, areas) = Column::draw(buf, area, &state.items, 0)?;
let mut w = 0u16;
let mut frames = vec![];
for device in state.items.iter() {
let style_midi = Style::default().black().bold().on_green();
let style_audio = Style::default().black().bold().on_red();
let midi_ins = device.midi_ins()?;
let midi_outs = device.midi_outs()?;
let audio_ins = device.audio_ins()?;
let audio_outs = device.audio_outs()?;
y = y + midi_ins.len() as u16;
let frame = device.render(buf, Rect {
x, y, width, height: height.saturating_sub(y)
})?;
frames.push(frame);
w = w.max(frame.width);
y = y - midi_ins.len() as u16;
for port in midi_ins.iter() {
buf.set_string(x + frame.width - 10, y, &format!(" <i> MIDI {port} "), style_midi);
y = y + 1;
}
y = y - audio_ins.len() as u16;
for port in audio_ins.iter() {
buf.set_string(x + frame.width - 10, y, &format!(" <i> MIDI {port} "), style_audio);
y = y + 1;
}
y = y + frame.height - 1;
y = y + midi_outs.len() as u16;
for port in midi_outs.iter() {
buf.set_string(x + 2, y, &format!(" <o> MIDI {port} "), style_midi);
y = y + 1;
}
y = y + audio_outs.len() as u16;
for port in audio_outs.iter() {
buf.set_string(x + 2, y, &format!(" <o> Audio {port} "), style_audio);
y = y + 1;
}
}
draw_box_styled(buf, frames[state.focus], selected);
Ok(area)
}
impl Focus for Chain {
fn unfocus (&mut self) {
self.focused = false
}
fn focused (&self) -> Option<&Box<dyn Device>> {
match self.focused {
true => self.items.get(self.focus),
false => None
}
}
fn focused_mut (&mut self) -> Option<&mut Box<dyn Device>> {
match self.focused {
true => self.items.get_mut(self.focus),
false => None
}
}
fn handle_focus (&mut self, event: &FocusEvent) -> Usually<bool> {
Ok(match event {
FocusEvent::Backward => {
if self.focus == 0 {
self.focus = self.items.len();
}
self.focus = self.focus - 1;
true
},
FocusEvent::Forward => {
self.focus = self.focus + 1;
if self.focus >= self.items.len() {
self.focus = 0;
}
true
},
FocusEvent::Inward => {
self.focused = true;
self.items[self.focus].handle(&AppEvent::Focus)?;
true
},
FocusEvent::Outward => {
if self.focused {
self.focused = false;
self.items[self.focus].handle(&AppEvent::Blur)?;
true
} else {
false
}
},
})
}
}
pub fn handle (state: &mut Chain, event: &AppEvent) -> Usually<bool> {
Ok(handle_focus(state, event, keymap!(Chain {
[Up, NONE, "focus_up", "focus row above",
|s: &mut Chain|s.handle_focus(&FocusEvent::Backward)],
[Down, NONE, "focus_down", "focus row below",
|s: &mut Chain|s.handle_focus(&FocusEvent::Forward)],
[Enter, NONE, "focus_down", "focus row below",
|s: &mut Chain|s.handle_focus(&FocusEvent::Inward)],
[Esc, NONE, "focus_down", "focus row below",
|s: &mut Chain|s.handle_focus(&FocusEvent::Outward)],
}))? || handle_keymap(state, event, keymap!(Chain {
[Char('a'), NONE, "add_device", "add a device", add_device]
}))?)
}
fn add_device (state: &mut Chain) -> Usually<bool> {
state.adding = true;
Ok(true)
}

View file

@ -1,281 +0,0 @@
use crate::core::*;
mod lv2; pub use lv2::*;
mod vst2;
mod vst3;
pub struct Plugin {
name: String,
path: Option<String>,
plugin: Option<PluginKind>,
selected: usize,
mapping: bool,
midi_ins: Vec<Port<MidiIn>>,
midi_outs: Vec<Port<MidiOut>>,
audio_ins: Vec<Port<AudioIn>>,
audio_outs: Vec<Port<AudioOut>>,
}
enum PluginKind {
LV2(LV2Plugin),
VST2 {
instance: ::vst::host::PluginInstance
},
VST3,
}
const HELM: &'static str = "file:///nix/store/ij3sz7nqg5l7v2dygdvzy3w6cj62bd6r-helm-0.9.0/lib/lv2/helm.lv2";
impl Plugin {
/// Load a LV2 plugin.
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(host)
}
pub fn new (name: &str) -> Usually<DynamicDevice<Self>> {
DynamicDevice::new(render, handle, Self::process, Self {
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(LV2Plugin { features, ref mut instance, .. })) => {
let urid = features.midi_urid();
let mut inputs = vec![];
for port in self.midi_ins.iter() {
let mut atom = ::livi::event::LV2AtomSequence::new(
&features,
scope.n_frames() as usize
);
for event in port.iter(scope) {
match event.bytes.len() {
3 => atom.push_midi_event::<3>(
event.time as i64,
urid,
&event.bytes[0..3]
).unwrap(),
_ => {}
}
}
inputs.push(atom);
}
let mut outputs = vec![];
for _ in self.midi_outs.iter() {
outputs.push(::livi::event::LV2AtomSequence::new(
&features,
scope.n_frames() as usize
));
}
let ports = ::livi::EmptyPortConnections::new()
.with_atom_sequence_inputs(
inputs.iter()
)
.with_atom_sequence_outputs(
outputs.iter_mut()
)
.with_audio_inputs(
self.audio_ins.iter().map(|o|o.as_slice(scope))
)
.with_audio_outputs(
self.audio_outs.iter_mut().map(|o|o.as_mut_slice(scope))
);
unsafe {
instance.run(scope.n_frames() as usize, ports).unwrap()
};
},
_ => {}
}
Control::Continue
}
}
impl PortList for Plugin {
fn audio_ins (&self) -> Usually<Vec<String>> {
let mut ports = vec![];
for port in self.audio_ins.iter() {
ports.push(port.name()?);
}
Ok(ports)
}
fn audio_outs (&self) -> Usually<Vec<String>> {
let mut ports = vec![];
for port in self.audio_outs.iter() {
ports.push(port.name()?);
}
Ok(ports)
}
fn midi_ins (&self) -> Usually<Vec<String>> {
let mut ports = vec![];
for port in self.midi_ins.iter() {
ports.push(port.name()?);
}
Ok(ports)
}
fn midi_outs (&self) -> Usually<Vec<String>> {
let mut ports = vec![];
for port in self.midi_outs.iter() {
ports.push(port.name()?);
}
Ok(ports)
}
}
pub fn render (state: &Plugin, buf: &mut Buffer, area: Rect)
-> Usually<Rect>
{
let Rect { x, y, height, .. } = area;
let mut width = 20u16;
match &state.plugin {
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 });
for i in start..end {
if let Some(port) = port_list.get(i) {
let value = if let Some(value) = instance.control_input(port.index) {
value
} else {
port.default_value
};
//let label = &format!("C·· M·· {:25} = {value:.03}", port.name);
let label = &format!("{:25} = {value:.03}", port.name);
width = width.max(label.len() as u16 + 4);
label.blit(buf, x + 2, y + 1 + i as u16 - start as u16, if i == state.selected {
Some(Style::default().green())
} else {
None
});
} else {
break
}
}
},
_ => {}
};
draw_header(state, buf, area.x, area.y, width)?;
Ok(Rect { width, ..area })
}
fn draw_header (state: &Plugin, buf: &mut Buffer, x: u16, y: u16, w: u16) -> Usually<Rect> {
let style = Style::default().gray();
let label1 = format!(" {}", state.name);
label1.blit(buf, x + 1, y, Some(style.white().bold()));
let label2 = format!("{}", &HELM[..(w as usize - 10).min(HELM.len())]);
label2.blit(buf, x + 2 + label1.len() as u16, y, Some(style.not_dim()));
Ok(Rect { x, y, width: w, height: 1 })
}
pub fn handle (s: &mut Plugin, event: &AppEvent) -> Usually<bool> {
handle_keymap(s, event, keymap!(Plugin {
[Up, NONE, "cursor_up", "move cursor up",
|s: &mut Plugin|{
if s.selected > 0 {
s.selected = s.selected - 1
} else {
s.selected = match &s.plugin {
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
_ => 0
}
}
Ok(true)
}],
[Down, NONE, "cursor_down", "move cursor down",
|s: &mut Plugin|{
s.selected = s.selected + 1;
match &s.plugin {
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => {
if s.selected >= port_list.len() {
s.selected = 0;
}
},
_ => {}
}
Ok(true)
}],
[Char(','), NONE, "decrement", "decrement value",
|s: &mut Plugin|{
match s.plugin.as_mut() {
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);
}
},
_ => {}
}
Ok(true)
}],
[Char('.'), NONE, "increment", "increment value",
|s: &mut Plugin|{
match s.plugin.as_mut() {
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);
}
},
_ => {}
}
Ok(true)
}],
[Char('m'), NONE, "toggle_midi_map", "toggle midi map mode",
|s: &mut Plugin|{
s.mapping = !s.mapping;
Ok(true)
}]
}))
}

View file

@ -1,41 +0,0 @@
use crate::core::*;
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,14 +0,0 @@
use crate::core::*;
use super::*;
impl ::vst::host::Host for Plugin {}
fn set_vst_plugin (host: &Arc<Mutex<Plugin>>, path: &str) -> Usually<PluginKind> {
let mut loader = ::vst::host::PluginLoader::load(
&std::path::Path::new("/nix/store/ij3sz7nqg5l7v2dygdvzy3w6cj62bd6r-helm-0.9.0/lib/lxvst/helm.so"),
host.clone()
)?;
Ok(PluginKind::VST2 {
instance: loader.instance()?
})
}

View file

@ -1,263 +0,0 @@
use crate::core::*;
pub struct Voice {
sample: Arc<Sample>,
after: usize,
position: usize,
}
impl Voice {
fn chunk (&mut self, mut frames: usize) -> Option<Vec<Vec<f32>>> {
// Create output buffer for each channel
let mut chunk = vec![vec![];self.sample.channels.len()];
// If it's not time to play yet, count down
if self.after >= frames {
self.after = self.after - frames;
return Some(chunk)
}
// If the voice will start playing within the current buffer,
// subtract the remaining number of wait frames.
if self.after > 0 && self.after < frames {
chunk = vec![vec![0.0;self.after];self.sample.channels.len()];
frames = frames - self.after;
self.after = 0;
}
if self.position < self.sample.end {
let start = self.position.min(self.sample.end);
let end = (self.position + frames).min(self.sample.end);
for (i, channel) in self.sample.channels.iter().enumerate() {
chunk[i].extend_from_slice(&channel[start..end]);
};
self.position = self.position + frames;
Some(chunk)
} else {
None
}
}
}
pub struct Sample {
pub name: String,
pub start: usize,
pub end: usize,
pub channels: Vec<Vec<f32>>,
}
impl Sample {
pub fn new (name: &str, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Arc<Self> {
Arc::new(Self { name: name.to_string(), start, end, channels })
}
fn play (self: &Arc<Self>, after: usize) -> Voice {
Voice { sample: self.clone(), after, position: self.start }
}
}
pub struct Sampler {
name: String,
cursor: (usize, usize),
samples: BTreeMap<u7, Arc<Sample>>,
voices: Vec<Voice>,
midi_in: Port<MidiIn>,
audio_ins: Vec<Port<AudioIn>>,
audio_outs: Vec<Port<AudioOut>>,
}
impl Sampler {
pub fn new (
name: &str,
samples: Option<BTreeMap<u7, Arc<Sample>>>,
) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
DynamicDevice::new(render, handle, Self::process, Self {
name: name.into(),
cursor: (0, 0),
samples: samples.unwrap_or(BTreeMap::new()),
voices: vec![],
midi_in: client.register_port("midi", ::jack::MidiIn::default())?,
audio_ins: vec![
client.register_port("recL", ::jack::AudioIn::default())?,
client.register_port("recR", ::jack::AudioIn::default())?,
],
audio_outs: vec![
client.register_port("outL", ::jack::AudioOut::default())?,
client.register_port("outR", ::jack::AudioOut::default())?,
],
}).activate(client)
}
pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
// Output buffer: this will be copied to the audio outs.
let channel_count = self.audio_outs.len();
let mut mixed = vec![vec![0.0;scope.n_frames() as usize];channel_count];
// Process MIDI input to add new voices.
for RawMidi { time, bytes } in self.midi_in.iter(scope) {
if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() {
if let MidiMessage::NoteOn { ref key, .. } = message {
if let Some(sample) = self.samples.get(key) {
self.voices.push(sample.play(time as usize));
}
}
}
}
// Emit next chunk of each currently playing voice,
// dropping voices that have reached their ends.
let mut voices = vec![];
std::mem::swap(&mut voices, &mut self.voices);
loop {
if voices.len() < 1 {
break
}
let mut voice = voices.swap_remove(0);
if let Some(chunk) = voice.chunk(scope.n_frames() as usize) {
for (i, channel) in chunk.iter().enumerate() {
let buffer = &mut mixed[i % channel_count];
for (i, sample) in channel.iter().enumerate() {
buffer[i] += sample;
}
}
self.voices.push(voice);
}
}
// Write output buffer to output ports.
for (i, port) in self.audio_outs.iter_mut().enumerate() {
let buffer = &mixed[i];
for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() {
*value = *buffer.get(i).unwrap_or(&0.0);
}
}
Control::Continue
}
fn load_sample (&mut self, _path: &str) {}
}
impl PortList for Sampler {
fn midi_ins (&self) -> Usually<Vec<String>> {
Ok(vec![self.midi_in.name()?])
}
fn audio_ins (&self) -> Usually<Vec<String>> {
let mut ports = vec![];
for port in self.audio_ins.iter() {
ports.push(port.name()?);
}
Ok(ports)
}
fn audio_outs (&self) -> Usually<Vec<String>> {
let mut ports = vec![];
for port in self.audio_outs.iter() {
ports.push(port.name()?);
}
Ok(ports)
}
}
pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, height, .. }: Rect)
-> Usually<Rect>
{
let style = Style::default().gray();
let title = format!(" {} ({})", state.name, state.voices.len());
title.blit(buf, x+1, y, Some(style.white().bold().not_dim()));
let mut width = title.len() + 2;
for (i, (note, sample)) in state.samples.iter().enumerate() {
let style = if i == state.cursor.0 {
Style::default().green()
} else {
Style::default()
};
let i = i as u16;
let y1 = y+1+i;
if y1 >= y + height {
break
}
if i as usize == state.cursor.0 {
"".blit(buf, x+1, y1, Some(style.bold()));
}
let label1 = format!("{note:3} {:8}", sample.name);
let label2 = format!("{:>6} {:>6}", sample.start, sample.end);
label1.blit(buf, x+2, y1, Some(style.bold()));
label2.blit(buf, x+3+label1.len()as u16, y1, Some(style));
width = width.max(label1.len() + label2.len() + 4);
}
let height = ((1 + state.samples.len()) as u16).min(height);
Ok(Rect { x, y, width: width as u16, height })
}
//fn render_table (
//state: &mut Sampler,
//stdout: &mut Stdout,
//offset: (u16, u16),
//) -> Result<(), Box<dyn Error>> {
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
//stdout.queue(move_to(0, 3))?.queue(
//Print(" Name Rate Trigger Route")
//)?;
//for (i, sample) in state.samples.lock().unwrap().iter().enumerate() {
//let row = 4 + i as u16;
//for (j, (column, field)) in [
//(0, format!(" {:7} ", sample.name)),
//(9, format!(" {:.1}Hz ", sample.rate)),
//(18, format!(" MIDI C{} {} ", sample.trigger.0, sample.trigger.1)),
//(33, format!(" {:.1}dB -> Output ", sample.gain)),
//(50, format!(" {} ", sample.playing.unwrap_or(0))),
//].into_iter().enumerate() {
//stdout.queue(move_to(column, row))?;
//if state.selected_sample == i && state.selected_column == j {
//stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?;
//} else {
//stdout.queue(PrintStyledContent(field.to_string().bold()))?;
//}
//}
//}
//Ok(())
//}
//fn render_meters (
//state: &mut Sampler,
//stdout: &mut Stdout,
//offset: (u16, u16),
//) -> Result<(), Box<dyn Error>> {
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
//for (i, sample) in state.samples.lock().iter().enumerate() {
//let row = 4 + i as u16;
//stdout.queue(move_to(32, row))?.queue(
//PrintStyledContent("▁".green())
//)?;
//}
//Ok(())
//}
pub fn handle (state: &mut Sampler, event: &AppEvent) -> Usually<bool> {
Ok(handle_keymap(state, event, KEYMAP)?)
}
pub const KEYMAP: &'static [KeyBinding<Sampler>] = keymap!(Sampler {
[Up, NONE, "cursor_up", "move cursor up", cursor_up],
[Down, NONE, "cursor_down", "move cursor down", cursor_down],
[Char('t'), NONE, "trigger", "play current sample", trigger],
[Enter, NONE, "select", "select item under cursor", select],
});
fn cursor_up (state: &mut Sampler) -> Usually<bool> {
state.cursor.0 = if state.cursor.0 == 0 {
state.samples.len() - 1
} else {
state.cursor.0 - 1
};
Ok(true)
}
fn cursor_down (state: &mut Sampler) -> Usually<bool> {
state.cursor.0 = (state.cursor.0 + 1) % state.samples.len();
Ok(true)
}
fn trigger (state: &mut Sampler) -> Usually<bool> {
for (i, sample) in state.samples.values().enumerate() {
if i == state.cursor.0 {
state.voices.push(sample.play(0))
}
}
Ok(true)
}
fn select (state: &mut Sampler) -> Usually<bool> {
for (i, _sample) in state.samples.values().enumerate() {
if i == state.cursor.0 {
//state.voices.push(sample.play(0))
}
}
Ok(true)
}

View file

@ -1,169 +0,0 @@
use crate::core::*;
use super::*;
pub struct SceneGrid<'a> {
pub buf: &'a mut Buffer,
pub area: Rect,
pub name: &'a str,
pub focused: bool,
pub scenes: &'a[Scene],
pub tracks: &'a[Track],
pub cursor: &'a(usize, usize),
}
impl<'a> SceneGrid<'a> {
pub fn new (
buf: &'a mut Buffer,
area: Rect,
name: &'a str,
focused: bool,
scenes: &'a[Scene],
tracks: &'a[Track],
cursor: &'a(usize, usize),
) -> Self {
Self {
buf,
area,
name,
focused,
scenes,
tracks,
cursor,
}
}
pub fn draw (&mut self) -> Usually<Rect> {
self.area.height = self.scenes.len() as u16 + 2;
let style = Some(Style::default().green().dim());
if self.focused {
let Rect { x, y, width, height } = self.area;
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 {
break
}
self.separator_v(x, i == self.cursor.0);
x = x + w;
self.separator_v(x, i == self.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 {
break
}
title.blit(
self.buf, x+1, y, Some(self.highlight(i == self.cursor.0).bold())
);
if i == 0 {
self.scenes(x+1, y + 1);
} else if i < columns.len() {
self.clips(x+1, y + 1, i - 1);
}
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 column_names = vec![self.name];
for track in self.tracks.iter() {
column_names.push(track.name.as_str());
}
column_names
}
fn column_widths (&self) -> Vec<u16> {
self.column_names().iter().map(|name|(name.len() as u16).max(10) + 3).collect()
}
fn scenes (&mut self, x: u16, y: u16) -> u16 {
let mut index = 0usize;
loop {
if index >= self.scenes.len() {
break
}
if y + index as u16 >= self.area.height {
break
}
if let Some(scene) = self.scenes.get(index) {
let style = Some(self.highlight(
(0 == self.cursor.0) && (index + 1 == self.cursor.1)
).bold());
"".blit(self.buf, x, y + index as u16, style);
scene.name.blit(self.buf, x+1, y + index as u16, style);
}
index = index + 1;
}
let hi = (0 == self.cursor.0) &&
(self.scenes.len() + 1 == self.cursor.1);
"Add scene…".blit(self.buf, x, y + index as u16, Some(if hi {
self.highlight(true)
} else {
Style::default().dim()
}));
index as u16
}
fn clips (&mut self, x: u16, y: u16, track: usize) -> u16 {
let mut index = 0;
loop {
if index >= self.scenes.len() {
break
}
if y + index as u16 >= self.area.height {
break
}
if let Some(scene) = self.scenes.get(index) {
let hi = (track + 1 == self.cursor.0) &&
(index + 1 == self.cursor.1);
let style = Some(self.highlight(hi));
let clip = scene.clips.get(track);
let index = index as u16;
let label = if let Some(Some(clip)) = clip {
let track = self.tracks[track].sequencer.state();
let phrase = track.phrases.get(*clip);
if let Some(phrase) = phrase {
format!("{}", phrase.name)
} else {
format!("????")
}
} else {
format!(" ·········")
};
label.blit(self.buf, x, y + index, style);
}
index = index + 1;
}
let hi = (track + 1 == self.cursor.0) &&
(self.scenes.len() + 1 == self.cursor.1);
" + Add clip".blit(self.buf, x, y + index as u16, Some(if hi {
self.highlight(true)
} else {
Style::default().dim()
}));
index as u16
}
fn highlight (&self, highlight: bool) -> Style {
if highlight {
if self.focused {
Style::default().green().not_dim()
} else {
Style::default().green().dim()
}
} else {
Style::default()
}
}
fn separator_v (&mut self, x: u16, highlight: bool) {
let style = Some(self.highlight(highlight));
for y in self.area.y+1..self.area.y+self.area.height-1 {
"".blit(self.buf, x, y, style);
}
}
}

View file

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

View file

@ -1,281 +0,0 @@
use crate::core::*;
use crate::layout::*;
use crate::device::*;
mod grid; pub use self::grid::*;
mod handle; pub use self::handle::*;
mod scene; pub use self::scene::*;
pub struct Launcher {
name: String,
timebase: Arc<Timebase>,
transport: ::jack::Transport,
playing: TransportState,
monitoring: bool,
recording: bool,
overdub: bool,
current_frame: usize,
cursor: (usize, usize),
pub tracks: Vec<Track>,
scenes: Vec<Scene>,
show_help: bool,
view: LauncherView,
modal: Option<Box<dyn Modal<Self>>>,
}
impl Launcher {
pub fn new (
name: &str,
timebase: &Arc<Timebase>,
tracks: Option<Vec<Track>>,
scenes: Option<Vec<Scene>>
) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
let transport = client.transport();
let ppq = timebase.ppq() as usize;
DynamicDevice::new(render, handle, process, Self {
name: name.into(),
view: LauncherView::Chains,
playing: transport.query_state()?,
transport,
timebase: timebase.clone(),
monitoring: true,
recording: false,
overdub: true,
cursor: (2, 2),
current_frame: 0,
scenes: scenes.unwrap_or_else(||vec![Scene::new(&"Scene 1", &[None])]),
tracks: if let Some(tracks) = tracks { tracks } else { vec![
Track::new("Track 1", &timebase, None, Some(vec![
Phrase::new("MIDI Clip 1", ppq * 4, Some(BTreeMap::from([
( ppq * 0, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
( ppq * 1, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
( ppq * 2, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
( ppq * 3, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
])))
]))?,
] },
show_help: true,
modal: None
}).activate(client)
}
fn cols (&self) -> usize {
(self.tracks.len() + 2) as usize
}
fn col (&self) -> usize {
self.cursor.0 as usize
}
fn dec_col (&mut self) {
self.cursor.0 = if self.cursor.0 > 0 {
self.cursor.0 - 1
} else {
(self.cols() - 1) as usize
}
}
fn inc_col (&mut self) {
self.cursor.0 = if self.cursor.0 >= self.cols() - 1 {
0
} else {
self.cursor.0 + 1
}
}
fn rows (&self) -> usize {
(self.scenes.len() + 2) as usize
}
fn row (&self) -> usize {
self.cursor.1 as usize
}
fn dec_row (&mut self) {
self.cursor.1 = if self.cursor.1 > 0 {
self.cursor.1 - 1
} else {
self.rows() - 1
}
}
fn inc_row (&mut self) {
self.cursor.1 = if self.cursor.1 >= self.rows() - 1 {
0
} else {
self.cursor.1 + 1
}
}
pub fn track <'a> (&'a self) -> Option<(usize, &'a Track)> {
match self.col() { 0 => None, _ => {
let id = self.col() as usize - 1;
self.tracks.get(id).map(|t|(id, t))
} }
}
pub fn scene <'a> (&'a self) -> Option<(usize, &'a Scene)> {
match self.row() { 0 => None, _ => {
let id = self.row() as usize - 1;
self.scenes.get(id).map(|t|(id, t))
} }
}
pub fn sequencer <'a> (&'a self) -> Option<MutexGuard<Sequencer>> {
Some(self.track()?.1.sequencer.state())
}
pub fn chain <'a> (&'a self) -> Option<MutexGuard<Chain>> {
Some(self.track()?.1.chain.state())
}
pub fn phrase_id (&self) -> Option<usize> {
let (track_id, _) = self.track()?;
let (_, scene) = self.scene()?;
*scene.clips.get(track_id)?
}
}
impl DynamicDevice<Launcher> {
pub fn connect (&self, midi_in: &str, audio_outs: &[&str]) -> Usually<&Self> {
{
let state = &self.state();
let (client, _status) = Client::new(
&format!("{}-init", &state.name), ClientOptions::NO_START_SERVER
)?;
let midi_ins = client.ports(Some(midi_in), None, PortFlags::IS_OUTPUT);
let audio_outs: Vec<Vec<String>> = audio_outs.iter()
.map(|pattern|client.ports(Some(pattern), None, PortFlags::IS_INPUT))
.collect();
for (i, sequencer) in state.tracks.iter().enumerate() {
for sequencer_midi_in in sequencer.midi_ins()?.iter() {
for midi_in in midi_ins.iter() {
client.connect_ports_by_name(&midi_in, &sequencer_midi_in)?;
}
}
let chain: &DynamicDevice<Chain> = &state.tracks[i].chain;
for port in sequencer.midi_outs()?.iter() {
for midi_in in chain.midi_ins()?.iter() {
client.connect_ports_by_name(&port, &midi_in)?;
}
}
for (j, port) in chain.audio_outs()?.iter().enumerate() {
for audio_out in audio_outs[j % audio_outs.len()].iter() {
client.connect_ports_by_name(&port, &audio_out)?;
}
}
}
}
Ok(self)
}
}
impl PortList for Launcher {}
pub fn process (state: &mut Launcher, _: &Client, _: &ProcessScope) -> Control {
let transport = state.transport.query().unwrap();
state.playing = transport.state;
state.current_frame = transport.pos.frame() as usize;
Control::Continue
}
pub fn render (state: &Launcher, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
//area.width = 80; // DOS mode
//area.height = 25;
let Rect { x, mut y, width, height } = area;
y = y + crate::device::Transport {
timebase: &state.timebase,
playing: state.playing,
record: state.sequencer().map(|s|s.recording).unwrap_or(false),
overdub: state.sequencer().map(|s|s.overdub).unwrap_or(false),
monitor: state.sequencer().map(|s|s.monitoring).unwrap_or(false),
frame: state.current_frame
}.render(buf, area)?.height;
y = y + crate::device::launcher::SceneGrid {
buf,
area: Rect { x, y, width, height },
name: &state.name,
focused: state.view.is_tracks(),
scenes: &state.scenes,
tracks: &state.tracks,
cursor: &state.cursor
}.draw()?.height;
y = y + draw_section_chains(
state,
buf,
Rect { x, y, width, height: height/3 }
)?.height;
y = y + draw_section_sequencer(
state,
buf,
Rect { x, y, width, height: height - y }
)?.height;
area.height = y;
if state.show_help {
let style = Some(Style::default().bold().white().not_dim().on_black().italic());
let hide = "[Tab] Mode [Arrows] Move [.,] Value [F1] Toggle help ";
hide.blit(buf, x + (width - hide.len() as u16) / 2, height - 1, style);
}
if let Some(ref modal) = state.modal {
for cell in buf.content.iter_mut() {
cell.fg = ::ratatui::style::Color::Gray;
cell.modifier = ::ratatui::style::Modifier::DIM;
}
}
Ok(area)
}
fn draw_section_sequencer (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Rect { x, y, width, height } = area;
let style = Some(Style::default().green().dim());
let view = &state.view;
match view {
LauncherView::Sequencer => {
lozenge_left(buf, x, y, height, style);
lozenge_right(buf, x + width - 1, y, height, style);
},
_ => {},
};
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::draw(
buf,
area,
match state.phrase_id().map(|id|sequencer.phrases.get(id)) {
Some(Some(phrase)) => Some(phrase),
_ => None
},
state.timebase.ppq() as usize,
sequencer.time_cursor,
sequencer.time_start,
sequencer.time_zoom,
sequencer.note_cursor,
sequencer.note_start,
Some(match view {
LauncherView::Sequencer => Style::default().green().not_dim(),
_ => Style::default().green().dim(),
})
)
}
fn draw_section_chains (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let style = Some(Style::default().green().dim());
if state.view.is_chains() {
let Rect { x, y, width, height} = area;
lozenge_left(buf, x, y, height, style);
lozenge_right(buf, x + width - 1, y, height, style);
}
let chain = state.chain();
let _ = if let Some(chain) = &chain {
let (_, plugins) = crate::device::chain::draw_as_row(
&*chain, buf, area, style
)?;
plugins
} else {
vec![]
};
Ok(area)
}
pub enum LauncherView {
Tracks,
Sequencer,
Chains
}
impl LauncherView {
fn is_chains (&self) -> bool {
match self { Self::Chains => true, _ => false }
}
fn is_tracks (&self) -> bool {
match self { Self::Tracks => true, _ => false }
}
}

View file

@ -1,13 +0,0 @@
pub struct Scene {
pub name: String,
pub clips: Vec<Option<usize>>,
}
impl Scene {
pub fn new (name: impl AsRef<str>, clips: impl AsRef<[Option<usize>]>) -> Self {
Self {
name: name.as_ref().into(),
clips: clips.as_ref().iter().map(|x|x.clone()).collect()
}
}
}

View file

@ -1,29 +0,0 @@
use crate::core::*;
pub struct Looper {
name: String
}
impl Looper {
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
Ok(DynamicDevice::new(render, handle, process, Self {
name: name.into(),
}))
}
}
pub fn process (_: &mut Looper, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
pub fn render (_: &Looper, _: &mut Buffer, _: Rect) -> Usually<Rect> {
Ok(Rect::default())
}
pub fn handle (_: &mut Looper, _: &AppEvent) -> Usually<bool> {
Ok(false)
}
pub const ACTIONS: [(&'static str, &'static str);1] = [
("Ins/Del", "Add/remove loop"),
];

View file

@ -1,249 +0,0 @@
use crate::core::*;
use crate::layout::*;
pub struct Mixer {
name: String,
tracks: Vec<Track>,
selected_track: usize,
selected_column: usize,
}
impl Mixer {
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?;
Ok(DynamicDevice::new(render, handle, process, Self {
name: name.into(),
selected_column: 0,
selected_track: 1,
tracks: vec![
Track::new(&client, 1, "Mono 1")?,
Track::new(&client, 1, "Mono 2")?,
Track::new(&client, 2, "Stereo 1")?,
Track::new(&client, 2, "Stereo 2")?,
Track::new(&client, 2, "Stereo 3")?,
Track::new(&client, 2, "Bus 1")?,
Track::new(&client, 2, "Bus 2")?,
Track::new(&client, 2, "Mix")?,
],
}))
}
}
pub fn process (
_: &mut Mixer,
_: &Client,
_: &ProcessScope
) -> Control {
Control::Continue
}
pub fn render (state: &Mixer, buf: &mut Buffer, mut area: Rect)
-> Usually<Rect>
{
if area.height < 2 {
return Ok(area)
}
area.x = area.width.saturating_sub(80) / 2;
area.width = area.width.min(80);
area.height = state.tracks.len() as u16 + 2;
draw_box(buf, area);
let x = area.x + 1;
let y = area.y + 1;
let _h = area.height - 2;
for (i, track) in state.tracks.iter().enumerate() {
//buf.set_string(
//x, y + index as u16,
//&track.name, Style::default().bold().not_dim()
//);
for (j, (column, field)) in [
(0, format!(" {:10} ", track.name)),
(12, format!(" {:.1}dB ", track.gain)),
(22, format!(" [ ] ")),
(30, format!(" C ")),
(35, format!(" {:.1}dB ", track.level)),
(45, format!(" [ ] ")),
(51, format!(" {:7} ", track.route)),
].into_iter().enumerate() {
buf.set_string(
x + column as u16,
y + i as u16,
field,
if state.selected_track == i && state.selected_column == j {
Style::default().white().bold().not_dim()
} else {
Style::default().not_dim()
}
);
//stdout.queue(move_to(column, row))?;
//if state.selected_track == i && state.selected_column == j {
//stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?;
//} else {
//stdout.queue(PrintStyledContent(field.to_string().bold()))?;
//}
//fn render_meters (
//state: &mut Mixer,
//stdout: &mut Stdout,
//offset: Rect
//) -> Result<(), Box<dyn Error>> {
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
//for (i, track) in state.tracks.iter().enumerate() {
//let row = (i + 1) as u16;
//stdout
//.queue(move_to(10, row))?.queue(PrintStyledContent("▁".green()))?
//.queue(move_to(20, row))?.queue(PrintStyledContent("▁".green()))?
//.queue(move_to(28, row))?.queue(PrintStyledContent("▁".green()))?
//.queue(move_to(43, row))?.queue(PrintStyledContent("▁".green()))?;
//}
//Ok(())
//}
}
}
Ok(area)
}
pub fn handle (state: &mut Mixer, event: &AppEvent) -> Usually<bool> {
if let AppEvent::Input(crossterm::event::Event::Key(event)) = event {
match event.code {
//KeyCode::Char('c') => {
//if event.modifiers == KeyModifiers::CONTROL {
//state.exit();
//}
//},
KeyCode::Down => {
state.selected_track = (state.selected_track + 1) % state.tracks.len();
println!("{}", state.selected_track);
return Ok(true)
},
KeyCode::Up => {
if state.selected_track == 0 {
state.selected_track = state.tracks.len() - 1;
} else {
state.selected_track = state.selected_track - 1;
}
println!("{}", state.selected_track);
return Ok(true)
},
KeyCode::Left => {
if state.selected_column == 0 {
state.selected_column = 6
} else {
state.selected_column = state.selected_column - 1;
}
return Ok(true)
},
KeyCode::Right => {
if state.selected_column == 6 {
state.selected_column = 0
} else {
state.selected_column = state.selected_column + 1;
}
return Ok(true)
},
_ => {
println!("\n{event:?}");
}
}
}
Ok(false)
}
// TODO:
// - Meters: propagate clipping:
// - If one stage clips, all stages after it are marked red
// - If one track clips, all tracks that feed from it are marked red?
pub const ACTIONS: [(&'static str, &'static str);2] = [
("+/-", "Adjust"),
("Ins/Del", "Add/remove track"),
];
pub struct Track {
name: String,
channels: u8,
input_ports: Vec<Port<AudioIn>>,
pre_gain_meter: f64,
gain: f64,
insert_ports: Vec<Port<AudioOut>>,
return_ports: Vec<Port<AudioIn>>,
post_gain_meter: f64,
post_insert_meter: f64,
level: f64,
pan: f64,
output_ports: Vec<Port<AudioOut>>,
post_fader_meter: f64,
route: String,
}
impl Track {
pub fn new (jack: &Client, channels: u8, name: &str) -> Result<Self, Box<dyn Error>> {
let mut input_ports = vec![];
let mut insert_ports = vec![];
let mut return_ports = vec![];
let mut output_ports = vec![];
for channel in 1..=channels {
input_ports.push(jack.register_port(&format!("{name} [input {channel}]"), AudioIn::default())?);
output_ports.push(jack.register_port(&format!("{name} [out {channel}]"), AudioOut::default())?);
let insert_port = jack.register_port(&format!("{name} [pre {channel}]"), AudioOut::default())?;
let return_port = jack.register_port(&format!("{name} [insert {channel}]"), AudioIn::default())?;
jack.connect_ports(&insert_port, &return_port)?;
insert_ports.push(insert_port);
return_ports.push(return_port);
}
Ok(Self {
name: name.into(),
channels,
input_ports,
pre_gain_meter: 0.0,
gain: 0.0,
post_gain_meter: 0.0,
insert_ports,
return_ports,
post_insert_meter: 0.0,
level: 0.0,
pan: 0.0,
post_fader_meter: 0.0,
route: "---".into(),
output_ports,
})
}
}
//impl<W: Write> Input<TUI<W>, bool> for Mixer {
//fn handle (&mut self, engine: &mut TUI<W>) -> Result<Option<bool>> {
//Ok(None)
//}
//
//impl<W: Write> Output<TUI<W>, [u16;2]> for Mixer {
//fn render (&self, envine: &mut TUI<W>) -> Result<Option<[u16;2]>> {
//let tracks_table = Columns::new()
//.add(titles)
//.add(input_meters)
//.add(gains)
//.add(gain_meters)
//.add(pres)
//.add(pre_meters)
//.add(levels)
//.add(pans)
//.add(pan_meters)
//.add(posts)
//.add(routes)
//Rows::new()
//.add(Columns::new()
//.add(Rows::new()
//.add("[Arrows]".bold())
//.add("Navigate"))
//.add(Rows::new()
//.add("[+/-]".bold())
//.add("Adjust"))
//.add(Rows::new()
//.add("[Ins/Del]".bold())
//.add("Add/remove track")))
//.add(tracks_table)
//.render(engine)
//}
//}

View file

@ -1,7 +0,0 @@
mod chain; pub use self::chain::{Chain, Plugin, Sampler, Sample, Voice};
mod launcher; pub use self::launcher::{Launcher, Scene};
mod looper; pub use self::looper::Looper;
mod mixer; pub use self::mixer::Mixer;
mod sequencer; pub use self::sequencer::{Sequencer, Phrase};
mod track; pub use self::track::Track;
mod transport; pub use self::transport::Transport;

View file

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

View file

@ -1,185 +0,0 @@
use crate::core::*;
use super::*;
pub fn draw (
buf: &mut Buffer,
area: Rect,
phrase: Option<&Phrase>,
ppq: usize,
time: usize,
time0: usize,
time_z: usize,
note: usize,
note0: usize,
style: Option<Style>,
) -> Usually<Rect> {
let now = 0;
let notes = &[];
match time_z {
1 => "1/384",
2 => "1/192",
3 => "1/128",
4 => "1/96",
6 => "1/64",
8 => "1/48",
12 => "1/32",
16 => "1/24",
24 => "1/16",
32 => "1/12",
48 => "1/8",
64 => "1/6",
96 => "1/4",
128 => "1/3",
192 => "1/2",
384 => "1/1",
_ => ""
}.blit(buf, area.x, area.y, Some(Style::default().dim()));
keys(buf, area, note0, notes)?;
timer(buf, area, time0, now);
if let Some(phrase) = phrase {
lanes(buf, area, phrase, ppq, time_z, time0, note0);
}
let style = style.unwrap_or_else(||{Style::default().green().not_dim()});
cursor(buf, area, style, time, note);
//footer(buf, area, note0, note, time0, time, time_z);
Ok(area)
}
pub fn timer (
buf: &mut Buffer,
area: Rect,
time0: usize,
now: usize
) {
let x = area.x + 5;
for step in time0..(time0+area.width as usize).saturating_sub(5) {
buf.set_string(x + step as u16, area.y, &"-", if step == now {
Style::default().yellow().bold().not_dim()
} else {
Style::default()
});
}
}
pub fn keys (
buf: &mut Buffer,
area: Rect,
note0: usize,
notes: &[bool],
) -> Usually<Rect> {
let bw = Style::default().dim();
let Rect { x, y, width, height } = area;
let h = height.saturating_sub(2);
for index in 0..h {
let y = y + h - index;
let key = KEYS_VERTICAL[(index % 6) as usize];
key.blit(buf, x + 1, y, Some(bw));
"".blit(buf, x + 2, y, Some(bw));
"|---".repeat(width.saturating_sub(6) as usize).blit(buf, x + 5, y, Some(bw.black()));
let note_a = note0 + (index * 2) as usize;
if note_a % 12 == 0 {
let octave = format!("C{}", (note_a / 12) as i8 - 2);
octave.blit(buf, x + 3, y, None);
continue
}
let note_b = note0 + (index * 2) as usize;
if note_b % 12 == 0 {
let octave = format!("C{}", (note_b / 12) as i8 - 2);
octave.blit(buf, x + 3, y, None);
continue
}
}
Ok(area)
}
pub fn lanes (
buf: &mut Buffer,
area: Rect,
phrase: &Phrase,
ppq: usize,
time_z: usize,
time0: usize,
note0: usize,
) {
let Rect { x, y, width, height } = area;
let time0 = time0 / time_z;
let time1 = time0 + width as usize;
let note1 = note0 + height as usize;
let bg = Style::default();
let (bw, wh) = (bg.dim(), bg.white());
let offset = 5;
for x in x+offset..x+width-offset {
let step = (x-offset) as usize * time_z;
if step % ppq == 0 {
"|".blit(buf, x as u16, y, Some(Style::default().dim()));
}
let bar = 4 * ppq;
if step % bar == 0 {
format!("{}", (step/bar)+1)
.blit(buf, x as u16, y, Some(Style::default().bold().not_dim()));
}
let (a, b) = (step, step + time_z);
for index in 0..height-2 {
let note_a = note0 + index as usize * 2;
let note_b = note0 + index as usize * 2 + 1;
let (character, style) = match (
phrase.contains_note_on(u7::from_int_lossy(note_a as u8), a, b),
phrase.contains_note_on(u7::from_int_lossy(note_b as u8), a, b),
) {
(true, true) => ("", wh),
(false, true) => ("", wh),
(true, false) => ("", wh),
(false, false) => ("·", bw),
};
let y = y + height.saturating_sub(index+2) as u16;
character.blit(buf, x, y, Some(style));
}
}
}
pub fn cursor (
buf: &mut Buffer,
area: Rect,
style: Style,
time: usize,
note: usize
) {
let x = area.x + 5 + time as u16;
let y = area.y + 1 + note as u16 / 2;
let c = if note % 2 == 0 { "" } else { "" };
c.blit(buf, x, y, Some(style));
}
pub fn footer (
buf: &mut Buffer,
area: Rect,
note0: usize,
note: usize,
time0: usize,
time: usize,
time_z: usize,
) {
let Rect { mut x, y, width, height } = area;
buf.set_string(x, y + height, format!("{}", "-".repeat((width - 2).into())),
Style::default().dim());
buf.set_string(x, y + height + 2, format!("{}", "-".repeat((width - 2).into())),
Style::default().dim());
x = x + 2;
{
for (_, [letter, title, value]) in [
["S", &format!("ync"), &format!("<4/4>")],
["Q", &format!("uant"), &format!("<1/{}>", 4 * time_z)],
["N", &format!("ote"), &format!("{} ({}-{})", note0 + note, note0, "X")],
["T", &format!("ime"), &format!("{} ({}-{})", time0 + time, time0 + 1, "X")],
].iter().enumerate() {
buf.set_string(x, y + height + 1, letter, Style::default().bold().yellow().dim());
x = x + 1;
buf.set_string(x, y + height + 1, &title, Style::default().bold().dim());
x = x + title.len() as u16 + 1;
buf.set_string(x, y + height + 1, &value, Style::default().not_dim());
x = x + value.len() as u16;
buf.set_string(x, y + height + 1, " ", Style::default().dim());
x = x + 2;
}
}
}

View file

@ -1,26 +0,0 @@
use crate::core::*;
pub const KEY_WHITE: Style = Style {
fg: Some(Color::Gray),
bg: None,
underline_color: None,
add_modifier: ::ratatui::style::Modifier::empty(),
sub_modifier: ::ratatui::style::Modifier::empty(),
};
pub const KEY_BLACK: Style = Style {
fg: Some(Color::Black),
bg: None,
underline_color: None,
add_modifier: ::ratatui::style::Modifier::empty(),
sub_modifier: ::ratatui::style::Modifier::empty(),
};
pub const KEY_STYLE: [Style;12] = [
KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE,
KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE,
];
pub const KEYS_VERTICAL: [&'static str; 6] = [
"", "", "", "", "", "",
];

View file

@ -1,210 +0,0 @@
use crate::core::*;
use crate::layout::*;
mod keys; use self::keys::*;
mod handle; pub use self::handle::*;
mod phrase; pub use self::phrase::*;
pub mod horizontal;
pub mod vertical;
pub struct Sequencer {
pub name: String,
/// JACK transport handle.
pub transport: ::jack::Transport,
/// JACK MIDI input port that will be created.
pub midi_in: Port<MidiIn>,
/// JACK MIDI output port that will be created.
pub midi_out: Port<MidiOut>,
/// Holds info about tempo
pub timebase: Arc<Timebase>,
/// Phrase selector
pub sequence: Option<usize>,
/// Map: tick -> MIDI events at tick
pub phrases: Vec<Phrase>,
/// Red keys on piano roll.
pub notes_on: Vec<bool>,
/// Play sequence to output.
pub playing: TransportState,
/// Play input through output.
pub monitoring: bool,
/// Write input to sequence.
pub recording: bool,
/// Don't delete when recording.
pub overdub: bool,
/// Display mode
pub view: SequencerView,
/// Range of notes to display
pub note_start: usize,
/// Position of cursor within note range
pub note_cursor: usize,
/// PPM per display unit
pub time_zoom: usize,
/// Range of time steps to display
pub time_start: usize,
/// Position of cursor within time range
pub time_cursor: usize,
}
#[derive(Debug, Clone)]
pub enum SequencerView { Tiny, Compact, Horizontal, Vertical, }
impl Sequencer {
pub fn new (
name: &str,
timebase: &Arc<Timebase>,
phrases: Option<Vec<Phrase>>,
) -> Usually<DynamicDevice<Self>> {
let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?;
let transport = client.transport();
DynamicDevice::new(render, handle, Self::process, Self {
name: name.into(),
timebase: timebase.clone(),
phrases: phrases.unwrap_or_else(||vec![Phrase::default()]),
sequence: Some(0),
transport,
midi_in: client.register_port("in", MidiIn::default())?,
monitoring: true,
recording: true,
midi_out: client.register_port("out", MidiOut::default())?,
playing: TransportState::Starting,
overdub: true,
view: SequencerView::Horizontal,
notes_on: vec![false;128],
note_start: 12,
note_cursor: 0,
time_zoom: 24,
time_start: 0,
time_cursor: 0,
}).activate(client)
}
pub fn phrase <'a> (&'a self) -> Option<&'a Phrase> {
self.phrases.get(self.sequence?)
}
pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
if self.sequence.is_none() { return Control::Continue }
let phrase = self.phrases.get_mut(self.sequence.unwrap());
if phrase.is_none() { return Control::Continue }
let phrase = phrase.unwrap();
let frame = scope.last_frame_time() as usize;
let frames = scope.n_frames() as usize;
let mut output: Vec<Option<Vec<Vec<u8>>>> = vec![None;frames];
let transport = self.transport.query().unwrap();
if transport.state != self.playing {
all_notes_off(&mut output);
}
self.playing = transport.state;
// Play from phrase into output buffer
if self.playing == TransportState::Rolling {
phrase.process_out(
&mut output,
&mut self.notes_on,
&self.timebase,
frame,
frames
);
}
// Play from input to monitor, and record into phrase.
phrase.process_in(
self.midi_in.iter(scope),
&mut self.notes_on,
if self.monitoring { Some(&mut output) } else { None },
self.recording && self.playing == TransportState::Rolling,
&self.timebase,
frame,
);
write_output(&mut self.midi_out.writer(scope), &mut output, frames);
Control::Continue
}
}
impl PortList for Sequencer {
fn midi_ins (&self) -> Usually<Vec<String>> { Ok(vec![self.midi_in.name()?]) }
fn midi_outs (&self) -> Usually<Vec<String>> { Ok(vec![self.midi_out.name()?]) }
}
fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Rect { x, y, width, height } = area;
let header = draw_header(s, buf, area)?;
let piano = match s.view {
SequencerView::Tiny => Rect { x, y, width, height: 0 },
SequencerView::Compact => Rect { x, y, width, height: 0 },
SequencerView::Vertical => self::vertical::draw(s, buf, Rect {
x, y: y + header.height, width, height,
})?,
SequencerView::Horizontal => self::horizontal::draw(
buf,
Rect { x, y: y + header.height, width, height, },
s.phrase(),
s.timebase.ppq() as usize,
s.time_cursor,
s.time_start,
s.time_zoom,
s.note_cursor,
s.note_start,
None
)?,
};
Ok(draw_box(buf, Rect {
x, y,
width: header.width.max(piano.width),
height: header.height + piano.height
}))
}
pub fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Rect { x, y, width, .. } = area;
let style = Style::default().gray();
crate::device::Transport {
timebase: &s.timebase,
playing: s.playing,
record: s.recording,
overdub: s.overdub,
monitor: s.monitoring,
frame: 0
}.render(buf, area)?;
let separator = format!("{}", "-".repeat((width - 2).into()));
separator.blit(buf, x, y + 2, Some(style.dim()));
let _ = draw_clips(s, buf, area)?;
Ok(Rect { x, y, width, height: 3 })
}
pub fn draw_clips (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Rect { x, y, .. } = area;
let style = Style::default().gray();
for (i, sequence) in s.phrases.iter().enumerate() {
let label = format!("{}", &sequence.name);
label.blit(buf, x + 2, y + 3 + (i as u16)*2, Some(if Some(i) == s.sequence {
match s.playing {
TransportState::Rolling => style.white().bold(),
_ => style.not_dim().bold()
}
} else {
style.dim()
}));
}
Ok(Rect { x, y, width: 14, height: 14 })
}
/// Add "all notes off" to the start of a buffer.
pub fn all_notes_off (output: &mut MIDIChunk) {
output[0] = Some(vec![]);
if let Some(Some(frame)) = output.get_mut(0) {
let mut buf = vec![];
let msg = MidiMessage::Controller { controller: 123.into(), value: 0.into() };
let evt = LiveEvent::Midi { channel: 0.into(), message: msg };
evt.write(&mut buf).unwrap();
frame.push(buf);
}
}
/// Write to JACK port from output buffer (containing notes from sequence and/or monitor)
fn write_output (writer: &mut ::jack::MidiWriter, output: &mut MIDIChunk, frames: usize) {
for time in 0..frames {
if let Some(Some(frame)) = output.get_mut(time ) {
for event in frame.iter() {
writer.write(&::jack::RawMidi { time: time as u32, bytes: &event })
.expect(&format!("{event:?}"));
}
}
}
}

View file

@ -1,135 +0,0 @@
use crate::core::*;
pub type PhraseData = BTreeMap<usize, Vec<MidiMessage>>;
pub type MIDIMessage = Vec<u8>;
pub type MIDIChunk = [Option<Vec<MIDIMessage>>];
pub struct Phrase {
pub name: String,
pub length: usize,
pub notes: PhraseData,
}
impl Phrase {
pub fn new (name: &str, length: usize, notes: Option<PhraseData>) -> Self {
Self { name: name.to_string(), length, notes: notes.unwrap_or(BTreeMap::new()) }
}
/// Check if a range `start..end` contains MIDI Note On `k`
pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool {
for (_, (_, events)) in self.notes.range(start..end).enumerate() {
for event in events.iter() {
match event {
MidiMessage::NoteOn {key,..} => {
if *key == k {
return true
}
}
_ => {}
}
}
}
return false
}
/// Write a chunk of MIDI events to an output port.
pub fn process_out (
&self,
output: &mut MIDIChunk,
notes_on: &mut Vec<bool>,
timebase: &Arc<Timebase>,
frame0: usize,
frames: usize,
) {
let start = frame0 as f64;
let end = start + frames as f64;
let repeat = timebase.pulses_frames(self.length as f64);
let ticks = timebase.frames_to_ticks(start, end, repeat);
//panic!("{start} {end} {repeat} {ticks:?}");
for (time, tick) in ticks.iter() {
let events = self.notes.get(&(*tick as usize));
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);
}
}
}
}
/// Read a chunk of MIDI events from an input port.
pub fn process_in (
&mut self,
input: ::jack::MidiIter,
notes_on: &mut Vec<bool>,
mut monitor: Option<&mut MIDIChunk>,
record: bool,
timebase: &Arc<Timebase>,
frame0: usize,
) {
for RawMidi { time, bytes } in input {
let time = time as usize;
let pulse = timebase.frames_pulses((frame0 + time) as f64) as usize;
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 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()]);
}
}
} 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()]);
}
}
}
}
}
}
}
impl Default for Phrase {
fn default () -> Self {
Self::new("", 0, None)
}
}

View file

@ -1,99 +0,0 @@
use crate::core::*;
use super::*;
pub fn draw (
s: &Sequencer,
buf: &mut Buffer,
mut area: Rect,
) -> Usually<Rect> {
area.x = area.x + 13;
keys(s, buf, area, 0);
steps(s, buf, area, 0);
playhead(s, buf, area.x, area.y);
Ok(area)
}
pub fn steps (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
if s.sequence.is_none() {
return
}
let ppq = s.timebase.ppq() as usize;
let bg = Style::default();
let bw = bg.dim();
let wh = bg.white();
let Rect { x, y, .. } = area;
for step in s.time_start..s.time_start+area.height as usize {
let y = y - (s.time_start + step / 2) as u16;
let step = step as usize;
//buf.set_string(x + 5, y, &" ".repeat(32.max(note1-s.note_start)as usize), bg);
if step % s.time_zoom == 0 {
buf.set_string(x + 2, y, &format!("{:2} ", step + 1), Style::default());
}
for k in s.note_start..s.note_start+area.width as usize {
let key = ::midly::num::u7::from_int_lossy(k as u8);
if step % 2 == 0 {
let (a, b, c) = (
(step + 0) as usize * ppq / s.time_zoom as usize,
(step + 1) as usize * ppq / s.time_zoom as usize,
(step + 2) as usize * ppq / s.time_zoom as usize,
);
let phrase = &s.phrases[s.sequence.unwrap()];
let (character, style) = match (
phrase.contains_note_on(key, a, b),
phrase.contains_note_on(key, b, c),
) {
(true, true) => ("", wh),
(true, false) => ("", wh),
(false, true) => ("", wh),
(false, false) => ("·", bw),
};
character.blit(buf, x + (5 + k - s.note_start) as u16, y, Some(style));
}
}
if beat == step as usize {
buf.set_string(x + 4, y, if beat % 2 == 0 { "" } else { "" }, Style::default().yellow());
for key in s.note_start..s.note_start+area.width as usize {
let _color = if s.notes_on[key as usize] {
Style::default().red()
} else {
KEY_STYLE[key as usize % 12]
};
}
}
}
}
pub fn playhead (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16) {
let x = x + 5 + s.note_cursor as u16;
let y = y + s.time_cursor as u16 / 2;
let c = if s.time_cursor % 2 == 0 { "" } else { "" };
buf.set_string(x, y, c, Style::default());
}
pub fn keys (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
let ppq = s.timebase.ppq() as usize;
let Rect { x, y, .. } = area;
for key in s.note_start..s.note_start+area.width as usize {
let x = x + (5 + key - s.note_start) as u16;
if key % 12 == 0 {
let octave = format!("C{}", (key / 12) as i8 - 4);
buf.set_string(x, y, &octave, Style::default());
}
let mut color = KEY_STYLE[key as usize % 12];
let mut is_on = s.notes_on[key as usize];
let step = beat;
let (a, b, c) = (
(step + 0) as usize * ppq / s.time_zoom as usize,
(step + 1) as usize * ppq / s.time_zoom as usize,
(step + 2) as usize * ppq / s.time_zoom as usize,
);
let key = ::midly::num::u7::from(key as u8);
let phrase = &s.phrases[s.sequence.unwrap()];
is_on = is_on || phrase.contains_note_on(key, a, b);
is_on = is_on || phrase.contains_note_on(key, b, c);
if is_on {
color = Style::default().red();
}
buf.set_string(x, y - 1, &format!(""), color);
}
}

View file

@ -1,46 +0,0 @@
use crate::core::*;
use crate::device::*;
use crate::layout::*;
pub struct Track {
pub name: String,
pub sequencer: DynamicDevice<Sequencer>,
pub chain: DynamicDevice<Chain>,
}
impl Track {
pub fn new (
name: &str,
tempo: &Arc<Timebase>,
devices: Option<Vec<Box<dyn Device>>>,
phrases: Option<Vec<Phrase>>,
) -> Usually<Self> {
let sequencer = Sequencer::new(&name, tempo, phrases)?;
let chain = Chain::new(&name, devices)?;
{
if let (Some(output), Some(input)) = (
sequencer.midi_outs()?.get(0).clone(),
if let Some(item) = chain.state().items.get(0) {
if let Some(port) = item.midi_ins()?.get(0) {
Some(port.clone())
} else {
None
}
} else {
None
}
) {
if let Some(client) = &sequencer.client {
client.as_client().connect_ports_by_name(&output, &input)?;
}
}
}
Ok(Self { name: name.to_string(), sequencer, chain })
}
}
impl PortList for Track {
fn midi_ins (&self) -> Usually<Vec<String>> {
self.sequencer.midi_ins()
}
fn audio_outs (&self) -> Usually<Vec<String>> {
self.chain.audio_outs()
}
}

View file

@ -1,90 +0,0 @@
use crate::core::*;
use crate::layout::*;
pub struct Transport<'a> {
pub timebase: &'a Arc<Timebase>,
pub playing: TransportState,
pub record: bool,
pub overdub: bool,
pub monitor: bool,
pub frame: usize,
}
impl<'a> Render for Transport<'a> {
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Rect { x, y, width, .. } = area;
draw_play_stop(buf, x + 1, y, &self.playing);
draw_rec(buf, x + 12, y, self.record);
draw_dub(buf, x + 19, y, self.overdub);
draw_mon(buf, x + 26, y, self.monitor);
draw_bpm(buf, x + 33, y, self.timebase.bpm() as usize);
draw_timer(buf, x + width - 1, y, &self.timebase, self.frame);
Ok(Rect { x, y, width, height: 1 })
}
}
pub fn draw_timer (buf: &mut Buffer, x: u16, y: u16, timebase: &Arc<Timebase>, frame: usize) {
let ppq = timebase.ppq() as usize;
let pulse = timebase.frames_pulses(frame as f64) as usize;
let (beats, pulses) = (pulse / ppq, pulse % ppq);
let (bars, beats) = (beats / 4, beats % 4);
let usecs = timebase.frames_usecs(frame as f64) as usize;
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
let (minutes, seconds) = (seconds / 60, seconds % 60);
let timer = format!("{minutes}:{seconds:02}:{msecs:03} {}.{}.{pulses:02}",
bars as usize + 1,
beats as usize + 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 {
TransportState::Rolling => "▶ PLAYING",
TransportState::Starting => "READY ...",
TransportState::Stopped => "⏹ STOPPED",
}.blit(buf, x, y, Some(match state {
TransportState::Stopped => style.dim().bold(),
TransportState::Starting => style.not_dim().bold(),
TransportState::Rolling => style.not_dim().white().bold()
}));
}
pub fn draw_rec (buf: &mut Buffer, x: u16, y: u16, on: bool) {
"⏺ REC".blit(buf, x, y, Some(if on {
Style::default().bold().red()
} else {
Style::default().bold().dim()
}))
}
pub fn draw_dub (buf: &mut Buffer, x: u16, y: u16, on: bool) {
"⏺ DUB".blit(buf, x, y, Some(if on {
Style::default().bold().yellow()
} else {
Style::default().bold().dim()
}))
}
pub fn draw_mon (buf: &mut Buffer, x: u16, y: u16, on: bool) {
"⏺ MON".blit(buf, x, y, Some(if on {
Style::default().bold().green()
} else {
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}", bpm as usize, bpm % 1)
.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()));
}