Implement recording callback and setup, TODO verify it works, start work on playback

This commit is contained in:
Kelvin Ly 2019-12-20 09:08:59 -05:00
commit c07837c3e2
4 changed files with 452 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
**/*.rs.bk

262
Cargo.lock generated Normal file
View File

@ -0,0 +1,262 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "alsa"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"alsa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "alsa-sys"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cc"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "core-foundation"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "core-foundation-sys"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "coremidi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"coremidi-sys 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "coremidi-sys"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "libc"
version = "0.2.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "log"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "memalloc"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "midi-looper"
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)",
]
[[package]]
name = "midir"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"alsa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"coremidi 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"memalloc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winmm-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ncurses"
version = "5.99.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nix"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pancurses"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"ncurses 5.99.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pdcurses-sys 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winreg 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pdcurses-sys"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pkg-config"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "redox_syscall"
version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "time"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-build"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winmm-sys"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winreg"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[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 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 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"
"checksum coremidi-sys 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "07f05827cebb30dcd539ff1ac9bf6764f574a15fa147f8572f99d7617142f95e"
"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
"checksum memalloc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df39d232f5c40b0891c10216992c2f250c054105cb1e56f0fc9032db6203ecc1"
"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 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 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"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
"checksum winmm-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "20a57a816b63ca4bf31aec70b4c334be13c4b73a30ab5b546135041627866035"
"checksum winreg 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a27a759395c1195c4cc5cda607ef6f8f6498f64e78f7900f5de0a127a424704a"

11
Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "midi-looper"
version = "0.1.0"
authors = ["Kelvin Ly <kelvin.ly1618@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
midir = "0.5"
pancurses = "0.16"

177
src/main.rs Normal file
View File

@ -0,0 +1,177 @@
extern crate midir;
extern crate pancurses;
use std::error::Error;
use std::thread;
use std::time::{Duration, Instant};
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicUsize, Ordering};
use midir::os::unix::{VirtualInput, VirtualOutput};
use pancurses::{initscr, endwin, noecho, Input};
#[derive(Clone, Copy, Debug)]
enum LooperState {
Idle,
Recording,
Playing
}
struct LooperInputCallback {
output: Arc<Mutex<midir::MidiOutputConnection>>,
state: Arc<AtomicUsize>,
outer_start: Arc<Mutex<Instant>>,
ts_offset: u64,
msg_buf: Arc<Mutex<Vec<(u64, Vec<u8>)>>>,
}
struct Looper {
virtual_input: Arc<midir::MidiInputConnection<LooperInputCallback>>,
virtual_output: Arc<Mutex<midir::MidiOutputConnection>>,
cur_state: Arc<AtomicUsize>,
// (time in milliseconds, msg)
msg_buf: Arc<Mutex<Vec<(u64, Vec<u8>)>>>,
// for either playback or recording
start_time: Arc<Mutex<Instant>>,
playback_thread: Option<std::thread::Thread>,
}
impl Looper {
const IDLE: usize = 0;
const RECORDING: usize = 1;
const PLAYBACK: usize = 2;
fn new() -> Result<Looper, Box<dyn Error>> {
let input = midir::MidiInput::new("midi looper")?;
let output = midir::MidiOutput::new("midi looper")?;
let cur_state = Arc::new(AtomicUsize::new(Looper::IDLE));
let msg_buf = Arc::new(Mutex::new(Vec::new()));
let start_time = Arc::new(Mutex::new(Instant::now()));
let output_unboxed = output.create_virtual("looper output")?;
let virtual_output = Arc::new(Mutex::new(output_unboxed));
let virtual_input = input.create_virtual(
"looper input",
|ts, data, me| {
{
let mut out = me.output.lock().unwrap();
out.send(data);
}
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();
// new recording session; find the new offset
if buf.len() == 0 {
let record_start = me.outer_start.lock().unwrap();
let time_since_start = msg_time.duration_since(*record_start);
// let's assume the user pressed their first note less than
// 2^64 milliseconds after starting recording
let time_millis = time_since_start.as_millis() as u64;
me.ts_offset = ts_millis - time_millis;
}
buf.push((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(),
})?;
Ok(Looper {
virtual_input: Arc::new(virtual_input),
virtual_output,
cur_state: cur_state.clone(),
msg_buf: msg_buf.clone(),
start_time: start_time.clone(),
playback_thread: None,
})
}
fn toggle_state(&mut self) -> Result<(), Box<dyn Error>>{
let now = Instant::now();
let next_state = (self.cur_state.load(Ordering::Acquire) + 1) % 3;
match next_state {
Looper::RECORDING => {
let mut buf = self.msg_buf.lock().unwrap();
let mut start_time = self.start_time.lock().unwrap();
buf.clear();
*start_time = now;
},
Looper::PLAYBACK => {
// TODO
},
_ => ()
}
self.cur_state.store(next_state, Ordering::Release);
Ok(())
}
fn state(&self) -> LooperState {
let cur_state = self.cur_state.load(Ordering::Acquire);
match cur_state {
0 => LooperState::Idle,
1 => LooperState::Recording,
2 => LooperState::Playing,
_ => unreachable!("invalid looper state reached {}", cur_state)
}
}
}
fn main() {
let mut looper = Looper::new().unwrap();
let window = initscr();
window.printw("============================\n");
window.printw("======== MIDI LOOPER =======\n");
window.printw("============================\n");
window.printw("\n");
window.printw("IDLE\n");
window.printw("\n");
window.printw("\n");
window.printw("\n");
window.printw("\n");
window.printw("start/stop = space\n");
window.printw("save = s\n");
window.printw("exit = Esc/Ctrl-C\n");
window.refresh();
window.keypad(true);
noecho();
loop {
match window.getch() {
Some(Input::Character('\x1b')) => break,
Some(Input::Character(c)) => {
match c {
' ' => {
looper.toggle_state().unwrap();
window.mvprintw(4, 0, match looper.state() {
LooperState::Idle => "IDLE ",
LooperState::Recording => "RECORDING",
LooperState::Playing => "PLAYING "
});
window.mv(12, 0);
window.refresh();
},
's' => {
},
_ => ()
}
},
Some(input) => {
window.addstr(&format!("{:?}", input));
window.refresh();
},
None => ()
}
}
endwin();
}