diff --git a/crates/tek_api/src/api_player.rs b/crates/tek_api/src/api_player.rs index 1e6ba37f..81a0f93a 100644 --- a/crates/tek_api/src/api_player.rs +++ b/crates/tek_api/src/api_player.rs @@ -199,22 +199,25 @@ pub trait MidiOutputApi: ClockApi + HasPhrase { if let Some(ref phrase) = phrase { // Source phrase from which the MIDI events will be taken. let phrase = phrase.read().unwrap(); - // Current pulse index in source phrase - let pulse = pulse % phrase.length; - // Output each MIDI event from phrase at appropriate frames of output buffer: - for message in phrase.notes[pulse].iter() { - // Clear output buffer for this MIDI event. - note_buffer.clear(); - // TODO: support MIDI channels other than CH1. - let channel = 0.into(); - // Serialize MIDI event into message buffer. - LiveEvent::Midi { channel, message: *message } - .write(note_buffer) - .unwrap(); - // Append serialized message to output buffer. - output_buffer[sample].push(note_buffer.clone()); - // Update the list of currently held notes. - update_keys(&mut*notes, &message); + // Phrase with zero length is not processed + if phrase.length > 0 { + // Current pulse index in source phrase + let pulse = pulse % phrase.length; + // Output each MIDI event from phrase at appropriate frames of output buffer: + for message in phrase.notes[pulse].iter() { + // Clear output buffer for this MIDI event. + note_buffer.clear(); + // TODO: support MIDI channels other than CH1. + let channel = 0.into(); + // Serialize MIDI event into message buffer. + LiveEvent::Midi { channel, message: *message } + .write(note_buffer) + .unwrap(); + // Append serialized message to output buffer. + output_buffer[sample].push(note_buffer.clone()); + // Update the list of currently held notes. + update_keys(&mut*notes, &message); + } } } } diff --git a/crates/tek_tui/src/tui_control_sequencer.rs b/crates/tek_tui/src/tui_control_sequencer.rs index 66847cbd..4df44484 100644 --- a/crates/tek_tui/src/tui_control_sequencer.rs +++ b/crates/tek_tui/src/tui_control_sequencer.rs @@ -6,19 +6,16 @@ impl Handle for SequencerTui { } } -pub trait SequencerControl: TransportControl {} - -impl SequencerControl for SequencerTui {} - #[derive(Clone, Debug)] pub enum SequencerCommand { Focus(FocusCommand), - Undo, - Redo, - Clear, Clock(ClockCommand), Phrases(PhrasesCommand), Editor(PhraseCommand), + Enqueue(Option>>), + Clear, + Undo, + Redo, } impl Command for SequencerCommand { @@ -29,6 +26,10 @@ impl Command for SequencerCommand { Phrases(cmd) => cmd.execute(state)?.map(Phrases), Editor(cmd) => cmd.execute(state)?.map(Editor), Clock(cmd) => cmd.execute(state)?.map(Clock), + Enqueue(phrase) => { + state.player.enqueue_next(phrase.as_ref()); + None + }, Undo => { todo!() }, Redo => { todo!() }, Clear => { todo!() }, @@ -47,43 +48,50 @@ pub fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option Clock( + if state.is_stopped() { Play(None) } else { Pause(None) } + ), + // Play from start/rewind to start + key!(Shift-Char(' ')) => Clock( + if state.is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } + ), // Edit phrase - //key!(Char('e')) => Editor(Show(state.phrase_to_edit().clone())), - key!(Char(' ')) => Clock(if state.is_stopped() { Play(None) } else { Pause(None) }), - key!(Shift-Char(' ')) => Clock(if state.is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }), - _ => match state.focused() { - SequencerFocus::Transport(_) => match TransportCommand::input_to_command(state, input)? { - TransportCommand::Clock(command) => Clock(command), - _ => return None, - }, - SequencerFocus::PhrasePlay => match input.event() { - key!(Char('e')) => Editor(Show( - state.player.play_phrase().as_ref().map(|x|x.1.as_ref()).flatten().map(|x|x.clone()) - )), - _ => return None, - }, - SequencerFocus::PhraseNext => match input.event() { - key!(Char('e')) => Editor(Show( - state.player.next_phrase().as_ref().map(|x|x.1.as_ref()).flatten().map(|x|x.clone()) - )), - _ => return None, - }, - SequencerFocus::PhraseList => match input.event() { - key!(Char('e')) => Editor(Show( - Some(state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone()) - )), - _ => Phrases( - PhrasesCommand::input_to_command(state, input)? - ) - }, - SequencerFocus::PhraseEditor => Editor( - PhraseCommand::input_to_command(state, input)? - ), + key!(Char('e')) => match state.focused() { + SequencerFocus::PhrasePlay => Editor(Show( + state.player.play_phrase().as_ref().map(|x|x.1.as_ref()).flatten().map(|x|x.clone()) + )), + SequencerFocus::PhraseNext => Editor(Show( + state.player.next_phrase().as_ref().map(|x|x.1.as_ref()).flatten().map(|x|x.clone()) + )), + SequencerFocus::PhraseList => Editor(Show( + Some(state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone()) + )), + _ => return None, + }, + _ => if state.entered() { + match state.focused() { + SequencerFocus::Transport(_) => match TransportCommand::input_to_command(state, input)? { + TransportCommand::Clock(command) => Clock(command), + _ => return None, + }, + SequencerFocus::PhraseEditor => Editor( + PhraseCommand::input_to_command(state, input)? + ), + SequencerFocus::PhraseList => match input.event() { + key!(Enter) => Enqueue(Some( + state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone() + )), + _ => Phrases( + PhrasesCommand::input_to_command(state, input)? + ), + } + _ => return None + } + } else { + return None } }) } diff --git a/crates/tek_tui/src/tui_model_clock.rs b/crates/tek_tui/src/tui_model_clock.rs index ccab54e3..0a66bb1e 100644 --- a/crates/tek_tui/src/tui_model_clock.rs +++ b/crates/tek_tui/src/tui_model_clock.rs @@ -21,11 +21,11 @@ pub struct ClockModel { impl From<&Arc> for ClockModel { fn from (transport: &Arc) -> Self { Self { - current: Instant::default().into(), playing: RwLock::new(None).into(), - quant: Quantize::default().into(), started: RwLock::new(None).into(), - sync: LaunchSync::default().into(), + current: Instant::default().into(), + quant: Arc::new(24.into()), + sync: Arc::new(384.into()), transport: transport.clone(), metronome: false, }