OSC messages for column beat tracking
This commit is contained in:
@@ -118,14 +118,17 @@ impl<const ROWS: usize> Column<ROWS> {
|
||||
chunk_factory: &mut impl ChunkFactory,
|
||||
controllers: &ColumnControllers,
|
||||
) -> Result<()> {
|
||||
let len = self.len();
|
||||
let old_len = self.len();
|
||||
|
||||
if self.idle() {
|
||||
if let Some(beat_index) = timing.beat_in_buffer {
|
||||
let idle_time = input_buffer.len() - beat_index as usize;
|
||||
if len == 0 {
|
||||
self.playback_position = self.frames_per_beat - idle_time;
|
||||
// When starting from idle, always start at beat 0 (beginning of first beat)
|
||||
// regardless of where in the buffer the beat occurs
|
||||
if old_len == 0 {
|
||||
self.playback_position = 0; // Start at beat 0 for first recording
|
||||
} else {
|
||||
self.playback_position = len - idle_time;
|
||||
let idle_time = input_buffer.len() - beat_index as usize;
|
||||
self.playback_position = old_len - idle_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,13 +150,38 @@ impl<const ROWS: usize> Column<ROWS> {
|
||||
}
|
||||
}
|
||||
|
||||
let len = self.len();
|
||||
if len > 0 {
|
||||
self.playback_position = (self.playback_position + input_buffer.len()) % self.len();
|
||||
} else {
|
||||
self.playback_position =
|
||||
(self.playback_position + input_buffer.len()) % self.frames_per_beat;
|
||||
let new_len = self.len();
|
||||
|
||||
if old_len == 0 && new_len > 0 {
|
||||
let beats = new_len / self.frames_per_beat;
|
||||
controllers.send_column_beats_changed(beats)?;
|
||||
}
|
||||
|
||||
// Update playback position
|
||||
if new_len > 0 {
|
||||
self.playback_position = (self.playback_position + input_buffer.len()) % new_len;
|
||||
} else {
|
||||
// During recording of first track, don't use modulo - let position grow
|
||||
// so that beat calculation works correctly
|
||||
self.playback_position += input_buffer.len();
|
||||
}
|
||||
|
||||
// Send beat change message if a beat occurred in this buffer
|
||||
if timing.beat_in_buffer.is_some() {
|
||||
// Calculate which beat we're entering (after the beat that just occurred)
|
||||
let current_beat = if new_len > 0 {
|
||||
// When column has defined length, calculate beat within that loop
|
||||
let column_beats = new_len / self.frames_per_beat;
|
||||
(self.playback_position / self.frames_per_beat) % column_beats
|
||||
} else {
|
||||
// During recording of first track, calculate beat based on absolute position
|
||||
// The beat that just occurred is the beat we're now in
|
||||
self.playback_position / self.frames_per_beat
|
||||
};
|
||||
|
||||
controllers.send_column_beat_changed(current_beat)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,18 @@ impl<'a> ColumnControllers<'a> {
|
||||
row,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_column_beats_changed(&self, beats: usize) -> Result<()> {
|
||||
self.osc.column_beats_changed(self.column, beats)
|
||||
}
|
||||
|
||||
pub fn send_column_beat_changed(&self, beat: usize) -> Result<()> {
|
||||
self.osc.column_beat_changed(self.column, beat)
|
||||
}
|
||||
|
||||
pub fn column(&self) -> usize {
|
||||
self.column
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TrackControllers<'a> {
|
||||
|
||||
@@ -84,10 +84,10 @@ async fn main() {
|
||||
let state = persistence_manager.state();
|
||||
|
||||
// Calculate tempo from state and jack client
|
||||
let tempo_bpm = (jack_client.sample_rate() as f32 * 60.0)
|
||||
/ state.borrow().metronome.frames_per_beat as f32;
|
||||
let tempo_bpm =
|
||||
(jack_client.sample_rate() as f32 * 60.0) / state.borrow().metronome.frames_per_beat as f32;
|
||||
|
||||
let (mut osc, osc_controller) = Osc::new(&args.socket, COLS, ROWS, tempo_bpm)
|
||||
let (mut osc, osc_controller) = Osc::<COLS, ROWS>::new(&args.socket, tempo_bpm)
|
||||
.await
|
||||
.expect("Could not create OSC server");
|
||||
|
||||
@@ -190,4 +190,4 @@ fn setup_jack() -> (jack::Client, JackPorts) {
|
||||
};
|
||||
|
||||
(jack_client, ports)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,4 +62,22 @@ impl OscController {
|
||||
Err(_) => Err(LooperError::Osc(std::panic::Location::caller())), // Channel closed
|
||||
}
|
||||
}
|
||||
|
||||
pub fn column_beats_changed(&self, column: usize, beats: usize) -> Result<()> {
|
||||
let message = osc::Message::ColumnBeatsChanged { column, beats };
|
||||
match self.sender.try_send(message) {
|
||||
Ok(true) => Ok(()),
|
||||
Ok(false) => Err(LooperError::Osc(std::panic::Location::caller())), // Channel full
|
||||
Err(_) => Err(LooperError::Osc(std::panic::Location::caller())), // Channel closed
|
||||
}
|
||||
}
|
||||
|
||||
pub fn column_beat_changed(&self, column: usize, beat: usize) -> Result<()> {
|
||||
let message = osc::Message::ColumnBeatChanged { column, beat };
|
||||
match self.sender.try_send(message) {
|
||||
Ok(true) => Ok(()),
|
||||
Ok(false) => Err(LooperError::Osc(std::panic::Location::caller())), // Channel full
|
||||
Err(_) => Err(LooperError::Osc(std::panic::Location::caller())), // Channel closed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,21 +3,16 @@ use futures::SinkExt;
|
||||
|
||||
const CLIENT_BUFFER_SIZE: usize = 32;
|
||||
|
||||
pub struct Osc {
|
||||
pub struct Osc<const COLS: usize, const ROWS: usize> {
|
||||
receiver: kanal::AsyncReceiver<osc::Message>,
|
||||
listener: osc_server::platform::PlatformListener,
|
||||
broadcaster: tokio::sync::broadcast::Sender<osc::Message>,
|
||||
shadow_state: osc::State,
|
||||
shadow_state: osc::State<COLS, ROWS>,
|
||||
}
|
||||
|
||||
impl Osc {
|
||||
/// Create new OSC server and controller with configurable matrix size and tempo
|
||||
pub async fn new(
|
||||
socket_path: &str,
|
||||
columns: usize,
|
||||
rows: usize,
|
||||
tempo: f32,
|
||||
) -> Result<(Self, OscController)> {
|
||||
impl<const COLS: usize, const ROWS: usize> Osc<COLS, ROWS> {
|
||||
/// Create new OSC server and controller with configurable tempo
|
||||
pub async fn new(socket_path: &str, tempo: f32) -> Result<(Self, OscController)> {
|
||||
// Create platform listener (server)
|
||||
let listener = osc_server::platform::create_listener(socket_path).await?;
|
||||
|
||||
@@ -32,7 +27,7 @@ impl Osc {
|
||||
receiver,
|
||||
listener,
|
||||
broadcaster,
|
||||
shadow_state: osc::State::new(columns, rows, tempo),
|
||||
shadow_state: osc::State::new(tempo),
|
||||
};
|
||||
|
||||
Ok((server, controller))
|
||||
@@ -110,4 +105,4 @@ impl Osc {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,9 @@ impl<F: ChunkFactory, const COLS: usize, const ROWS: usize> ProcessHandler<F, CO
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: ChunkFactory, const COLS: usize, const ROWS: usize> jack::ProcessHandler for ProcessHandler<F, COLS, ROWS> {
|
||||
impl<F: ChunkFactory, const COLS: usize, const ROWS: usize> jack::ProcessHandler
|
||||
for ProcessHandler<F, COLS, ROWS>
|
||||
{
|
||||
fn process(&mut self, _client: &jack::Client, ps: &jack::ProcessScope) -> jack::Control {
|
||||
if let Err(e) = self.process_with_error_handling(ps) {
|
||||
log::error!("Error processing audio: {}", e);
|
||||
@@ -181,4 +183,4 @@ impl<F: ChunkFactory, const COLS: usize, const ROWS: usize> ProcessHandler<F, CO
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user