Implement playback; I think it works!
This commit is contained in:
parent
c07837c3e2
commit
21e719c4d2
|
@ -20,6 +20,11 @@ dependencies = [
|
|||
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "0.3.3"
|
||||
|
@ -40,6 +45,16 @@ name = "cfg-if"
|
|||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.2.3"
|
||||
|
@ -100,6 +115,8 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"midir 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pancurses 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"timer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -138,6 +155,23 @@ dependencies = [
|
|||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pancurses"
|
||||
version = "0.16.1"
|
||||
|
@ -179,6 +213,14 @@ dependencies = [
|
|||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "timer"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
|
@ -233,10 +275,12 @@ dependencies = [
|
|||
[metadata]
|
||||
"checksum alsa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b4a0d4ebc8b23041c5de9bc9aee13b4bad844a589479701f31a5934cfe4aeb32"
|
||||
"checksum alsa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b0edcbbf9ef68f15ae1b620f722180b82a98b6f0628d30baa6b8d2a5abc87d58"
|
||||
"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
|
||||
"checksum bitflags 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "32866f4d103c4e438b1db1158aa1b1a80ee078e5d77a59a2f906fd62a577389c"
|
||||
"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
|
||||
"checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76"
|
||||
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
"checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01"
|
||||
"checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67"
|
||||
"checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d"
|
||||
"checksum coremidi 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae2b8d4679b3a2a92b843d4c8e4a721be038ce49fce701c46296b2713f24c814"
|
||||
|
@ -247,11 +291,14 @@ dependencies = [
|
|||
"checksum midir 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e653b919aca8b5f2697854f1819b33bfe49bcb3378abb0dbd834ce43ede9c7b1"
|
||||
"checksum ncurses 5.99.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15699bee2f37e9f8828c7b35b2bc70d13846db453f2d507713b758fabe536b82"
|
||||
"checksum nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2c5afeb0198ec7be8569d666644b574345aad2e95a53baf3a532da3e0f3fb32"
|
||||
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
|
||||
"checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4"
|
||||
"checksum pancurses 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d3058bc37c433096b2ac7afef1c5cdfae49ede0a4ffec3dfc1df1df0959d0ff0"
|
||||
"checksum pdcurses-sys 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "084dd22796ff60f1225d4eb6329f33afaf4c85419d51d440ab6b8c6f4529166b"
|
||||
"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
|
||||
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
|
||||
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
|
||||
"checksum timer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "31d42176308937165701f50638db1c31586f183f1aab416268216577aec7306b"
|
||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
||||
|
|
|
@ -9,3 +9,5 @@ edition = "2018"
|
|||
[dependencies]
|
||||
midir = "0.5"
|
||||
pancurses = "0.16"
|
||||
time = "0.1"
|
||||
timer = "0.2"
|
||||
|
|
154
src/main.rs
154
src/main.rs
|
@ -1,16 +1,62 @@
|
|||
extern crate midir;
|
||||
extern crate pancurses;
|
||||
extern crate time;
|
||||
extern crate timer;
|
||||
|
||||
use std::error::Error;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
use midir::os::unix::{VirtualInput, VirtualOutput};
|
||||
|
||||
use pancurses::{initscr, endwin, noecho, Input};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum MidiMessage {
|
||||
NoteOn(u8, u8, u8),
|
||||
NoteOff(u8, u8, u8),
|
||||
PitchBend(u8, i16),
|
||||
}
|
||||
|
||||
impl MidiMessage {
|
||||
fn from_slice(s: &[u8]) -> Option<MidiMessage> {
|
||||
if s.len() == 0 {
|
||||
return None;
|
||||
}
|
||||
let channel = s[0] & 0x0f;
|
||||
match s[0] & 0xf0 {
|
||||
0x80 => {
|
||||
if s.len() == 3 {
|
||||
Some(MidiMessage::NoteOff(channel, s[1], s[2]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
0x90 => {
|
||||
if s.len() == 3 {
|
||||
Some(MidiMessage::NoteOn(channel, s[1], s[2]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
0xE0 => {
|
||||
if s.len() == 3 {
|
||||
let lsb = s[2] as i16;
|
||||
let msb = s[1] as i16;
|
||||
Some(MidiMessage::PitchBend(channel, (msb << 8) | lsb))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum LooperState {
|
||||
Idle,
|
||||
|
@ -24,6 +70,7 @@ struct LooperInputCallback {
|
|||
outer_start: Arc<Mutex<Instant>>,
|
||||
ts_offset: u64,
|
||||
msg_buf: Arc<Mutex<Vec<(u64, Vec<u8>)>>>,
|
||||
log_msgs: Box<dyn FnMut(u64, Vec<u8>) + Send>,
|
||||
}
|
||||
|
||||
struct Looper {
|
||||
|
@ -35,7 +82,10 @@ struct Looper {
|
|||
msg_buf: Arc<Mutex<Vec<(u64, Vec<u8>)>>>,
|
||||
// for either playback or recording
|
||||
start_time: Arc<Mutex<Instant>>,
|
||||
playback_thread: Option<std::thread::Thread>,
|
||||
recording_length: u64,
|
||||
playback_structs: Option<timer::Guard>,
|
||||
playback_timer: timer::Timer,
|
||||
playback_cb: Arc<Mutex<dyn FnMut(u64, MidiMessage) + Send + 'static>>,
|
||||
}
|
||||
|
||||
impl Looper {
|
||||
|
@ -43,7 +93,10 @@ impl Looper {
|
|||
const RECORDING: usize = 1;
|
||||
const PLAYBACK: usize = 2;
|
||||
|
||||
fn new() -> Result<Looper, Box<dyn Error>> {
|
||||
fn new<F, G>(mut record_cb: F, playback_cb: G) -> Result<Looper, Box<dyn Error>>
|
||||
where F: 'static + FnMut(u64, MidiMessage) + Send,
|
||||
G: 'static + FnMut(u64, MidiMessage) + Send
|
||||
{
|
||||
let input = midir::MidiInput::new("midi looper")?;
|
||||
let output = midir::MidiOutput::new("midi looper")?;
|
||||
|
||||
|
@ -59,11 +112,11 @@ impl Looper {
|
|||
|ts, data, me| {
|
||||
{
|
||||
let mut out = me.output.lock().unwrap();
|
||||
out.send(data);
|
||||
out.send(data).unwrap();
|
||||
}
|
||||
let ts_millis = ts/1000;
|
||||
if me.state.load(Ordering::Acquire) == Looper::RECORDING {
|
||||
let msg_time = Instant::now();
|
||||
let ts_millis = ts/1000;
|
||||
|
||||
let mut buf = me.msg_buf.lock().unwrap();
|
||||
|
||||
|
@ -78,12 +131,18 @@ impl Looper {
|
|||
}
|
||||
buf.push((ts_millis - me.ts_offset, data.to_vec()));
|
||||
}
|
||||
(me.log_msgs)(ts_millis - me.ts_offset, data.to_vec());
|
||||
}, LooperInputCallback {
|
||||
output: virtual_output.clone(),
|
||||
state: cur_state.clone(),
|
||||
outer_start: start_time.clone(),
|
||||
ts_offset: 0,
|
||||
msg_buf: msg_buf.clone(),
|
||||
log_msgs: Box::new(move |ts, msg| {
|
||||
if let Some(msg) = MidiMessage::from_slice(msg.as_slice()) {
|
||||
record_cb(ts, msg);
|
||||
}
|
||||
}),
|
||||
})?;
|
||||
|
||||
Ok(Looper {
|
||||
|
@ -93,7 +152,10 @@ impl Looper {
|
|||
cur_state: cur_state.clone(),
|
||||
msg_buf: msg_buf.clone(),
|
||||
start_time: start_time.clone(),
|
||||
playback_thread: None,
|
||||
playback_structs: None,
|
||||
playback_timer: timer::Timer::new(),
|
||||
playback_cb: Arc::new(Mutex::new(playback_cb)),
|
||||
recording_length: 0,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -108,7 +170,60 @@ impl Looper {
|
|||
*start_time = now;
|
||||
},
|
||||
Looper::PLAYBACK => {
|
||||
// TODO
|
||||
let now = Instant::now();
|
||||
if self.msg_buf.lock().unwrap().len() == 0 {
|
||||
// empty buffer, no messages to send
|
||||
return Ok(());
|
||||
}
|
||||
let mut start = self.start_time.lock().unwrap();
|
||||
self.recording_length = now.duration_since(*start).as_millis() as u64;
|
||||
*start = now;
|
||||
|
||||
// setup/start playback thread
|
||||
let mut cur_tick: u64 = 0;
|
||||
let mut cur_idx = 0;
|
||||
let playback_buf = self.msg_buf.clone();
|
||||
let record_len = self.recording_length;
|
||||
let output_mutex = self.virtual_output.clone();
|
||||
let playback_cb = self.playback_cb.clone();
|
||||
let playback_guard = self.playback_timer.schedule_repeating(
|
||||
time::Duration::milliseconds(1),
|
||||
move || {
|
||||
// TODO optimize this so locks happen after
|
||||
// the messages are sent; this would remove
|
||||
// timing jitter caused by lock contention
|
||||
let buf = playback_buf.lock().unwrap();
|
||||
let (ref ts1, ref msg1) = &buf[cur_idx];
|
||||
let mut ts = ts1;
|
||||
let mut msg = msg1;
|
||||
if *ts == cur_tick {
|
||||
let mut output = output_mutex.lock().unwrap();
|
||||
while *ts == cur_tick {
|
||||
output.send(msg.as_slice()).unwrap();
|
||||
if let Some(msg) = MidiMessage::from_slice(msg) {
|
||||
let mut cb = playback_cb.lock().unwrap();
|
||||
(&mut *cb)(*ts, msg);
|
||||
}
|
||||
cur_idx += 1;
|
||||
if cur_idx == buf.len() {
|
||||
cur_idx = 0;
|
||||
break;
|
||||
}
|
||||
let (ts_next, msg_next) = &buf[cur_idx];
|
||||
ts = ts_next;
|
||||
msg = msg_next;
|
||||
}
|
||||
}
|
||||
cur_tick += 1;
|
||||
if cur_tick >= record_len {
|
||||
cur_tick = 0;
|
||||
cur_idx = 0;
|
||||
}
|
||||
});
|
||||
self.playback_structs = Some(playback_guard);
|
||||
},
|
||||
Looper::IDLE => {
|
||||
self.playback_structs = None;
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
|
@ -128,9 +243,8 @@ impl Looper {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
let mut looper = Looper::new().unwrap();
|
||||
|
||||
let window = initscr();
|
||||
|
||||
window.printw("============================\n");
|
||||
window.printw("======== MIDI LOOPER =======\n");
|
||||
window.printw("============================\n");
|
||||
|
@ -145,7 +259,20 @@ fn main() {
|
|||
window.printw("exit = Esc/Ctrl-C\n");
|
||||
window.refresh();
|
||||
window.keypad(true);
|
||||
window.nodelay(true);
|
||||
noecho();
|
||||
|
||||
let (tx, rx) = channel();
|
||||
let tx_record = tx.clone();
|
||||
let tx_playback = tx.clone();
|
||||
let mut looper = Looper::new(move |ts, msg| {
|
||||
tx_record.send((ts, msg)).unwrap();
|
||||
}, move |ts, msg| {
|
||||
tx_playback.send((ts, msg)).unwrap();
|
||||
}).unwrap();
|
||||
// TODO turn off delay, and sleep for 1 millisecond between getch calls
|
||||
// TODO call refresh once every 16 frames
|
||||
let mut count = 0;
|
||||
loop {
|
||||
match window.getch() {
|
||||
Some(Input::Character('\x1b')) => break,
|
||||
|
@ -168,10 +295,19 @@ fn main() {
|
|||
},
|
||||
Some(input) => {
|
||||
window.addstr(&format!("{:?}", input));
|
||||
window.refresh();
|
||||
},
|
||||
None => ()
|
||||
}
|
||||
while let Ok((ts, msg)) = rx.try_recv() {
|
||||
window.mvprintw(5, 0, format!("{:?} {}.{:03}", msg, ts/1000, ts%1000));
|
||||
}
|
||||
|
||||
count = (count + 1) % 16;
|
||||
if count == 0 {
|
||||
window.mv(12, 0);
|
||||
window.refresh();
|
||||
}
|
||||
thread::sleep(Duration::from_millis(1));
|
||||
}
|
||||
endwin();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue