beat synced actions for track
This commit is contained in:
parent
9e6bd37b8f
commit
675216eb71
@ -45,6 +45,10 @@ pub fn process_events<F: ChunkFactory>(
|
|||||||
// Button 2: Play/Mute
|
// Button 2: Play/Mute
|
||||||
process_handler.play_toggle()?;
|
process_handler.play_toggle()?;
|
||||||
}
|
}
|
||||||
|
24 => {
|
||||||
|
// Button 5: Clear track
|
||||||
|
process_handler.clear_track()?;
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Other CC messages - ignore for now
|
// Other CC messages - ignore for now
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use crate::*;
|
use crate::*;
|
||||||
use crate::metronome::Metronome;
|
use crate::metronome::Metronome;
|
||||||
|
use crate::track::{TrackTiming, TrackState};
|
||||||
|
|
||||||
pub struct ProcessHandler<F: ChunkFactory> {
|
pub struct ProcessHandler<F: ChunkFactory> {
|
||||||
track: Track,
|
track: Track,
|
||||||
@ -27,105 +28,167 @@ impl<F: ChunkFactory> ProcessHandler<F> {
|
|||||||
|
|
||||||
/// Handle record/play toggle button (Button 1)
|
/// Handle record/play toggle button (Button 1)
|
||||||
pub fn record_toggle(&mut self) -> Result<()> {
|
pub fn record_toggle(&mut self) -> Result<()> {
|
||||||
match self.track.state() {
|
self.track.queue_record_toggle();
|
||||||
TrackState::Idle => {
|
|
||||||
// Clear previous recording and start new recording
|
|
||||||
self.track.clear(&mut self.chunk_factory)?;
|
|
||||||
self.playback_position = 0;
|
|
||||||
self.track.set_state(TrackState::Recording);
|
|
||||||
}
|
|
||||||
TrackState::Recording => {
|
|
||||||
self.track.set_state(TrackState::Playing);
|
|
||||||
self.playback_position = 0;
|
|
||||||
}
|
|
||||||
TrackState::Playing => {
|
|
||||||
self.track.set_state(TrackState::Idle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle play/mute toggle button (Button 2)
|
/// Handle play/mute toggle button (Button 2)
|
||||||
pub fn play_toggle(&mut self) -> Result<()> {
|
pub fn play_toggle(&mut self) -> Result<()> {
|
||||||
match self.track.state() {
|
self.track.queue_play_toggle();
|
||||||
TrackState::Idle | TrackState::Recording => {
|
Ok(())
|
||||||
if self.track.len() > 0 {
|
|
||||||
self.track.set_state(TrackState::Playing);
|
|
||||||
self.playback_position = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TrackState::Playing => {
|
|
||||||
self.track.set_state(TrackState::Idle);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handle clear button (Button 5)
|
||||||
|
pub fn clear_track(&mut self) -> Result<()> {
|
||||||
|
self.track.queue_clear();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F: ChunkFactory> jack::ProcessHandler for ProcessHandler<F> {
|
impl<F: ChunkFactory> jack::ProcessHandler for ProcessHandler<F> {
|
||||||
fn process(&mut self, client: &jack::Client, ps: &jack::ProcessScope) -> jack::Control {
|
fn process(&mut self, client: &jack::Client, ps: &jack::ProcessScope) -> jack::Control {
|
||||||
// Process MIDI first
|
// Process MIDI first - this updates next_state on the track
|
||||||
if let Err(e) = midi::process_events(self, ps) {
|
if let Err(e) = midi::process_events(self, ps) {
|
||||||
log::error!("Error processing MIDI events: {}", e);
|
log::error!("Error processing MIDI events: {}", e);
|
||||||
return jack::Control::Quit;
|
return jack::Control::Quit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = self.metronome.process(ps, &mut self.ports) {
|
// Process metronome and get beat timing information
|
||||||
|
let beat_sample_index = match self.metronome.process(ps, &mut self.ports) {
|
||||||
|
Ok(beat_index) => beat_index,
|
||||||
|
Err(e) => {
|
||||||
log::error!("Error processing metronome: {}", e);
|
log::error!("Error processing metronome: {}", e);
|
||||||
return jack::Control::Quit;
|
return jack::Control::Quit;
|
||||||
}
|
}
|
||||||
|
|
||||||
let input_buffer = self.ports.audio_in.as_slice(ps);
|
|
||||||
let output_buffer = self.ports.audio_out.as_mut_slice(ps);
|
|
||||||
|
|
||||||
let jack_buffer_size = client.buffer_size() as usize;
|
|
||||||
|
|
||||||
let mut index = 0;
|
|
||||||
|
|
||||||
while index < jack_buffer_size {
|
|
||||||
match self.track.state() {
|
|
||||||
TrackState::Recording => {
|
|
||||||
// Recording - continue until manually stopped
|
|
||||||
let sample_count_to_append = jack_buffer_size - index;
|
|
||||||
let samples_to_append = &input_buffer[index..index + sample_count_to_append];
|
|
||||||
if let Err(e) = self
|
|
||||||
.track
|
|
||||||
.append_samples(samples_to_append, &mut self.chunk_factory)
|
|
||||||
{
|
|
||||||
log::error!("Error appending samples: {}", e);
|
|
||||||
return jack::Control::Quit;
|
|
||||||
};
|
};
|
||||||
output_buffer[index..(index + sample_count_to_append)].fill(0.0);
|
|
||||||
index += sample_count_to_append;
|
let buffer_size = client.buffer_size() as usize;
|
||||||
}
|
let state_before = self.track.current_state().clone();
|
||||||
TrackState::Playing => {
|
|
||||||
// Playback
|
// Calculate timing information for track processing
|
||||||
let sample_count_to_play = jack_buffer_size - index;
|
let timing = self.calculate_track_timing(beat_sample_index, buffer_size, &state_before);
|
||||||
let sample_count_to_play =
|
|
||||||
sample_count_to_play.min(self.track.len() - self.playback_position);
|
// Process track audio with calculated timing
|
||||||
if let Err(e) = self
|
if let Err(e) = self.track.process(
|
||||||
.track
|
ps,
|
||||||
.copy_samples(
|
&mut self.ports,
|
||||||
&mut output_buffer[index..(index + sample_count_to_play)],
|
timing,
|
||||||
self.playback_position,
|
&mut self.chunk_factory
|
||||||
)
|
) {
|
||||||
{
|
log::error!("Error processing track: {}", e);
|
||||||
log::error!("Error copying samples: {}", e);
|
|
||||||
return jack::Control::Quit;
|
return jack::Control::Quit;
|
||||||
}
|
}
|
||||||
index += sample_count_to_play;
|
|
||||||
self.playback_position += sample_count_to_play;
|
// Update playback position based on what happened
|
||||||
if self.playback_position >= self.track.len() {
|
self.update_playback_position(beat_sample_index, buffer_size, &state_before);
|
||||||
self.playback_position = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TrackState::Idle => {
|
|
||||||
// Idle - output silence
|
|
||||||
output_buffer[index..jack_buffer_size].fill(0.0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jack::Control::Continue
|
jack::Control::Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<F: ChunkFactory> ProcessHandler<F> {
|
||||||
|
/// Calculate timing information for track processing
|
||||||
|
fn calculate_track_timing(
|
||||||
|
&self,
|
||||||
|
beat_sample_index: Option<u32>,
|
||||||
|
buffer_size: usize,
|
||||||
|
state_before: &TrackState,
|
||||||
|
) -> TrackTiming {
|
||||||
|
match beat_sample_index {
|
||||||
|
None => {
|
||||||
|
// No beat in this buffer
|
||||||
|
TrackTiming::NoBeat {
|
||||||
|
position: self.playback_position,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(beat_index) => {
|
||||||
|
let beat_index = beat_index as usize;
|
||||||
|
let pre_beat_position = self.playback_position;
|
||||||
|
let post_beat_position = self.calculate_post_beat_position(state_before);
|
||||||
|
|
||||||
|
TrackTiming::Beat {
|
||||||
|
pre_beat_position,
|
||||||
|
post_beat_position,
|
||||||
|
beat_sample_index: beat_index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the correct playback position after a beat transition
|
||||||
|
fn calculate_post_beat_position(&self, state_before: &TrackState) -> usize {
|
||||||
|
let state_after = self.track.next_state(); // Use next_state since transition hasn't happened yet
|
||||||
|
|
||||||
|
match (state_before, state_after) {
|
||||||
|
(_, TrackState::Playing) if *state_before != TrackState::Playing => {
|
||||||
|
// Just started playing - start from beginning
|
||||||
|
// Note: In future Column implementation, this will be:
|
||||||
|
// column.get_sync_position() to sync with other playing tracks
|
||||||
|
0
|
||||||
|
}
|
||||||
|
(TrackState::Playing, TrackState::Playing) => {
|
||||||
|
// Continue playing - use current position
|
||||||
|
self.playback_position
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Not playing after transition - position doesn't matter
|
||||||
|
self.playback_position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update playback position after track processing
|
||||||
|
fn update_playback_position(
|
||||||
|
&mut self,
|
||||||
|
beat_sample_index: Option<u32>,
|
||||||
|
buffer_size: usize,
|
||||||
|
state_before: &TrackState,
|
||||||
|
) {
|
||||||
|
let state_after = self.track.current_state().clone();
|
||||||
|
|
||||||
|
match beat_sample_index {
|
||||||
|
None => {
|
||||||
|
// No beat - simple position update
|
||||||
|
if *state_before == TrackState::Playing {
|
||||||
|
self.advance_playback_position(buffer_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(beat_index) => {
|
||||||
|
let beat_index = beat_index as usize;
|
||||||
|
|
||||||
|
// Handle position updates around beat boundary
|
||||||
|
if beat_index > 0 && *state_before == TrackState::Playing {
|
||||||
|
// Advance position for samples before beat
|
||||||
|
self.advance_playback_position(beat_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if state transition at beat affects position
|
||||||
|
if state_after == TrackState::Playing && *state_before != TrackState::Playing {
|
||||||
|
// Started playing at beat - reset position to post-beat calculation
|
||||||
|
self.playback_position = self.calculate_post_beat_position(state_before);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance position for samples after beat if playing
|
||||||
|
if beat_index < buffer_size && state_after == TrackState::Playing {
|
||||||
|
let samples_after_beat = buffer_size - beat_index;
|
||||||
|
self.advance_playback_position(samples_after_beat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance playback position with looping
|
||||||
|
fn advance_playback_position(&mut self, samples: usize) {
|
||||||
|
if self.track.len() == 0 {
|
||||||
|
self.playback_position = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.playback_position += samples;
|
||||||
|
|
||||||
|
// Handle looping
|
||||||
|
while self.playback_position >= self.track.len() {
|
||||||
|
self.playback_position -= self.track.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
304
src/track.rs
304
src/track.rs
@ -1,57 +1,321 @@
|
|||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum TrackState {
|
pub enum TrackState {
|
||||||
Idle,
|
Empty, // No audio data (---)
|
||||||
Playing,
|
Idle, // Has data, not playing (READY)
|
||||||
Recording,
|
Playing, // Currently playing (PLAY)
|
||||||
|
Recording, // Currently recording (REC)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum TrackTiming {
|
||||||
|
NoBeat {
|
||||||
|
position: usize
|
||||||
|
},
|
||||||
|
Beat {
|
||||||
|
pre_beat_position: usize,
|
||||||
|
post_beat_position: usize,
|
||||||
|
beat_sample_index: usize,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Track {
|
pub struct Track {
|
||||||
audio_chunks: Arc<AudioChunk>,
|
audio_chunks: Option<Arc<AudioChunk>>,
|
||||||
length: usize,
|
length: usize,
|
||||||
state: TrackState,
|
current_state: TrackState,
|
||||||
|
next_state: TrackState,
|
||||||
|
volume: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Track {
|
impl Track {
|
||||||
pub fn new<F: ChunkFactory>(chunk_factory: &mut F) -> Result<Self> {
|
pub fn new<F: ChunkFactory>(chunk_factory: &mut F) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
audio_chunks: chunk_factory.create_chunk()?,
|
audio_chunks: None,
|
||||||
length: 0,
|
length: 0,
|
||||||
state: TrackState::Idle,
|
current_state: TrackState::Empty,
|
||||||
|
next_state: TrackState::Empty,
|
||||||
|
volume: 1.0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Main audio processing method called from ProcessHandler
|
||||||
|
pub fn process<F: ChunkFactory>(
|
||||||
|
&mut self,
|
||||||
|
ps: &jack::ProcessScope,
|
||||||
|
ports: &mut JackPorts,
|
||||||
|
timing: TrackTiming,
|
||||||
|
chunk_factory: &mut F,
|
||||||
|
) -> Result<()> {
|
||||||
|
let input_buffer = ports.audio_in.as_slice(ps);
|
||||||
|
let output_buffer = ports.audio_out.as_mut_slice(ps);
|
||||||
|
let buffer_size = output_buffer.len();
|
||||||
|
|
||||||
|
match timing {
|
||||||
|
TrackTiming::NoBeat { position } => {
|
||||||
|
// No beat in this buffer - process entire buffer with current state
|
||||||
|
self.process_audio_range(
|
||||||
|
input_buffer,
|
||||||
|
output_buffer,
|
||||||
|
0,
|
||||||
|
buffer_size,
|
||||||
|
position,
|
||||||
|
chunk_factory,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
TrackTiming::Beat {
|
||||||
|
pre_beat_position,
|
||||||
|
post_beat_position,
|
||||||
|
beat_sample_index
|
||||||
|
} => {
|
||||||
|
if beat_sample_index > 0 {
|
||||||
|
// Process samples before beat with current state
|
||||||
|
self.process_audio_range(
|
||||||
|
input_buffer,
|
||||||
|
output_buffer,
|
||||||
|
0,
|
||||||
|
beat_sample_index,
|
||||||
|
pre_beat_position,
|
||||||
|
chunk_factory,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply state transition at beat boundary
|
||||||
|
self.apply_state_transition(chunk_factory)?;
|
||||||
|
|
||||||
|
if beat_sample_index < buffer_size {
|
||||||
|
// Process samples after beat with new current state
|
||||||
|
self.process_audio_range(
|
||||||
|
input_buffer,
|
||||||
|
output_buffer,
|
||||||
|
beat_sample_index,
|
||||||
|
buffer_size,
|
||||||
|
post_beat_position,
|
||||||
|
chunk_factory,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process audio for a specific range within the buffer
|
||||||
|
fn process_audio_range<F: ChunkFactory>(
|
||||||
|
&mut self,
|
||||||
|
input_buffer: &[f32],
|
||||||
|
output_buffer: &mut [f32],
|
||||||
|
start_index: usize,
|
||||||
|
end_index: usize,
|
||||||
|
playback_position: usize,
|
||||||
|
chunk_factory: &mut F,
|
||||||
|
) -> Result<()> {
|
||||||
|
let sample_count = end_index - start_index;
|
||||||
|
if sample_count == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.current_state {
|
||||||
|
TrackState::Empty | TrackState::Idle => {
|
||||||
|
// Output silence for this range
|
||||||
|
output_buffer[start_index..end_index].fill(0.0);
|
||||||
|
}
|
||||||
|
TrackState::Recording => {
|
||||||
|
// Record input samples
|
||||||
|
let samples_to_record = &input_buffer[start_index..end_index];
|
||||||
|
self.append_samples(samples_to_record, chunk_factory)?;
|
||||||
|
|
||||||
|
// Output silence during recording
|
||||||
|
output_buffer[start_index..end_index].fill(0.0);
|
||||||
|
}
|
||||||
|
TrackState::Playing => {
|
||||||
|
// Playback with looping
|
||||||
|
self.copy_samples_to_output(
|
||||||
|
&mut output_buffer[start_index..end_index],
|
||||||
|
sample_count,
|
||||||
|
playback_position,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply state transition from next_state to current_state
|
||||||
|
fn apply_state_transition<F: ChunkFactory>(
|
||||||
|
&mut self,
|
||||||
|
chunk_factory: &mut F
|
||||||
|
) -> Result<()> {
|
||||||
|
// Handle transitions that require setup
|
||||||
|
match (&self.current_state, &self.next_state) {
|
||||||
|
(_, TrackState::Recording) => {
|
||||||
|
// Starting to record - clear previous data and create new chunk
|
||||||
|
self.clear_audio_data(chunk_factory)?;
|
||||||
|
}
|
||||||
|
(_, TrackState::Playing) => {
|
||||||
|
// Starting playback - check if we have audio data
|
||||||
|
if self.audio_chunks.is_none() || self.length == 0 {
|
||||||
|
// No audio data - transition to Idle instead
|
||||||
|
self.next_state = TrackState::Idle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(_, TrackState::Empty) => {
|
||||||
|
// Clear operation - remove audio data
|
||||||
|
// Note: Actual deallocation will happen later via IO thread
|
||||||
|
self.audio_chunks = None;
|
||||||
|
self.length = 0;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Other transitions don't require special handling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the state transition
|
||||||
|
self.current_state = self.next_state.clone();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy samples from track audio to output buffer with looping
|
||||||
|
fn copy_samples_to_output(
|
||||||
|
&self,
|
||||||
|
output_slice: &mut [f32],
|
||||||
|
sample_count: usize,
|
||||||
|
mut playback_position: usize,
|
||||||
|
) -> Result<()> {
|
||||||
|
if let Some(ref audio_chunks) = self.audio_chunks {
|
||||||
|
if self.length == 0 {
|
||||||
|
output_slice.fill(0.0);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut samples_written = 0;
|
||||||
|
|
||||||
|
while samples_written < sample_count {
|
||||||
|
let samples_remaining = sample_count - samples_written;
|
||||||
|
let samples_until_loop = self.length - playback_position;
|
||||||
|
let samples_to_copy = samples_remaining.min(samples_until_loop);
|
||||||
|
|
||||||
|
// Copy directly from audio chunks to output slice
|
||||||
|
audio_chunks.copy_samples(
|
||||||
|
&mut output_slice[samples_written..samples_written + samples_to_copy],
|
||||||
|
playback_position,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Apply volume scaling in-place
|
||||||
|
for sample in &mut output_slice[samples_written..samples_written + samples_to_copy] {
|
||||||
|
*sample *= self.volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
samples_written += samples_to_copy;
|
||||||
|
playback_position += samples_to_copy;
|
||||||
|
|
||||||
|
// Handle looping
|
||||||
|
if playback_position >= self.length {
|
||||||
|
playback_position = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output_slice.fill(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Append samples to the track's audio data
|
||||||
pub fn append_samples<F: ChunkFactory>(
|
pub fn append_samples<F: ChunkFactory>(
|
||||||
&mut self,
|
&mut self,
|
||||||
samples: &[f32],
|
samples: &[f32],
|
||||||
chunk_factory: &mut F,
|
chunk_factory: &mut F,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.audio_chunks.append_samples(samples, chunk_factory)?;
|
if self.audio_chunks.is_none() {
|
||||||
|
self.audio_chunks = Some(chunk_factory.create_chunk()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref mut chunks) = self.audio_chunks {
|
||||||
|
chunks.append_samples(samples, chunk_factory)?;
|
||||||
self.length += samples.len();
|
self.length += samples.len();
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clear all audio data from the track
|
||||||
|
fn clear_audio_data<F: ChunkFactory>(
|
||||||
|
&mut self,
|
||||||
|
chunk_factory: &mut F,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.audio_chunks = Some(chunk_factory.create_chunk()?);
|
||||||
|
self.length = 0;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public accessors and commands for MIDI handling
|
||||||
|
pub fn current_state(&self) -> &TrackState {
|
||||||
|
&self.current_state
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_state(&self) -> &TrackState {
|
||||||
|
&self.next_state
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_next_state(&mut self, state: TrackState) {
|
||||||
|
self.next_state = state;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.length
|
self.length
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear<F: ChunkFactory>(&mut self, chunk_factory: &mut F) -> Result<()> {
|
pub fn volume(&self) -> f32 {
|
||||||
self.audio_chunks = chunk_factory.create_chunk()?;
|
self.volume
|
||||||
self.length = 0;
|
|
||||||
self.state = TrackState::Idle;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn copy_samples(&self, dest: &mut [f32], start: usize) -> Result<()> {
|
pub fn set_volume(&mut self, volume: f32) {
|
||||||
self.audio_chunks.copy_samples(dest, start)
|
self.volume = volume.clamp(0.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn state(&self) -> &TrackState {
|
/// Handle record/play toggle command (sets next_state)
|
||||||
&self.state
|
pub fn queue_record_toggle(&mut self) {
|
||||||
|
match self.current_state {
|
||||||
|
TrackState::Empty | TrackState::Idle => {
|
||||||
|
self.next_state = TrackState::Recording;
|
||||||
|
}
|
||||||
|
TrackState::Recording => {
|
||||||
|
self.next_state = TrackState::Playing;
|
||||||
|
}
|
||||||
|
TrackState::Playing => {
|
||||||
|
self.next_state = TrackState::Idle;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_state(&mut self, state: TrackState) {
|
/// Handle play/mute toggle command (sets next_state)
|
||||||
self.state = state;
|
pub fn queue_play_toggle(&mut self) {
|
||||||
|
match self.current_state {
|
||||||
|
TrackState::Empty => {
|
||||||
|
// Can't play empty track
|
||||||
|
self.next_state = TrackState::Empty;
|
||||||
|
}
|
||||||
|
TrackState::Idle => {
|
||||||
|
if self.length > 0 {
|
||||||
|
self.next_state = TrackState::Playing;
|
||||||
|
} else {
|
||||||
|
self.next_state = TrackState::Idle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TrackState::Recording => {
|
||||||
|
// Don't change state while recording
|
||||||
|
self.next_state = TrackState::Recording;
|
||||||
|
}
|
||||||
|
TrackState::Playing => {
|
||||||
|
self.next_state = TrackState::Idle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle clear command (sets next_state)
|
||||||
|
pub fn queue_clear(&mut self) {
|
||||||
|
self.next_state = TrackState::Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user