mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
group devices
This commit is contained in:
parent
60627ac3e5
commit
5afed6f055
8 changed files with 147 additions and 267 deletions
92
src/device/looper.rs
Normal file
92
src/device/looper.rs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub struct Looper {
|
||||
exited: bool
|
||||
}
|
||||
|
||||
pub const ACTIONS: [(&'static str, &'static str);1] = [
|
||||
("Ins/Del", "Add/remove loop"),
|
||||
];
|
||||
|
||||
impl Looper {
|
||||
pub fn new () -> Result<Self, Box<dyn Error>> {
|
||||
Ok(Self { exited: false })
|
||||
}
|
||||
}
|
||||
|
||||
impl Exitable for Looper {
|
||||
fn exit (&mut self) {
|
||||
self.exited = true
|
||||
}
|
||||
fn exited (&self) -> bool {
|
||||
self.exited
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetRef for Looper {
|
||||
fn render_ref (&self, area: Rect, buf: &mut Buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render (
|
||||
state: &mut Looper,
|
||||
stdout: &mut std::io::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(0, 0))?.queue(Print(" Name Input Length Route"))?
|
||||
.queue(move_to(0, 1))?.queue(PrintStyledContent(" Metronome [ ] ████ Track 1".bold()))?
|
||||
.queue(move_to(0, 2))?.queue(PrintStyledContent(" Loop 1 [ ] ████ Track 1".bold()))?
|
||||
.queue(move_to(0, 3))?.queue(PrintStyledContent(" Loop 2 [ ] ████████ Track 2".bold()))?
|
||||
.queue(move_to(0, 4))?.queue(PrintStyledContent(" Loop 3 [ ] ████████ Track 3".bold()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl HandleInput for Looper {
|
||||
fn handle (&mut self, event: &Event) -> Result<(), Box<dyn Error>> {
|
||||
handle(self, event)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle (state: &mut Looper, event: &Event) -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct Notifications;
|
||||
|
||||
impl NotificationHandler for Notifications {
|
||||
fn thread_init (&self, _: &Client) {
|
||||
}
|
||||
|
||||
fn shutdown (&mut self, status: ClientStatus, reason: &str) {
|
||||
}
|
||||
|
||||
fn freewheel (&mut self, _: &Client, is_enabled: bool) {
|
||||
}
|
||||
|
||||
fn sample_rate (&mut self, _: &Client, _: Frames) -> Control {
|
||||
Control::Quit
|
||||
}
|
||||
|
||||
fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) {
|
||||
}
|
||||
|
||||
fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) {
|
||||
}
|
||||
|
||||
fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) {
|
||||
}
|
||||
|
||||
fn graph_reorder (&mut self, _: &Client) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
fn xrun (&mut self, _: &Client) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
307
src/device/mixer.rs
Normal file
307
src/device/mixer.rs
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
// 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 Mixer {
|
||||
exited: bool,
|
||||
jack: Jack<Notifications>,
|
||||
tracks: Vec<Track>,
|
||||
selected_track: usize,
|
||||
selected_column: usize,
|
||||
}
|
||||
|
||||
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 Mixer {
|
||||
|
||||
pub fn new () -> Result<Self, Box<dyn Error>> {
|
||||
let (client, status) = Client::new(
|
||||
"bloop-mixer",
|
||||
ClientOptions::NO_START_SERVER
|
||||
)?;
|
||||
let jack = client.activate_async(
|
||||
Notifications,
|
||||
ClosureProcessHandler::new(Box::new(
|
||||
move |_: &Client, _: &ProcessScope| -> Control {
|
||||
Control::Continue
|
||||
}) as Box<dyn FnMut(&Client, &ProcessScope)->Control + Send>
|
||||
)
|
||||
)?;
|
||||
Ok(Self {
|
||||
exited: false,
|
||||
selected_column: 0,
|
||||
selected_track: 1,
|
||||
tracks: vec![
|
||||
Track::new(&jack.as_client(), 1, "Kick")?,
|
||||
Track::new(&jack.as_client(), 1, "Snare")?,
|
||||
Track::new(&jack.as_client(), 2, "Hihats")?,
|
||||
Track::new(&jack.as_client(), 2, "Sample")?,
|
||||
Track::new(&jack.as_client(), 2, "Bus 1")?,
|
||||
Track::new(&jack.as_client(), 2, "Bus 2")?,
|
||||
Track::new(&jack.as_client(), 2, "Mix")?,
|
||||
],
|
||||
jack,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 Exitable for Mixer {
|
||||
fn exit (&mut self) {
|
||||
self.exited = true
|
||||
}
|
||||
fn exited (&self) -> bool {
|
||||
self.exited
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetRef for Mixer {
|
||||
fn render_ref (&self, area: Rect, buf: &mut Buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render (
|
||||
state: &mut Mixer,
|
||||
stdout: &mut Stdout,
|
||||
mut offset: (u16, u16)
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
render_table(state, stdout, offset)?;
|
||||
render_meters(state, stdout, offset)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_table (
|
||||
state: &mut Mixer,
|
||||
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, 0)
|
||||
)?.queue(
|
||||
Print(" Name Gain FX1 Pan Level FX2 Route")
|
||||
)?;
|
||||
for (i, track) in state.tracks.iter().enumerate() {
|
||||
let row = (i + 1) as u16;
|
||||
for (j, (column, field)) in [
|
||||
(0, format!(" {:7} ", 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() {
|
||||
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()))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_meters (
|
||||
state: &mut Mixer,
|
||||
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, 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(())
|
||||
}
|
||||
|
||||
impl HandleInput for Mixer {
|
||||
fn handle (&mut self, event: &Event) -> Result<(), Box<dyn Error>> {
|
||||
handle(self, event)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle (state: &mut Mixer, event: &Event) -> Result<(), Box<dyn Error>> {
|
||||
|
||||
if let Event::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);
|
||||
},
|
||||
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);
|
||||
},
|
||||
KeyCode::Left => {
|
||||
if state.selected_column == 0 {
|
||||
state.selected_column = 6
|
||||
} else {
|
||||
state.selected_column = state.selected_column - 1;
|
||||
}
|
||||
},
|
||||
KeyCode::Right => {
|
||||
if state.selected_column == 6 {
|
||||
state.selected_column = 0
|
||||
} else {
|
||||
state.selected_column = state.selected_column + 1;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
println!("\n{event:?}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct Notifications;
|
||||
|
||||
impl NotificationHandler for Notifications {
|
||||
fn thread_init (&self, _: &Client) {
|
||||
}
|
||||
|
||||
fn shutdown (&mut self, status: ClientStatus, reason: &str) {
|
||||
}
|
||||
|
||||
fn freewheel (&mut self, _: &Client, is_enabled: bool) {
|
||||
}
|
||||
|
||||
fn sample_rate (&mut self, _: &Client, _: Frames) -> Control {
|
||||
Control::Quit
|
||||
}
|
||||
|
||||
fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) {
|
||||
}
|
||||
|
||||
fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) {
|
||||
}
|
||||
|
||||
fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) {
|
||||
}
|
||||
|
||||
fn graph_reorder (&mut self, _: &Client) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
fn xrun (&mut self, _: &Client) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//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)
|
||||
//}
|
||||
//}
|
||||
273
src/device/sampler.rs
Normal file
273
src/device/sampler.rs
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const ACTIONS: [(&'static str, &'static str);2] = [
|
||||
("Enter", "Play sample"),
|
||||
("Ins/Del", "Add/remove sample"),
|
||||
];
|
||||
|
||||
pub struct Sampler {
|
||||
exited: Arc<AtomicBool>,
|
||||
jack: Jack<Notifications>,
|
||||
samples: Arc<Mutex<Vec<Sample>>>,
|
||||
selected_sample: usize,
|
||||
selected_column: usize,
|
||||
}
|
||||
|
||||
impl Sampler {
|
||||
pub fn new () -> Result<Self, Box<dyn Error>> {
|
||||
let exited = Arc::new(AtomicBool::new(false));
|
||||
let (client, status) = Client::new(
|
||||
"Sampler#000",
|
||||
ClientOptions::NO_START_SERVER
|
||||
)?;
|
||||
let samples = vec![
|
||||
Sample::new("Kick", &client, 1, 35)?,
|
||||
Sample::new("Snare", &client, 1, 38)?,
|
||||
];
|
||||
let samples = Arc::new(Mutex::new(samples));
|
||||
let input = client.register_port("trigger", ::jack::MidiIn::default())?;
|
||||
let handler: BoxedProcessHandler = Box::new({
|
||||
let exited = exited.clone();
|
||||
let samples = samples.clone();
|
||||
Box::new(move |_: &Client, scope: &ProcessScope| -> Control {
|
||||
if exited.fetch_and(true, Ordering::Relaxed) {
|
||||
return Control::Quit
|
||||
}
|
||||
let mut samples = samples.lock().unwrap();
|
||||
for event in input.iter(scope) {
|
||||
let len = 3.min(event.bytes.len());
|
||||
let mut data = [0; 3];
|
||||
data[..len].copy_from_slice(&event.bytes[..len]);
|
||||
if (data[0] >> 4) == 0b1001 { // note on
|
||||
let channel = data[0] & 0b00001111;
|
||||
let note = data[1];
|
||||
let velocity = data[2];
|
||||
for sample in samples.iter_mut() {
|
||||
if /*sample.trigger.0 == channel &&*/ sample.trigger.1 == note {
|
||||
sample.play(velocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
for sample in samples.iter_mut() {
|
||||
if let Some(playing) = sample.playing {
|
||||
for (index, value) in sample.port.as_mut_slice(scope).iter_mut().enumerate() {
|
||||
*value = *sample.data[0].get(playing + index).unwrap_or(&0f32);
|
||||
}
|
||||
if playing + scope.n_frames() as usize > sample.data[0].len() {
|
||||
sample.playing = None
|
||||
} else {
|
||||
sample.playing = Some(playing + scope.n_frames() as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Control::Continue
|
||||
})
|
||||
});
|
||||
Ok(Self {
|
||||
exited,
|
||||
selected_sample: 0,
|
||||
selected_column: 0,
|
||||
samples,
|
||||
jack: client.activate_async(
|
||||
self::Notifications,
|
||||
ClosureProcessHandler::new(handler)
|
||||
)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Sample {
|
||||
port: Port<AudioOut>,
|
||||
name: String,
|
||||
rate: u32,
|
||||
gain: f64,
|
||||
channels: u8,
|
||||
data: Vec<Vec<f32>>,
|
||||
trigger: (u8, u8),
|
||||
playing: Option<usize>,
|
||||
}
|
||||
|
||||
impl Sample {
|
||||
|
||||
pub fn new (name: &str, client: &Client, channel: u8, note: u8) -> Result<Self, Box<dyn Error>> {
|
||||
Ok(Self {
|
||||
port: client.register_port(name, ::jack::AudioOut::default())?,
|
||||
name: name.into(),
|
||||
rate: 44100,
|
||||
channels: 1,
|
||||
gain: 0.0,
|
||||
data: vec![vec![1.0, 0.0, 0.0, 0.0]],
|
||||
trigger: (channel, note),
|
||||
playing: None
|
||||
})
|
||||
}
|
||||
|
||||
fn play (&mut self, velocity: u8) {
|
||||
self.playing = Some(0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Exitable for Sampler {
|
||||
fn exit (&mut self) {
|
||||
self.exited.store(true, Ordering::Relaxed)
|
||||
}
|
||||
fn exited (&self) -> bool {
|
||||
self.exited.fetch_and(true, Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetRef for Sampler {
|
||||
fn render_ref (&self, area: Rect, buf: &mut Buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render (
|
||||
state: &mut Sampler,
|
||||
stdout: &mut Stdout,
|
||||
offset: (u16, u16),
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
render_table(state, stdout, offset)?;
|
||||
render_meters(state, stdout, offset)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
|
||||
impl HandleInput for Sampler {
|
||||
fn handle (&mut self, event: &Event) -> Result<(), Box<dyn Error>> {
|
||||
handle(self, event)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle (
|
||||
state: &mut Sampler,
|
||||
event: &Event
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
|
||||
if let Event::Input(crossterm::event::Event::Key(event)) = event {
|
||||
|
||||
match event.code {
|
||||
KeyCode::Char('c') => {
|
||||
if event.modifiers == KeyModifiers::CONTROL {
|
||||
state.exit();
|
||||
}
|
||||
},
|
||||
KeyCode::Down => {
|
||||
state.selected_sample = (state.selected_sample + 1) % state.samples.lock().unwrap().len();
|
||||
println!("{}", state.selected_sample);
|
||||
},
|
||||
KeyCode::Up => {
|
||||
if state.selected_sample == 0 {
|
||||
state.selected_sample = state.samples.lock().unwrap().len() - 1;
|
||||
} else {
|
||||
state.selected_sample = state.selected_sample - 1;
|
||||
}
|
||||
println!("{}", state.selected_sample);
|
||||
},
|
||||
KeyCode::Left => {
|
||||
if state.selected_column == 0 {
|
||||
state.selected_column = 6
|
||||
} else {
|
||||
state.selected_column = state.selected_column - 1;
|
||||
}
|
||||
},
|
||||
KeyCode::Right => {
|
||||
if state.selected_column == 6 {
|
||||
state.selected_column = 0
|
||||
} else {
|
||||
state.selected_column = state.selected_column + 1;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
println!("{event:?}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub struct Notifications;
|
||||
|
||||
impl NotificationHandler for Notifications {
|
||||
fn thread_init (&self, _: &Client) {
|
||||
}
|
||||
|
||||
fn shutdown (&mut self, status: ClientStatus, reason: &str) {
|
||||
}
|
||||
|
||||
fn freewheel (&mut self, _: &Client, is_enabled: bool) {
|
||||
}
|
||||
|
||||
fn sample_rate (&mut self, _: &Client, _: Frames) -> Control {
|
||||
Control::Quit
|
||||
}
|
||||
|
||||
fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) {
|
||||
}
|
||||
|
||||
fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) {
|
||||
}
|
||||
|
||||
fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) {
|
||||
}
|
||||
|
||||
fn graph_reorder (&mut self, _: &Client) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
fn xrun (&mut self, _: &Client) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
422
src/device/sequencer.rs
Normal file
422
src/device/sequencer.rs
Normal file
|
|
@ -0,0 +1,422 @@
|
|||
use crate::prelude::*;
|
||||
use ratatui::style::Stylize;
|
||||
|
||||
pub const ACTIONS: [(&'static str, &'static str);4] = [
|
||||
("+/-", "Zoom"),
|
||||
("A/D", "Add/delete note"),
|
||||
("]/[", "Duration"),
|
||||
("CapsLock", "Auto advance"),
|
||||
];
|
||||
|
||||
pub struct Sequencer {
|
||||
name: Arc<str>,
|
||||
exited: Arc<AtomicBool>,
|
||||
sequence: Arc<Mutex<Vec<Vec<Option<Event>>>>>,
|
||||
cursor: (u16, u16, u16),
|
||||
timesig: (f32, f32),
|
||||
pub jack_client: Jack<Notifications>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Event {
|
||||
NoteOn(u8, u8),
|
||||
NoteOff(u8)
|
||||
}
|
||||
|
||||
impl Sequencer {
|
||||
|
||||
pub fn new (name: Option<&str>) -> Result<Self, Box<dyn Error>> {
|
||||
let exited = Arc::new(AtomicBool::new(false));
|
||||
let name = name.unwrap_or("sequencer");
|
||||
let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
||||
let mut port = client.register_port("sequence", ::jack::MidiOut::default())?;
|
||||
let sequence: Arc<Mutex<Vec<Vec<Option<Event>>>>> = Arc::new(Mutex::new(vec![vec![None;64];128]));
|
||||
let beats = 4;
|
||||
let steps = 16;
|
||||
let bpm = 120.0;
|
||||
let rate = 44100; // Hz
|
||||
let frame = 1f64 / rate as f64; // msec
|
||||
let buf = 512; // frames
|
||||
let t_beat = 60.0 / bpm; // msec
|
||||
let t_loop = t_beat * beats as f64; // msec
|
||||
let t_step = t_beat / steps as f64; // msec
|
||||
let mut step_frames = vec![];
|
||||
for step in 0..beats*steps {
|
||||
let step_index = (step as f64 * t_step / frame) as usize;
|
||||
step_frames.push(step_index);
|
||||
}
|
||||
let loop_frames = (t_loop*rate as f64) as usize;
|
||||
let mut frame_steps: Vec<Option<usize>> = vec![None;loop_frames];
|
||||
for (index, frame) in step_frames.iter().enumerate() {
|
||||
frame_steps[*frame] = Some(index);
|
||||
}
|
||||
Ok(Self {
|
||||
name: name.into(),
|
||||
exited: exited.clone(),
|
||||
sequence: sequence.clone(),
|
||||
cursor: (11, 0, 0),
|
||||
timesig: (4.0, 4.0),
|
||||
jack_client: crate::engine::activate_jack_client(
|
||||
client,
|
||||
Notifications,
|
||||
Box::new(move |client: &Client, scope: &ProcessScope| -> Control {
|
||||
if exited.fetch_and(true, Ordering::Relaxed) {
|
||||
return Control::Quit
|
||||
}
|
||||
if client.transport().query_state().unwrap() == TransportState::Rolling {
|
||||
let chunk_start = scope.last_frame_time();
|
||||
let chunk_size = scope.n_frames();
|
||||
let chunk_end = chunk_start + chunk_size;
|
||||
let start_looped = chunk_start as usize % loop_frames;
|
||||
let end_looped = chunk_end as usize % loop_frames;
|
||||
let mut writer = port.writer(scope);
|
||||
let sequence = sequence.lock().unwrap();
|
||||
for frame in 0..chunk_size {
|
||||
let value = frame_steps[(start_looped + frame as usize) % loop_frames];
|
||||
if let Some(step) = value {
|
||||
for track in sequence.iter() {
|
||||
for event in track[step].iter() {
|
||||
writer.write(&::jack::RawMidi {
|
||||
time: frame as u32,
|
||||
bytes: &match event {
|
||||
Event::NoteOn(pitch, velocity) => [
|
||||
0b10010000,
|
||||
*pitch,
|
||||
*velocity
|
||||
],
|
||||
Event::NoteOff(pitch) => [
|
||||
0b10000000,
|
||||
*pitch,
|
||||
0b00000000
|
||||
],
|
||||
}
|
||||
}).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Control::Continue
|
||||
})
|
||||
)?
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Exitable for Sequencer {
|
||||
fn exit (&mut self) {
|
||||
self.exited.store(true, Ordering::Relaxed)
|
||||
}
|
||||
fn exited (&self) -> bool {
|
||||
self.exited.fetch_and(true, Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
impl HandleInput for Sequencer {
|
||||
fn handle (&mut self, event: &crate::engine::Event) -> Result<(), Box<dyn Error>> {
|
||||
handle(self, event)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle (state: &mut Sequencer, event: &crate::engine::Event) -> Result<(), Box<dyn Error>> {
|
||||
|
||||
if let crate::engine::Event::Input(crossterm::event::Event::Key(event)) = event {
|
||||
match event.code {
|
||||
KeyCode::Down => {
|
||||
state.cursor.0 = if state.cursor.0 >= 23 {
|
||||
0
|
||||
} else {
|
||||
state.cursor.0 + 1
|
||||
}
|
||||
},
|
||||
KeyCode::Up => {
|
||||
state.cursor.0 = if state.cursor.0 == 0 {
|
||||
23
|
||||
} else {
|
||||
state.cursor.0 - 1
|
||||
}
|
||||
},
|
||||
KeyCode::Left => {
|
||||
state.cursor.1 = if state.cursor.1 == 0 {
|
||||
63
|
||||
} else {
|
||||
state.cursor.1 - 1
|
||||
}
|
||||
},
|
||||
KeyCode::Right => {
|
||||
state.cursor.1 = if state.cursor.1 == 63 {
|
||||
0
|
||||
} else {
|
||||
state.cursor.1 + 1
|
||||
}
|
||||
},
|
||||
KeyCode::Char('[') => {
|
||||
if state.cursor.2 > 0 {
|
||||
state.cursor.2 = state.cursor.2 - 1
|
||||
}
|
||||
},
|
||||
KeyCode::Char(']') => {
|
||||
state.cursor.2 = state.cursor.2 + 1
|
||||
},
|
||||
KeyCode::Char('a') => {
|
||||
let row = state.cursor.0 as usize;
|
||||
let step = state.cursor.1 as usize;
|
||||
let duration = state.cursor.2 as usize;
|
||||
let mut sequence = state.sequence.lock().unwrap();
|
||||
sequence[row][step] = Some(self::Event::NoteOn(48 - row as u8, 128));
|
||||
if state.cursor.2 > 0 {
|
||||
sequence[row][step + duration] = Some(self::Event::NoteOff(35));
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
println!("{event:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const NOTE_NAMES: [&'static str;12] = [
|
||||
"C ", "C#", "D ", "D#", "E ", "F ", "F#", "G ", "G#", "A ", "A#", "B ",
|
||||
];
|
||||
|
||||
const KEYS: [&'static str; 6] = [
|
||||
"▀", "▀", "▀", "█", "▄", "▄",
|
||||
];
|
||||
|
||||
impl WidgetRef for Sequencer {
|
||||
fn render_ref (&self, area: Rect, buf: &mut Buffer) {
|
||||
draw_leaf(buf, area, 1, 0, &format!("{} ", &self.name));
|
||||
draw_leaf(buf, area, 3, 0, "Channel: 01");
|
||||
draw_leaf(buf, area, 5, 0, "Zoom: 1/64");
|
||||
draw_leaf(buf, area, 7, 0, "Rate: 1/1");
|
||||
|
||||
draw_leaf(buf, area, 10, 0, "Inputs: ");
|
||||
draw_leaf(buf, area, 13, 0, "Outputs: ");
|
||||
{
|
||||
let mut area = area.clone();
|
||||
area.height = 18;
|
||||
draw_box(buf, area);
|
||||
}
|
||||
{
|
||||
let mut area = area.inner(&Margin { horizontal: 1, vertical: 3, });
|
||||
area.x = area.x + 14;
|
||||
draw_sequence_keys(area, buf, &self.jack_client.as_client().transport().query().unwrap(), &self.sequence);
|
||||
draw_sequence_header(area, buf);
|
||||
draw_sequence_cursor(area, buf, self.cursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_sequence_header (
|
||||
area: Rect, buf: &mut Buffer
|
||||
) {
|
||||
buf.set_string(area.x + 3, area.y, "╭1.1.", Style::default().dim());
|
||||
buf.set_string(area.x + 3 + 16, area.y, "╭1.2.", Style::default().dim());
|
||||
buf.set_string(area.x + 3 + 32, area.y, "╭1.3.", Style::default().dim());
|
||||
buf.set_string(area.x + 3 + 48, area.y, "╭1.4.", Style::default().dim());
|
||||
}
|
||||
|
||||
fn draw_sequence_keys (
|
||||
area: Rect,
|
||||
buf: &mut Buffer,
|
||||
transport: &::jack::TransportStatePosition,
|
||||
sequence: &Arc<Mutex<Vec<Vec<Option<self::Event>>>>>
|
||||
) {
|
||||
//buf.set_string(area.x + 2, area.y, "╭", Style::default().dim());
|
||||
//buf.set_string(area.x + 2, area.y + 13, "╰", Style::default().dim());
|
||||
buf.set_string(area.x + 2 + 65, area.y, "╮", Style::default().dim());
|
||||
buf.set_string(area.x + 2 + 65, area.y + 13, "╯", Style::default().dim());
|
||||
let frame = transport.pos.frame();
|
||||
let rate = transport.pos.frame_rate().unwrap();
|
||||
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;
|
||||
let sequence = sequence.lock().unwrap();
|
||||
buf.set_string(
|
||||
area.x + area.width - 25, area.y - 2, format!("{bars:04}:{beat:02}.{:02}", (beat_sub * 16.0) as u32), Style::default()
|
||||
);
|
||||
for key in 0..12 {
|
||||
buf.set_string(area.x, area.y + 1 + key, KEYS[(key % 6) as usize],
|
||||
Style::default().black());
|
||||
buf.set_string(area.x + 1, area.y + 1 + key, "█",
|
||||
Style::default().black());
|
||||
for step in 0..64 {
|
||||
let bg = if step as u32 == (beat - 1) * 16 + (beat_sub * 16.0) as u32 {
|
||||
ratatui::style::Color::Black
|
||||
} else {
|
||||
ratatui::style::Color::Reset
|
||||
};
|
||||
let top = sequence[(key * 2) as usize][step].is_some();
|
||||
let bottom = sequence[(key * 2 + 1) as usize][step].is_some();
|
||||
match (top, bottom) {
|
||||
(true, true) => {
|
||||
buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "█",
|
||||
Style::default().yellow().bold().bg(bg));
|
||||
},
|
||||
(true, false) => {
|
||||
buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "▀",
|
||||
Style::default().yellow().bold().bg(bg));
|
||||
},
|
||||
(false, true) => {
|
||||
buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "▄",
|
||||
Style::default().yellow().bold().bg(bg));
|
||||
},
|
||||
(false, false) => if step % 16 == 0 {
|
||||
buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "┊",
|
||||
Style::default().black().bg(bg))
|
||||
} else {
|
||||
buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "·",
|
||||
Style::default().black().bg(bg))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for step in 0..64 {
|
||||
if step % 8 == 0 {
|
||||
buf.set_string(area.x + 3 + step as u16, area.y + 1 + 12, [
|
||||
"|A", "|B", "|C", "|D", "|E", "|F", "|G", "|H"
|
||||
][step / 8 as usize], Style::default().dim())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_sequence_cursor (
|
||||
area: Rect, buf: &mut Buffer, cursor: (u16, u16, u16)
|
||||
) {
|
||||
let cursor_character = match cursor.0 % 2 {
|
||||
0 => "▀",
|
||||
1 => "▄",
|
||||
_ => unreachable!()
|
||||
};
|
||||
let cursor_y = cursor.0 / 2;
|
||||
buf.set_string(area.x + cursor.1 + 3, area.y + 1 + cursor_y, if cursor.2 == 0 {
|
||||
cursor_character.into()
|
||||
} else {
|
||||
cursor_character.repeat(cursor.2 as usize)
|
||||
}, Style::default().yellow());
|
||||
}
|
||||
|
||||
pub struct Notifications;
|
||||
|
||||
impl NotificationHandler for Notifications {
|
||||
fn thread_init (&self, _: &Client) {
|
||||
}
|
||||
|
||||
fn shutdown (&mut self, status: ClientStatus, reason: &str) {
|
||||
}
|
||||
|
||||
fn freewheel (&mut self, _: &Client, is_enabled: bool) {
|
||||
}
|
||||
|
||||
fn sample_rate (&mut self, _: &Client, _: Frames) -> Control {
|
||||
Control::Quit
|
||||
}
|
||||
|
||||
fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) {
|
||||
}
|
||||
|
||||
fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) {
|
||||
}
|
||||
|
||||
fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) {
|
||||
}
|
||||
|
||||
fn graph_reorder (&mut self, _: &Client) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
fn xrun (&mut self, _: &Client) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_midi_frames () {
|
||||
let beats = 4;
|
||||
let steps = 16;
|
||||
let bpm = 120;
|
||||
let rate = 44100; // Hz
|
||||
let frame = 1f64 / rate as f64; // msec
|
||||
let buf = 512; // frames
|
||||
let t_beat = 60.0 / bpm as f64; // msec
|
||||
let t_loop = t_beat * beats as f64; // msec
|
||||
let t_step = t_beat / steps as f64; // msec
|
||||
|
||||
let assign = |chunk: usize| {
|
||||
let start = chunk * buf; // frames
|
||||
let end = (chunk + 1) * buf; // frames
|
||||
println!("{chunk}: {start} .. {end}");
|
||||
let mut steps: Vec<(usize, usize, f64)> = vec![];
|
||||
for frame_index in start..end {
|
||||
let frame_msec = frame_index as f64 * frame;
|
||||
let offset = (frame_msec * 1000.0) % (t_step * 1000.0);
|
||||
if offset < 0.1 {
|
||||
let time = frame_index - start;
|
||||
let step_index = (frame_msec % t_loop / t_step) as usize;
|
||||
println!("{chunk}: {frame_index} ({time}) -> {step_index} ({frame_msec} % {t_step} = {offset})");
|
||||
steps.push((time, step_index, offset));
|
||||
}
|
||||
}
|
||||
steps
|
||||
};
|
||||
|
||||
for chunk in 0..10 {
|
||||
let chunk = assign(chunk);
|
||||
//println!("{chunk} {:#?}", assign(chunk));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_midi_frames_2 () {
|
||||
let beats = 4;
|
||||
let steps = 16;
|
||||
let bpm = 120;
|
||||
let rate = 44100; // Hz
|
||||
let frame = 1f64 / rate as f64; // msec
|
||||
let buf = 512; // frames
|
||||
let t_beat = 60.0 / bpm as f64; // msec
|
||||
let t_loop = t_beat * beats as f64; // msec
|
||||
let t_step = t_beat / steps as f64; // msec
|
||||
let mut step_frames = vec![];
|
||||
for step in 0..beats*steps {
|
||||
let step_index = (step as f64 * t_step / frame) as usize;
|
||||
step_frames.push(step_index);
|
||||
}
|
||||
let loop_frames = (t_loop*rate as f64) as usize;
|
||||
let mut frame_steps: Vec<Option<usize>> = vec![None;loop_frames];
|
||||
for (index, frame) in step_frames.iter().enumerate() {
|
||||
println!("{index} {frame}");
|
||||
frame_steps[*frame] = Some(index);
|
||||
}
|
||||
let assign = |chunk: usize| {
|
||||
let (start, end) = (chunk * buf, (chunk + 1) * buf); // frames
|
||||
let (start_looped, end_looped) = (start % loop_frames, end % loop_frames);
|
||||
println!("{chunk}: {start} .. {end} ({start_looped} .. {end_looped})");
|
||||
let mut steps: Vec<Option<usize>> = vec![None;buf];
|
||||
for frame in 0..buf {
|
||||
let value = frame_steps[(start_looped + frame) as usize % loop_frames];
|
||||
if value.is_some() { println!("{frame:03} = {value:?}, ") };
|
||||
steps[frame as usize] = value;
|
||||
}
|
||||
steps
|
||||
};
|
||||
for chunk in 0..1000 {
|
||||
let chunk = assign(chunk);
|
||||
//println!("{chunk} {:#?}", assign(chunk));
|
||||
}
|
||||
}
|
||||
}
|
||||
242
src/device/transport.rs
Normal file
242
src/device/transport.rs
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const ACTIONS: [(&'static str, &'static str);4] = [
|
||||
("?", "Toggle help"),
|
||||
("(Shift-)Tab", "Switch pane"),
|
||||
("Arrows", "Navigate"),
|
||||
("(Shift-)Space", "⯈ Play/pause"),
|
||||
];
|
||||
|
||||
pub struct Transport {
|
||||
exited: bool,
|
||||
title: String,
|
||||
transport: ::jack::Transport,
|
||||
bpm: f64,
|
||||
timesig: (f32, f32),
|
||||
}
|
||||
|
||||
impl Transport {
|
||||
pub fn new (client: &Client) -> Result<Self, Box<dyn Error>> {
|
||||
let transport = client.transport();
|
||||
Ok(Self {
|
||||
exited: false,
|
||||
title: String::from("Untitled project"),
|
||||
bpm: 113.0,
|
||||
timesig: (4.0, 4.0),
|
||||
transport,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn play_from_start_or_stop_and_rewind (&mut self) {
|
||||
}
|
||||
|
||||
pub fn play_or_pause (&mut self) -> Result<(), Box<dyn Error>> {
|
||||
match self.transport.query_state()? {
|
||||
TransportState::Stopped => self.play(),
|
||||
TransportState::Rolling => self.stop(),
|
||||
_ => Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play (&mut self) -> Result<(), Box<dyn Error>> {
|
||||
Ok(self.transport.start()?)
|
||||
}
|
||||
|
||||
pub fn stop (&mut self) -> Result<(), Box<dyn Error>> {
|
||||
Ok(self.transport.stop()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Exitable for Transport {
|
||||
fn exit (&mut self) {
|
||||
self.exited = true
|
||||
}
|
||||
fn exited (&self) -> bool {
|
||||
self.exited
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetRef for Transport {
|
||||
fn render_ref (&self, area: Rect, buf: &mut Buffer) {
|
||||
use ratatui::{layout::*, widgets::*, style::Stylize};
|
||||
draw_leaf(buf, area, 0, 0, "REC");
|
||||
draw_leaf(buf, area, 0, 5, "DUB");
|
||||
draw_leaf(buf, area, 0, 10, "STOP");
|
||||
draw_leaf(buf, area, 0, 16, "PLAY/PAUSE");
|
||||
draw_leaf(buf, area, 0, 28, "START");
|
||||
draw_leaf(buf, area, 0, 35, "Project: Witty Gerbil - Sha Na Na ");
|
||||
let position = self.transport.query().expect("failed to query transport");
|
||||
draw_leaf(buf, area, 2, 0, &format!(
|
||||
"BPM {:03}.{:03}",
|
||||
self.bpm as u64,
|
||||
((self.bpm % 1.0) * 1000.0) as u64
|
||||
));
|
||||
//let bbt = position.pos.bbt().map(|mut bbt|*bbt
|
||||
//.with_bpm(self.bpm)
|
||||
//.with_timesig(self.timesig.0, self.timesig.1));
|
||||
//.unwrap();
|
||||
draw_leaf(buf, area, 2, 13, &format!("BBT {}.{}.{}",
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
));
|
||||
let rate = position.pos.frame_rate().unwrap();
|
||||
let frame = position.pos.frame();
|
||||
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, 2, 27, &format!(
|
||||
"Time {:02}:{:02}:{:02}.{:03}",
|
||||
hours as u64,
|
||||
minutes as u64,
|
||||
seconds as u64,
|
||||
(msec * 1000.0) as u64
|
||||
));
|
||||
draw_leaf(buf, area, 2, 46, &format!("Rate {:>6}Hz", rate));
|
||||
draw_leaf(buf, area, 2, 61, &format!("Frame {:>12}", frame));
|
||||
//Line::from("Project:").render(area, buf);
|
||||
//if let Ok(position) = self.transport.query() {
|
||||
//let frame = position.pos.frame();
|
||||
//let rate = position.pos.frame_rate();
|
||||
//let bbt = position.pos.bbt().map(|mut bbt|*bbt
|
||||
//.with_bpm(self.bpm)
|
||||
//.with_timesig(self.timesig.0, self.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(())
|
||||
//}
|
||||
|
||||
impl HandleInput for self::Transport {
|
||||
fn handle (&mut self, event: &Event) -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct Notifications;
|
||||
|
||||
impl NotificationHandler for Notifications {
|
||||
fn thread_init (&self, _: &Client) {
|
||||
}
|
||||
|
||||
fn shutdown (&mut self, status: ClientStatus, reason: &str) {
|
||||
}
|
||||
|
||||
fn freewheel (&mut self, _: &Client, is_enabled: bool) {
|
||||
}
|
||||
|
||||
fn sample_rate (&mut self, _: &Client, _: Frames) -> Control {
|
||||
Control::Quit
|
||||
}
|
||||
|
||||
fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) {
|
||||
}
|
||||
|
||||
fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) {
|
||||
}
|
||||
|
||||
fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) {
|
||||
}
|
||||
|
||||
fn graph_reorder (&mut self, _: &Client) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
fn xrun (&mut self, _: &Client) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue