wip: assigning steps to frames

This commit is contained in:
🪞👃🪞 2024-06-03 17:53:10 +03:00
parent 7c1dc9ce9b
commit faac61180b
6 changed files with 389 additions and 13 deletions

217
Cargo.lock generated
View file

@ -2,6 +2,24 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "allocator-api2"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.14" version = "0.6.14"
@ -76,6 +94,22 @@ dependencies = [
"clap", "clap",
"crossterm", "crossterm",
"jack", "jack",
"ratatui",
]
[[package]]
name = "cassowary"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "castaway"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc"
dependencies = [
"rustversion",
] ]
[[package]] [[package]]
@ -130,6 +164,19 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]]
name = "compact_str"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f"
dependencies = [
"castaway",
"cfg-if",
"itoa",
"ryu",
"static_assertions",
]
[[package]] [[package]]
name = "crossterm" name = "crossterm"
version = "0.27.0" version = "0.27.0"
@ -155,6 +202,22 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "either"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
@ -167,6 +230,21 @@ version = "1.70.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]] [[package]]
name = "jack" name = "jack"
version = "0.10.0" version = "0.10.0"
@ -231,6 +309,15 @@ version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "lru"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc"
dependencies = [
"hashbrown",
]
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.8.11" version = "0.8.11"
@ -243,6 +330,12 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.2" version = "0.12.2"
@ -266,6 +359,12 @@ dependencies = [
"windows-targets 0.52.5", "windows-targets 0.52.5",
] ]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.30" version = "0.3.30"
@ -290,6 +389,26 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "ratatui"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef"
dependencies = [
"bitflags 2.5.0",
"cassowary",
"compact_str",
"crossterm",
"itertools",
"lru",
"paste",
"stability",
"strum",
"unicode-segmentation",
"unicode-truncate",
"unicode-width",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.1" version = "0.5.1"
@ -299,6 +418,18 @@ dependencies = [
"bitflags 2.5.0", "bitflags 2.5.0",
] ]
[[package]]
name = "rustversion"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
@ -341,12 +472,50 @@ version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "stability"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.11.1" version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7993a8e3a9e88a00351486baae9522c91b123a088f76469e5bd5cc17198ea87"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.60" version = "2.0.60"
@ -364,12 +533,40 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-segmentation"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]]
name = "unicode-truncate"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226"
dependencies = [
"itertools",
"unicode-width",
]
[[package]]
name = "unicode-width"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"
@ -536,3 +733,23 @@ name = "windows_x86_64_msvc"
version = "0.52.5" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]]
name = "zerocopy"
version = "0.7.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View file

@ -6,3 +6,4 @@ edition = "2021"
jack = "0.10" jack = "0.10"
clap = { version = "4.5.4", features = [ "derive" ] } clap = { version = "4.5.4", features = [ "derive" ] }
crossterm = "0.27" crossterm = "0.27"
ratatui = "0.26.3"

View file

@ -72,7 +72,7 @@ fn run_one (command: &cli::Command) -> Result<(), Box<dyn Error>> {
), ),
cli::Command::Sequencer => engine.run( cli::Command::Sequencer => engine.run(
&mut sequencer::Sequencer::new(engine.jack_client.as_client())?, &mut sequencer::Sequencer::new()?,
|state, stdout, mut offset| { |state, stdout, mut offset| {
let (w, h) = render::render_toolbar_vertical(stdout, offset, &sequencer::ACTIONS)?; let (w, h) = render::render_toolbar_vertical(stdout, offset, &sequencer::ACTIONS)?;
offset.0 = offset.0 + w + 2; offset.0 = offset.0 + w + 2;
@ -122,7 +122,7 @@ fn run_all () -> Result<(), Box<dyn Error>> {
exited: false, exited: false,
mode: Mode::Sequencer, mode: Mode::Sequencer,
transport: transport::Transport::new(engine.jack_client.as_client())?, transport: transport::Transport::new(engine.jack_client.as_client())?,
sequencer: sequencer::Sequencer::new(engine.jack_client.as_client())?, sequencer: sequencer::Sequencer::new()?,
mixer: mixer::Mixer::new()?, mixer: mixer::Mixer::new()?,
looper: looper::Looper::new()?, looper: looper::Looper::new()?,
sampler: sampler::Sampler::new()?, sampler: sampler::Sampler::new()?,

View file

@ -51,9 +51,10 @@ pub fn handle (state: &mut Sequencer, event: &Event) -> Result<(), Box<dyn Error
let row = state.cursor.0 as usize; let row = state.cursor.0 as usize;
let step = state.cursor.1 as usize; let step = state.cursor.1 as usize;
let duration = state.cursor.2 as usize; let duration = state.cursor.2 as usize;
state.sequence[row][step] = Some(super::Event::NoteOn(35, 128)); let mut sequence = state.sequence.lock().unwrap();
sequence[row][step] = Some(super::Event::NoteOn(35, 128));
if state.cursor.2 > 0 { if state.cursor.2 > 0 {
state.sequence[row][step + duration] = Some(super::Event::NoteOff(35)); sequence[row][step + duration] = Some(super::Event::NoteOff(35));
} }
}, },
_ => { _ => {

View file

@ -14,12 +14,13 @@ pub const ACTIONS: [(&'static str, &'static str);4] = [
]; ];
pub struct Sequencer { pub struct Sequencer {
exited: bool, exited: Arc<AtomicBool>,
cursor: (u16, u16, u16), cursor: (u16, u16, u16),
sequence: Vec<Vec<Option<Event>>>, sequence: Arc<Mutex<Vec<Vec<Option<Event>>>>>,
transport: ::jack::Transport, transport: ::jack::Transport,
bpm: f64, bpm: f64,
timesig: (f32, f32), timesig: (f32, f32),
pub jack_client: Jack<self::jack::Notifications>,
} }
#[derive(Clone)] #[derive(Clone)]
@ -29,24 +30,179 @@ pub enum Event {
} }
impl Sequencer { impl Sequencer {
pub fn new (client: &Client) -> Result<Self, Box<dyn Error>> {
pub fn new () -> Result<Self, Box<dyn Error>> {
let exited = Arc::new(AtomicBool::new(false));
let sequence: Arc<Mutex<Vec<Vec<Option<Event>>>>> = Arc::new(Mutex::new(vec![vec![None;64];4]));
let (client, _status) = Client::new(
"blinkenlive-sequencer",
ClientOptions::NO_START_SERVER
)?;
let transport = client.transport(); let transport = client.transport();
let mut port = client.register_port("sequence", ::jack::MidiOut::default())?;
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() {
frame_steps[*frame] = Some(index);
}
let handler: BoxedProcessHandler = Box::new({
let transport = client.transport();
let exited = exited.clone();
let sequence = sequence.clone();
move |client: &Client, scope: &ProcessScope| -> Control {
if exited.fetch_and(true, Ordering::Relaxed) {
return Control::Quit
}
if 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
}
});
Ok(Self { Ok(Self {
exited: false, exited,
cursor: (0, 0, 0), cursor: (0, 0, 0),
transport, transport,
sequence: vec![vec![None;64];4], sequence,
bpm: 120.0, bpm: 120.0,
timesig: (4.0, 4.0) timesig: (4.0, 4.0),
jack_client: client.activate_async(
self::jack::Notifications,
ClosureProcessHandler::new(handler)
)?
}) })
} }
} }
impl Exitable for Sequencer { impl Exitable for Sequencer {
fn exit (&mut self) { fn exit (&mut self) {
self.exited = true self.exited.store(true, Ordering::Relaxed)
} }
fn exited (&self) -> bool { fn exited (&self) -> bool {
self.exited self.exited.fetch_and(true, Ordering::Relaxed)
}
}
#[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));
}
} }
} }

View file

@ -41,7 +41,8 @@ fn render_grid (
.queue(move_to(17, 3))?.queue(Print("1.2"))? .queue(move_to(17, 3))?.queue(Print("1.2"))?
.queue(move_to(33, 3))?.queue(Print("1.3"))? .queue(move_to(33, 3))?.queue(Print("1.3"))?
.queue(move_to(49, 3))?.queue(Print("1.4"))?; .queue(move_to(49, 3))?.queue(Print("1.4"))?;
for (index, row) in state.sequence.iter().enumerate() { let sequence = state.sequence.lock().unwrap();
for (index, row) in sequence.iter().enumerate() {
let y = index as u16 + 4; let y = index as u16 + 4;
for x in 0u16..64 { for x in 0u16..64 {
let bg = if x as u32 == (beat - 1) * 16 + (beat_sub * 16.0) as u32 { let bg = if x as u32 == (beat - 1) * 16 + (beat_sub * 16.0) as u32 {