Skip to content

Commit

Permalink
Fix #156: Route MIDI messages to external interface
Browse files Browse the repository at this point in the history
I had expected to add an OrchestratorEvent::Midi so that the app could handle it
by forwarding to the MIDI interface. But I forgot that we call
Orchestrator::render() in the NeedsAudioFn, so that's where we receive our
messages. Fortunately, it wasn't hard to get a MidiInterfaceInput channel sender
in there, so (a) I can do it at all, and (b) since it's a channel rather than a
method call, I don't have to worry about NeedsAudioFn becoming janky if
MidiInterface gets slow.
  • Loading branch information
sowbug committed Sep 15, 2023
1 parent 1e87495 commit 66bd3f1
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 28 deletions.
19 changes: 12 additions & 7 deletions examples/minidaw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,13 @@ use groove::{
app_version,
mini::{register_factory_entities, DragDropManager, EntityFactory, Key, Orchestrator},
panels::{
AudioPanelEvent, ControlPanel, ControlPanelAction, MidiPanelEvent, NeedsAudioFn,
AudioPanelEvent, ControlPanel, ControlPanelAction, MidiPanel, MidiPanelEvent, NeedsAudioFn,
OrchestratorEvent, OrchestratorInput, OrchestratorPanel, PaletteAction, PalettePanel,
SettingsPanel,
},
};
use groove_core::{
time::SampleRate,
traits::{gui::Displays, Configurable},
};
use groove_core::traits::{gui::Displays, Configurable, EntityEvent};
use groove_midi::MidiInterfaceInput;
use std::{
path::PathBuf,
sync::{Arc, Mutex},
Expand Down Expand Up @@ -229,10 +227,17 @@ impl MiniDaw {
let orchestrator_panel = OrchestratorPanel::default();
let mini_orchestrator = Arc::clone(orchestrator_panel.orchestrator());

let midi_panel = MidiPanel::default();
let midi_panel_sender = midi_panel.sender().clone();

let mini_orchestrator_for_fn = Arc::clone(&mini_orchestrator);
let needs_audio: NeedsAudioFn = Box::new(move |audio_queue, samples_requested| {
if let Ok(mut o) = mini_orchestrator_for_fn.lock() {
o.render_and_enqueue(samples_requested, audio_queue);
o.render_and_enqueue(samples_requested, audio_queue, &mut |_, event| {
if let EntityEvent::Midi(channel, message) = event {
let _ = midi_panel_sender.send(MidiInterfaceInput::Midi(channel, message));
}
});
}
});

Expand All @@ -242,7 +247,7 @@ impl MiniDaw {
control_panel: Default::default(),
orchestrator_panel,
palette_panel: Default::default(),
settings_panel: SettingsPanel::new_with(Box::new(needs_audio)),
settings_panel: SettingsPanel::new_with(midi_panel, Box::new(needs_audio)),

exit_requested: Default::default(),

Expand Down
45 changes: 26 additions & 19 deletions src/mini/orchestrator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ pub struct OrchestratorEphemerals {
/// let uid = track.append_entity(Box::new(ToySynth::default())).unwrap();
///
/// let mut samples = [StereoSample::SILENCE; Orchestrator::SAMPLE_BUFFER_SIZE];
/// orchestrator.render(&mut samples);
/// orchestrator.render_and_ignore_events(&mut samples);
/// ```
#[derive(Serialize, Deserialize, Debug, Builder)]
#[builder(setter(skip), default)]
Expand Down Expand Up @@ -198,26 +198,28 @@ impl Orchestrator {

/// Renders the next set of samples into the provided buffer. This is the
/// main event loop.
pub fn render(&mut self, samples: &mut [StereoSample]) {
pub fn render(
&mut self,
samples: &mut [StereoSample],
control_events_fn: &mut ControlEventsFn,
) {
// Note that advance() can return the same range twice, depending on
// sample rate. TODO: we should decide whose responsibility it is to
// handle that -- either we skip calling work() if the time range is the
// same as prior, or everyone who gets called needs to detect the case
// or be idempotent.
let range = self.transport.advance(samples.len());
self.update_time(&range);
self.work(&mut |uid, event| {
match event {
EntityEvent::Midi(channel, message) => {
eprintln!("I'd like to send this MIDI message to the external interface: {uid} -> {channel}, {message:?}")
}
_ => panic!("We received an unexpected event {event:?}"),
}
// TODO: forward to external MIDI
});
self.work(control_events_fn);
self.generate_batch_values(samples);
}

/// A convenience method for callers who would have ignored any
/// [EntityEvent]s produced by the render() method.
pub fn render_and_ignore_events(&mut self, samples: &mut [StereoSample]) {
self.render(samples, &mut |_, _| {});
}

/// Renders part of the project to audio, creating at least the requested
/// number of [StereoSample]s and inserting them in the given [AudioQueue].
/// Exceptions: the method operates only in [Self::SAMPLE_BUFFER_SIZE]
Expand All @@ -230,7 +232,12 @@ impl Orchestrator {
//
// TODO: I don't think there's any reason why this must be limited to an
// `AudioQueue` rather than a more general `Vec`-like interface.
pub fn render_and_enqueue(&mut self, samples_requested: usize, queue: &AudioQueue) {
pub fn render_and_enqueue(
&mut self,
samples_requested: usize,
queue: &AudioQueue,
control_events_fn: &mut ControlEventsFn,
) {
// Round up
let buffers_requested =
(samples_requested + Self::SAMPLE_BUFFER_SIZE - 1) / Self::SAMPLE_BUFFER_SIZE;
Expand All @@ -241,7 +248,7 @@ impl Orchestrator {
if false {
self.render_debug(&mut samples);
} else {
self.render(&mut samples);
self.render(&mut samples, control_events_fn);
}
// No need to do the Arc deref each time through the loop.
// TODO: is there a queue type that allows pushing a batch?
Expand Down Expand Up @@ -357,7 +364,7 @@ impl Orchestrator {
// our own Entities). It is not called for external MIDI messages.
fn dispatch_event(&mut self, uid: Uid, event: EntityEvent) {
match event {
EntityEvent::Midi(_, _) => {
EntityEvent::Midi(..) => {
panic!("FATAL: we were asked to dispatch an EntityEvent::Midi, which should already have been handled")
}
EntityEvent::Control(value) => {
Expand Down Expand Up @@ -427,7 +434,7 @@ impl Orchestrator {
break;
}
buffer.fill(StereoSample::SILENCE);
self.render(&mut buffer);
self.render_and_ignore_events(&mut buffer);
for sample in buffer {
let (left, right) = sample.into_i16();
let _ = writer.write_sample(left);
Expand Down Expand Up @@ -832,7 +839,7 @@ mod tests {
}
_prior_start_time = o.transport().current_time();
let mut samples = [StereoSample::SILENCE; 1];
o.render(&mut samples);
o.render_and_ignore_events(&mut samples);
}

// TODO: this section is confusing me. It used to say
Expand Down Expand Up @@ -955,7 +962,7 @@ mod tests {
.is_ok());
}
let mut samples = [StereoSample::SILENCE; TrackBuffer::LEN];
o.render(&mut samples);
o.render_and_ignore_events(&mut samples);
let expected_sample = StereoSample::from(ToyAudioSource::MEDIUM);
assert!(
samples.iter().all(|s| *s == expected_sample),
Expand All @@ -964,7 +971,7 @@ mod tests {

assert!(o.send_to_aux(track_uid, aux_uid, Normal::from(0.5)).is_ok());
let mut samples = [StereoSample::SILENCE; TrackBuffer::LEN];
o.render(&mut samples);
o.render_and_ignore_events(&mut samples);
let expected_sample = StereoSample::from(0.75);
assert!(
samples.iter().all(|s| *s == expected_sample),
Expand All @@ -981,7 +988,7 @@ mod tests {
.is_ok());
}
let mut samples = [StereoSample::SILENCE; TrackBuffer::LEN];
o.render(&mut samples);
o.render_and_ignore_events(&mut samples);
let expected_sample = StereoSample::from(0.5 + 0.5 * 0.5 * 0.5);
assert!(
samples.iter().all(|s| *s == expected_sample),
Expand Down
5 changes: 5 additions & 0 deletions src/panels/midi_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ impl MidiPanel {
// TODO: Create the MidiPanelInput channel, add it to the receiver loop, etc.
eprintln!("MIDI Panel acks the quit... TODO");
}

/// Allows sending to the [MidiInterfaceInput] channel.
pub fn sender(&self) -> &Sender<MidiInterfaceInput> {
&self.sender
}
}
impl Displays for MidiPanel {
fn ui(&mut self, ui: &mut Ui) -> eframe::egui::Response {
Expand Down
9 changes: 7 additions & 2 deletions src/panels/settings_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ pub struct SettingsPanel {
}
impl SettingsPanel {
/// Creates a new [SettingsPanel].
pub fn new_with(needs_audio_fn: NeedsAudioFn) -> Self {
pub fn new_with(midi_panel: MidiPanel, needs_audio_fn: NeedsAudioFn) -> Self {
Self {
audio_panel: AudioPanel::new_with(needs_audio_fn),
midi_panel: Default::default(),
midi_panel,
is_open: Default::default(),
}
}
Expand All @@ -41,6 +41,11 @@ impl SettingsPanel {
&self.midi_panel
}

/// The owned [MidiPanel] (mutable).
pub fn midi_panel_mut(&mut self) -> &mut MidiPanel {
&mut self.midi_panel
}

/// Asks the panel to shut down any services associated with contained panels.
pub fn exit(&self) {
self.audio_panel.exit();
Expand Down

0 comments on commit 66bd3f1

Please sign in to comment.