ensnare_services/
project.rs

1// Copyright (c) 2024 Mike Tsao
2
3use anyhow::Error;
4use crossbeam_channel::{Receiver, Sender};
5#[cfg(feature = "egui")]
6use eframe::egui::Key;
7#[cfg(feature = "egui")]
8use egui::KeyHandler;
9use ensnare::{
10    orchestration::{AudioSenderFn, ProjectTitle},
11    prelude::*,
12    types::VisualizationQueue,
13    Project,
14};
15use std::{
16    path::PathBuf,
17    sync::{Arc, RwLock},
18};
19
20#[allow(missing_docs)]
21#[derive(Debug)]
22pub enum ProjectServiceInput {
23    AudioReset(SampleRate, u8),
24    FramesNeeded(usize),
25    #[cfg(feature = "egui")]
26    KeyEvent(Key, bool, Option<Key>),
27    Midi(MidiChannel, MidiMessage),
28    NextTimelineDisplayer,
29    ProjectExportToWav(Option<PathBuf>),
30    ProjectLinkControl(Uid, Uid, ControlIndex),
31    ProjectLoad(PathBuf),
32    ProjectNew,
33    ProjectPlay,
34    ProjectRemoveEntity(Uid),
35    ProjectSave(Option<PathBuf>),
36    ProjectSetSampleRate(SampleRate),
37    ProjectStop,
38    ServiceInit,
39    ServiceQuit,
40    TrackAddEntity(TrackUid, EntityKey),
41    TrackNewAudio,
42    TrackNewAux,
43    TrackNewMidi,
44    VisualizationQueue(VisualizationQueue),
45}
46
47#[allow(missing_docs)]
48#[derive(Debug)]
49pub enum ProjectServiceEvent {
50    ExportFailed(Error),
51    Exported(PathBuf),
52    IsPerformingChanged(bool),
53    LoadFailed(PathBuf, Error),
54    Loaded(Arc<RwLock<Project>>), // The supplied Project is for the recipient to keep. No need to Arc::clone().
55    Midi(MidiChannel, MidiMessage), // Handled by EnsnareEventAggregationService, never sent to app.
56    Quit,
57    SaveFailed(Error),
58    Saved(PathBuf),
59    TitleChanged(ProjectTitle),
60}
61
62/// A wrapper around a [Project] that provides a channel-based interface to it.
63#[derive(Debug)]
64pub struct ProjectService {
65    inputs: CrossbeamChannel<ProjectServiceInput>,
66    events: CrossbeamChannel<ProjectServiceEvent>,
67
68    factory: Arc<EntityFactory<dyn Entity>>,
69}
70impl ProvidesService<ProjectServiceInput, ProjectServiceEvent> for ProjectService {
71    fn sender(&self) -> &Sender<ProjectServiceInput> {
72        &self.inputs.sender
73    }
74
75    fn receiver(&self) -> &Receiver<ProjectServiceEvent> {
76        &self.events.receiver
77    }
78}
79impl ProjectService {
80    #[allow(missing_docs)]
81    pub fn new_with(
82        factory: &Arc<EntityFactory<dyn Entity>>,
83        audio_sender_fn: AudioSenderFn,
84    ) -> Self {
85        let r = Self {
86            inputs: Default::default(),
87            events: Default::default(),
88            factory: Arc::clone(factory),
89        };
90        r.spawn_thread(audio_sender_fn);
91        let _ = r.sender().send(ProjectServiceInput::ServiceInit);
92        r
93    }
94
95    fn spawn_thread(&self, audio_sender_fn: AudioSenderFn) {
96        let receiver = self.inputs.receiver.clone();
97        let sender = self.events.sender.clone();
98        let factory = Arc::clone(&self.factory);
99        std::thread::spawn(move || {
100            let mut daemon =
101                ProjectServiceDaemon::new_with(receiver, sender, factory, audio_sender_fn);
102            daemon.execute();
103        });
104    }
105}
106
107struct ProjectServiceDaemon {
108    receiver: Receiver<ProjectServiceInput>,
109    sender: Sender<ProjectServiceEvent>,
110    factory: Arc<EntityFactory<dyn Entity>>,
111
112    project: Arc<RwLock<Project>>,
113
114    #[cfg(feature = "egui")]
115    key_handler: KeyHandler,
116
117    visualization_queue: Option<VisualizationQueue>,
118}
119impl ProjectServiceDaemon {
120    pub fn new_with(
121        receiver: Receiver<ProjectServiceInput>,
122        sender: Sender<ProjectServiceEvent>,
123        factory: Arc<EntityFactory<dyn Entity>>,
124        audio_sender_fn: AudioSenderFn,
125    ) -> Self {
126        let mut project = Project::new_project();
127        project.set_audio_service_sender_fn(audio_sender_fn);
128        Self {
129            receiver,
130            sender,
131            factory,
132            project: Arc::new(RwLock::new(project)),
133            #[cfg(feature = "egui")]
134            key_handler: Default::default(),
135            visualization_queue: Default::default(),
136        }
137    }
138
139    fn notify_new_project(&self) {
140        let _ = self
141            .sender
142            .send(ProjectServiceEvent::Loaded(Arc::clone(&self.project)));
143    }
144
145    fn set_up_new_project(&self, new_project: &mut Project) {
146        if let Some(queue) = self.visualization_queue.as_ref() {
147            new_project.e.visualization_queue = Some(queue.clone());
148        }
149    }
150
151    fn swap_project(&mut self, mut new_project: Project) {
152        self.set_up_new_project(&mut new_project);
153        self.project = Arc::new(RwLock::new(new_project));
154        self.notify_new_project();
155    }
156
157    fn execute(&mut self) {
158        while let Ok(input) = self.receiver.recv() {
159            match input {
160                ProjectServiceInput::ServiceInit => {
161                    self.notify_new_project();
162                }
163                ProjectServiceInput::ProjectNew => {
164                    // TODO: set_up_successor
165                    let new_project = Project::new_project();
166                    self.swap_project(new_project);
167                }
168                ProjectServiceInput::ProjectLoad(path) => match Project::load(path.clone()) {
169                    Ok(new_project) => {
170                        self.swap_project(new_project);
171                    }
172                    Err(e) => {
173                        let _ = self.sender.send(ProjectServiceEvent::LoadFailed(path, e));
174                    }
175                },
176                ProjectServiceInput::ProjectSave(path) => {
177                    let mut project = self.project.write().unwrap();
178                    match project.save(path) {
179                        Ok(save_path) => {
180                            let _ = self.sender.send(ProjectServiceEvent::Saved(save_path));
181                        }
182                        Err(e) => {
183                            let _ = self.sender.send(ProjectServiceEvent::SaveFailed(e));
184                        }
185                    }
186                }
187                ProjectServiceInput::ServiceQuit => {
188                    eprintln!("ProjectServiceInput::Quit");
189                    let _ = self.sender.send(ProjectServiceEvent::Quit);
190                    break;
191                }
192                ProjectServiceInput::ProjectSetSampleRate(sample_rate) => {
193                    self.project
194                        .write()
195                        .unwrap()
196                        .update_sample_rate(sample_rate);
197                }
198                ProjectServiceInput::ProjectPlay => {
199                    self.project.write().unwrap().play();
200                    let _ = self
201                        .sender
202                        .send(ProjectServiceEvent::IsPerformingChanged(true));
203                }
204                ProjectServiceInput::ProjectStop => {
205                    self.project.write().unwrap().stop();
206                    let _ = self
207                        .sender
208                        .send(ProjectServiceEvent::IsPerformingChanged(false));
209                }
210                ProjectServiceInput::TrackAddEntity(track_uid, key) => {
211                    if let Ok(mut project) = self.project.write() {
212                        let uid = project.mint_entity_uid();
213                        if let Some(entity) = self.factory.new_entity(&key, uid) {
214                            let _ = project.add_entity(track_uid, entity);
215                        } else {
216                            eprintln!("ProjectServiceInput::TrackAddEntity failed");
217                        }
218                    }
219                }
220                ProjectServiceInput::ProjectLinkControl(source_uid, target_uid, index) => {
221                    let _ = self
222                        .project
223                        .write()
224                        .unwrap()
225                        .link(source_uid, target_uid, index);
226                }
227                #[cfg(feature = "egui")]
228                ProjectServiceInput::KeyEvent(key, pressed, _physical_key) => {
229                    if let Some(message) = self.key_handler.handle_key(&key, pressed) {
230                        self.project.write().unwrap().handle_midi_message(
231                            MidiChannel::default(),
232                            message,
233                            &mut |c, m| {
234                                eprintln!("TODO: {c:?} {m:?}");
235                            },
236                        )
237                    }
238                }
239                ProjectServiceInput::NextTimelineDisplayer => {
240                    if let Ok(mut project) = self.project.write() {
241                        let selected_track_uids = project.view_state.track_selection_set.clone();
242                        selected_track_uids
243                            .iter()
244                            .for_each(|track_uid| project.advance_track_view_mode(*track_uid));
245                    }
246                }
247                ProjectServiceInput::VisualizationQueue(queue) => {
248                    self.visualization_queue = Some(queue.clone());
249                    self.project.write().unwrap().e.visualization_queue = Some(queue)
250                }
251                ProjectServiceInput::Midi(channel, message) => self
252                    .project
253                    .write()
254                    .unwrap()
255                    .handle_midi_message(channel, message, &mut |c, m| {
256                        eprintln!("TODO: {c:?} {m:?}");
257                    }),
258                ProjectServiceInput::ProjectRemoveEntity(uid) => {
259                    let _ = self.project.write().unwrap().remove_entity(uid);
260                }
261                ProjectServiceInput::TrackNewAudio => {
262                    let _ = self.project.write().unwrap().new_audio_track();
263                }
264                ProjectServiceInput::TrackNewAux => {
265                    let _ = self.project.write().unwrap().new_aux_track();
266                }
267                ProjectServiceInput::TrackNewMidi => {
268                    let _ = self.project.write().unwrap().new_midi_track();
269                }
270                ProjectServiceInput::ProjectExportToWav(path) => {
271                    let path = path.unwrap_or(PathBuf::from("exported-project.wav"));
272                    let _ = self.project.write().unwrap().export_to_wav(path);
273                }
274                ProjectServiceInput::FramesNeeded(count) => {
275                    self.project.write().unwrap().generate_and_dispatch_audio(
276                        count,
277                        Some(&mut |c, m| {
278                            // If we had a channel sender to the MIDI service,
279                            // then we could send directly there from here. But
280                            // that would introduce a dependency between
281                            // ProjectService and MidiService, and I'd rather
282                            // stay with a simple hub/spoke event architecture
283                            // until it proves to be a performance issue.
284                            let _ = self.sender.send(ProjectServiceEvent::Midi(c, m));
285                        }),
286                    );
287                }
288                ProjectServiceInput::AudioReset(sample_rate, _channel_count) => {
289                    self.project
290                        .write()
291                        .unwrap()
292                        .update_sample_rate(sample_rate);
293                }
294            }
295        }
296        eprintln!("ProjectServiceDaemon exit");
297    }
298}
299
300#[cfg(feature = "egui")]
301pub(super) mod egui {
302    use super::*;
303    use derivative::Derivative;
304    use synonym::Synonym;
305
306    /// Represents an octave as MIDI conventions expect them: A before middle C is
307    /// in octave 5, and the range is from 0 to 10.
308    ///
309    /// TODO: I looked around for a bounded integer type or crate, but all made a
310    /// mountain out of this molehill-sized use case.
311    #[derive(Synonym, Derivative)]
312    #[derivative(Default)]
313    #[synonym(skip(Default))]
314    pub(super) struct Octave(#[derivative(Default(value = "5"))] pub u8);
315    impl Octave {
316        fn decrease(&mut self) {
317            if self.0 > 0 {
318                self.0 -= 1;
319            }
320        }
321        fn increase(&mut self) {
322            if self.0 < 10 {
323                self.0 += 1;
324            }
325        }
326    }
327
328    /// Maps [eframe::egui::Key] presses to MIDI events using a piano-keyboard-like
329    /// layout of QWERTY keys homed at the A-K row. Contains a bit of state, using
330    /// left/right arrow to change octaves.
331    #[derive(Debug, Default)]
332    pub(super) struct KeyHandler {
333        octave: Octave,
334    }
335
336    impl KeyHandler {
337        pub fn handle_key(&mut self, key: &Key, pressed: bool) -> Option<MidiMessage> {
338            match key {
339                Key::A => Some(self.midi_note_message(0, pressed)),
340                Key::W => Some(self.midi_note_message(1, pressed)),
341                Key::S => Some(self.midi_note_message(2, pressed)),
342                Key::E => Some(self.midi_note_message(3, pressed)),
343                Key::D => Some(self.midi_note_message(4, pressed)),
344                Key::F => Some(self.midi_note_message(5, pressed)),
345                Key::T => Some(self.midi_note_message(6, pressed)),
346                Key::G => Some(self.midi_note_message(7, pressed)),
347                Key::Y => Some(self.midi_note_message(8, pressed)),
348                Key::H => Some(self.midi_note_message(9, pressed)),
349                Key::U => Some(self.midi_note_message(10, pressed)),
350                Key::J => Some(self.midi_note_message(11, pressed)),
351                Key::K => Some(self.midi_note_message(12, pressed)),
352                Key::O => Some(self.midi_note_message(13, pressed)),
353                Key::ArrowLeft => {
354                    if pressed {
355                        self.octave.decrease();
356                    }
357                    None
358                }
359                Key::ArrowRight => {
360                    if pressed {
361                        self.octave.increase();
362                    }
363                    None
364                }
365                _ => None,
366            }
367        }
368
369        fn midi_note_message(&self, midi_note_number: u8, pressed: bool) -> MidiMessage {
370            let midi_note_number = (midi_note_number + self.octave.0 * 12).min(127);
371
372            if pressed {
373                MidiMessage::NoteOn {
374                    key: u7::from(midi_note_number),
375                    vel: u7::from(127),
376                }
377            } else {
378                MidiMessage::NoteOff {
379                    key: u7::from(midi_note_number),
380                    vel: u7::from(0),
381                }
382            }
383        }
384    }
385
386    #[cfg(test)]
387    mod tests {
388        use super::*;
389
390        #[test]
391        fn expected_messages_for_keystrokes() {
392            let mut k = KeyHandler::default();
393            let message = k.handle_key(&Key::A, true).unwrap();
394            assert_eq!(
395                message,
396                MidiMessage::NoteOn {
397                    key: u7::from(MidiNote::C4 as u8),
398                    vel: u7::from(127)
399                }
400            );
401        }
402
403        #[test]
404        fn octaves() {
405            let mut k = KeyHandler::default();
406
407            // Play a note at initial octave 4.
408            let message = k.handle_key(&Key::A, true).unwrap();
409            let _ = k.handle_key(&Key::A, false).unwrap();
410            assert_eq!(
411                message,
412                MidiMessage::NoteOn {
413                    key: u7::from(MidiNote::C4 as u8),
414                    vel: u7::from(127)
415                }
416            );
417
418            // Increase octave and try again.
419            let _ = k.handle_key(&Key::ArrowRight, true);
420            let message = k.handle_key(&Key::A, true).unwrap();
421            let _ = k.handle_key(&Key::A, false).unwrap();
422            assert_eq!(
423                message,
424                MidiMessage::NoteOn {
425                    key: u7::from(MidiNote::C5 as u8),
426                    vel: u7::from(127)
427                }
428            );
429
430            // Up to maximum octave 10 (AKA octave 9).
431            let _ = k.handle_key(&Key::ArrowRight, true);
432            let _ = k.handle_key(&Key::ArrowRight, true);
433            let _ = k.handle_key(&Key::ArrowRight, true);
434            let _ = k.handle_key(&Key::ArrowRight, true);
435            let message = k.handle_key(&Key::A, true).unwrap();
436            let _ = k.handle_key(&Key::A, false).unwrap();
437            assert_eq!(
438                message,
439                MidiMessage::NoteOn {
440                    key: u7::from(MidiNote::C9 as u8),
441                    vel: u7::from(127)
442                }
443            );
444
445            let _ = k.handle_key(&Key::ArrowRight, true);
446            let message = k.handle_key(&Key::A, true).unwrap();
447            let _ = k.handle_key(&Key::A, false).unwrap();
448            assert_eq!(
449                message,
450                MidiMessage::NoteOn {
451                    key: u7::from(MidiNote::C9 as u8),
452                    vel: u7::from(127)
453                },
454                "Trying to go higher than max octave shouldn't change anything."
455            );
456
457            // Now start over and try again with lower octaves.
458            let mut k = KeyHandler::default();
459            let _ = k.handle_key(&Key::ArrowLeft, true);
460            let message = k.handle_key(&Key::A, true).unwrap();
461            let _ = k.handle_key(&Key::A, false).unwrap();
462            assert_eq!(
463                message,
464                MidiMessage::NoteOn {
465                    key: u7::from(MidiNote::C3 as u8),
466                    vel: u7::from(127)
467                }
468            );
469            let _ = k.handle_key(&Key::ArrowLeft, true);
470            let message = k.handle_key(&Key::A, true).unwrap();
471            let _ = k.handle_key(&Key::A, false).unwrap();
472            assert_eq!(
473                message,
474                MidiMessage::NoteOn {
475                    key: u7::from(MidiNote::C2 as u8),
476                    vel: u7::from(127)
477                }
478            );
479            let _ = k.handle_key(&Key::ArrowLeft, true);
480            let message = k.handle_key(&Key::A, true).unwrap();
481            let _ = k.handle_key(&Key::A, false).unwrap();
482            assert_eq!(
483                message,
484                MidiMessage::NoteOn {
485                    key: u7::from(MidiNote::C1 as u8),
486                    vel: u7::from(127)
487                }
488            );
489            let _ = k.handle_key(&Key::ArrowLeft, true);
490            let message = k.handle_key(&Key::A, true).unwrap();
491            let _ = k.handle_key(&Key::A, false).unwrap();
492            assert_eq!(
493                message,
494                MidiMessage::NoteOn {
495                    key: u7::from(MidiNote::C0 as u8),
496                    vel: u7::from(127)
497                }
498            );
499            let _ = k.handle_key(&Key::ArrowLeft, true);
500            let message = k.handle_key(&Key::A, true).unwrap();
501            let _ = k.handle_key(&Key::A, false).unwrap();
502            assert_eq!(
503                message,
504                MidiMessage::NoteOn {
505                    key: u7::from(MidiNote::CSub0 as u8),
506                    vel: u7::from(127)
507                }
508            );
509            let _ = k.handle_key(&Key::ArrowLeft, true);
510            let message = k.handle_key(&Key::A, true).unwrap();
511            let _ = k.handle_key(&Key::A, false).unwrap();
512            assert_eq!(
513                message,
514                MidiMessage::NoteOn {
515                    key: u7::from(MidiNote::CSub0 as u8),
516                    vel: u7::from(127)
517                },
518                "Trying to go below the lowest octave should stay at lowest octave."
519            );
520        }
521    }
522}