1use std::collections::HashMap;
16use std::error::Error;
17use std::fs::{self, File};
18use std::io::Read;
19use std::path::PathBuf;
20use std::sync::{
21 atomic::{AtomicBool, AtomicUsize, Ordering},
22 Arc,
23};
24use std::time::{Duration, SystemTime};
25
26use atomic_float::AtomicF32;
27use bon::Builder;
28use nodi::{
29 midly::{Format, MidiMessage, Smf, Timing},
30 timers::TimeFormatError,
31 Event as NodiEvent, MidiEvent, Moment, Sheet,
32};
33use ringbuf::{traits::*, HeapCons, HeapProd, HeapRb};
34use rustysynth::{SoundFont, Synthesizer, SynthesizerSettings};
35
36pub struct Player {
38 sheet_receiver: HeapCons<Option<Arc<MidiSheet>>>,
39 tempo_rate: Arc<AtomicF32>,
40 volume: Arc<AtomicF32>,
41 note_off_all_listener: HeapCons<bool>,
42 is_playing: Arc<AtomicBool>,
43 position: Arc<AtomicUsize>,
44 previous_position: usize,
45 settings: Settings,
46 sheet: Option<Arc<MidiSheet>>,
47 synthesizer: Synthesizer,
48 tick_clock: u32, }
50
51impl Player {
52 pub fn new(
54 soundfont: &str,
55 settings: Settings,
56 ) -> Result<(Self, PlayerController), Box<dyn Error>> {
57 let sf_file = fs::read(soundfont)?;
58 let sf = SoundFont::new(&mut sf_file.as_slice())?;
59 let sf = Arc::new(sf);
60 let synthesizer = Synthesizer::new(&sf, &settings.clone().into())?;
61 let rb = HeapRb::new(1);
62 let (sheet_sender, sheet_receiver) = rb.split();
63 let rb = HeapRb::new(1);
64 let (note_off_all_sender, note_off_all_listener) = rb.split();
65 let is_playing = Arc::new(AtomicBool::new(false));
66 let position = Arc::new(AtomicUsize::new(0));
67 let sample_rate = settings.sample_rate;
68 let tempo_rate = Arc::new(AtomicF32::new(1.0));
69 let volume = Arc::new(AtomicF32::new(1.0));
70
71 Ok((
72 Self {
73 is_playing: is_playing.clone(),
74 position: position.clone(),
75 tempo_rate: tempo_rate.clone(),
76 volume: volume.clone(),
77 sheet_receiver,
78 note_off_all_listener,
79 settings,
80 synthesizer,
81 sheet: None,
82 tick_clock: 0,
83 previous_position: 0,
84 },
85 PlayerController {
86 is_playing,
87 position,
88 tempo_rate,
89 volume,
90 sheet_length: Default::default(),
91 sheet: None,
92 sheet_sender,
93 note_off_all_sender,
94 cache: Cache::new(sample_rate),
95 },
96 ))
97 }
98
99 pub fn settings(&self) -> &Settings {
101 &self.settings
102 }
103
104 pub fn render(&mut self, left: &mut [f32], right: &mut [f32]) {
106 if left.len() != right.len() {
107 panic!("left and right channel buffer size cannot be different");
108 }
109
110 if let Some(should_note_off) = self.note_off_all_listener.try_pop() {
111 if should_note_off {
112 self.synthesizer.note_off_all(false);
113 self.synthesizer.render(left, right);
114 return;
115 }
116 }
117
118 if !self.is_playing.load(Ordering::Relaxed) {
119 self.synthesizer.render(left, right);
120 return;
121 }
122
123 if let Some(sheet) = self.sheet_receiver.try_pop() {
124 self.sheet = sheet;
125 }
126
127 if let Some(sheet) = &self.sheet {
128 self.synthesizer
129 .set_master_volume(self.volume.load(Ordering::Relaxed));
130 for _ in 0..left.len() {
131 let position = self.position.load(Ordering::Relaxed);
132 if position == sheet.pulses.len() {
133 self.is_playing.store(false, Ordering::Relaxed);
134 self.synthesizer.note_off_all(false);
135 return;
136 }
137
138 if position != self.previous_position {
140 self.tick_clock = 0;
141 self.previous_position = position;
142 }
143
144 let pulse = &sheet.pulses[position];
145
146 if self.tick_clock == 0 {
147 for event in &pulse.events {
148 self.synthesizer.process_midi_message(
149 event.channel,
150 event.command,
151 event.data1,
152 event.data2,
153 );
154 }
155 }
156
157 self.tick_clock += 1;
158
159 let pulse_duration = (pulse.duration as f32
160 * self.tempo_rate.load(Ordering::Relaxed))
161 .round() as u32;
162
163 if self.tick_clock == pulse_duration && position < sheet.pulses.len() {
164 self.position.store(position + 1, Ordering::Relaxed);
165 }
166 }
167
168 self.synthesizer.render(left, right);
169 }
170 }
171}
172
173pub struct PlayerController {
175 is_playing: Arc<AtomicBool>,
176 position: Arc<AtomicUsize>,
177 tempo_rate: Arc<AtomicF32>,
178 volume: Arc<AtomicF32>,
179 sheet_length: Arc<AtomicUsize>,
181 sheet: Option<Arc<MidiSheet>>,
182 sheet_sender: HeapProd<Option<Arc<MidiSheet>>>,
183 note_off_all_sender: HeapProd<bool>,
184 cache: Cache,
185}
186
187impl PlayerController {
188 pub fn play(&self) -> bool {
192 if self.sheet.is_none() {
193 self.is_playing.store(false, Ordering::SeqCst);
194 return false;
195 }
196
197 let position = self.position();
198
199 if self.is_playing() && position < 1.0 {
200 return true;
201 }
202
203 if position == 1.0 {
204 self.is_playing.store(false, Ordering::Relaxed);
205 return false;
206 }
207
208 self.is_playing.store(true, Ordering::Relaxed);
209
210 true
211 }
212
213 pub fn is_playing(&self) -> bool {
215 self.is_playing.load(Ordering::SeqCst)
216 }
217
218 pub fn stop(&mut self) {
220 if self.is_playing() {
221 self.is_playing.store(false, Ordering::SeqCst);
222 self.note_off_all();
223 }
224 }
225
226 pub fn set_position_ticks(&self, value: u64) {
231 let length = self.sheet_length.load(Ordering::Relaxed);
232 if length == 0 {
233 return;
234 }
235
236 let tick = value.min(length as u64) as usize;
237 self.position.store(tick, Ordering::Relaxed);
238 }
239
240 pub fn position_ticks(&self) -> u64 {
242 self.position.load(Ordering::Relaxed) as u64
243 }
244
245 pub fn total_ticks(&self) -> u64 {
247 self.sheet_length.load(Ordering::Relaxed) as u64
248 }
249
250 pub fn set_position(&self, value: f64) {
254 let total_ticks = self.total_ticks();
255 if total_ticks == 0 {
256 return;
257 }
258
259 let position = value.max(0.0).min(1.0);
260 let tick = (total_ticks as f64 * position) as u64;
261 self.set_position_ticks(tick);
262 }
263
264 pub fn position(&self) -> f64 {
266 let total_ticks = self.total_ticks();
267 if total_ticks == 0 {
268 return 0.0;
269 }
270
271 let position = self.position_ticks() as f64;
272 (position / total_ticks as f64).max(0.0).min(1.0)
273 }
274
275 pub fn new_position_observer(&self) -> PositionObserver {
277 PositionObserver {
278 position: self.position.clone(),
279 length: self.sheet_length.clone(),
280 }
281 }
282
283 pub fn set_tempo(&mut self, tempo: f32) {
285 if let Some(sheet) = &mut self.sheet {
286 self.tempo_rate
287 .store(sheet.tempo / tempo, Ordering::Relaxed);
288 }
289 }
290
291 pub fn tempo(&self) -> Option<f32> {
295 self.sheet
296 .as_ref()
297 .map(|s| s.tempo / self.tempo_rate.load(Ordering::SeqCst))
298 }
299
300 pub fn set_volume(&mut self, volume: f32) {
302 self.volume.store(volume.max(0.0), Ordering::Relaxed);
303 }
304
305 pub fn volume(&self) -> f32 {
307 self.volume.load(Ordering::Relaxed)
308 }
309
310 pub fn duration(&self) -> Duration {
312 self.sheet
313 .as_ref()
314 .map(|s| s.duration())
315 .unwrap_or_default()
316 }
317
318 pub fn set_file(&mut self, path: Option<impl Into<PathBuf>>) -> Result<(), Box<dyn Error>> {
323 match path {
324 Some(path) => self.open_file(path),
325 None => {
326 self.offload_file();
327 Ok(())
328 }
329 }
330 }
331
332 fn offload_file(&mut self) {
333 self.stop();
334 self.sheet_length.store(0, Ordering::SeqCst);
335 self.sheet_sender
336 .try_push(None)
337 .expect("ringbuf producer must be big enough to handle new files");
338 self.sheet = None;
339 self.tempo_rate.store(1.0, Ordering::Relaxed);
340 self.set_position(0.0);
341 }
342
343 fn open_file(&mut self, path: impl Into<PathBuf>) -> Result<(), Box<dyn Error>> {
344 self.stop();
345 let sheet = self.cache.open(&path.into())?;
346 self.sheet_length.store(sheet.total_ticks, Ordering::SeqCst);
347 self.sheet_sender
348 .try_push(Some(sheet.clone()))
349 .expect("ringbuf producer must be big enough to handle new files");
350 self.sheet = Some(sheet);
351 self.tempo_rate.store(1.0, Ordering::Relaxed);
352 self.set_position(0.0);
353
354 Ok(())
355 }
356
357 pub fn note_off_all(&mut self) {
359 self.note_off_all_sender
360 .try_push(true)
361 .expect("ringbuf must be big enough for sending note off all message");
362 }
363}
364
365struct Cache {
366 sample_rate: u32,
367 map: HashMap<PathBuf, Arc<MidiSheet>>,
368}
369
370impl Cache {
371 fn new(sample_rate: u32) -> Self {
372 Self {
373 sample_rate,
374 map: HashMap::new(),
375 }
376 }
377
378 fn open(&mut self, path: &PathBuf) -> Result<Arc<MidiSheet>, Box<dyn Error>> {
379 let file = File::open(path)?;
380
381 match self.map.get(path) {
382 Some(s) => {
383 if file.metadata()?.modified()? == s.modified {
384 Ok(s.clone())
385 } else {
386 self.upsert(path, file)
387 }
388 }
389
390 None => self.upsert(path, file),
391 }
392 }
393
394 fn upsert(&mut self, path: &PathBuf, file: File) -> Result<Arc<MidiSheet>, Box<dyn Error>> {
395 let sheet = Arc::new(MidiSheet::new(file, self.sample_rate)?);
396 self.map.insert(path.clone(), sheet.clone());
397
398 Ok(sheet)
399 }
400}
401
402#[derive(Debug, Clone)]
404pub struct PositionObserver {
405 position: Arc<AtomicUsize>,
406 length: Arc<AtomicUsize>,
407}
408
409impl PositionObserver {
410 pub fn get(&self) -> f32 {
412 let length = self.length.load(Ordering::Relaxed);
413 if length == 0 {
414 return 0.0;
415 }
416
417 self.position.load(Ordering::Relaxed) as f32 / length as f32
418 }
419
420 pub fn ticks(&self) -> u64 {
422 self.position.load(Ordering::Relaxed) as u64
423 }
424
425 pub fn total_ticks(&self) -> u64 {
427 self.length.load(Ordering::Relaxed) as u64
428 }
429}
430
431#[allow(missing_docs)]
433#[derive(Builder, Clone, Debug)]
434pub struct Settings {
435 #[builder(default = 44100)]
436 pub sample_rate: u32,
437 #[builder(default = 64)]
438 pub block_size: u32,
439 #[builder(default = 512)]
440 pub audio_buffer_size: u32,
441 #[builder(default = 64)]
442 pub max_polyphony: u8,
443 #[builder(default = true)]
444 pub enable_effects: bool,
445}
446
447impl From<Settings> for SynthesizerSettings {
448 fn from(settings: Settings) -> Self {
449 let mut result = SynthesizerSettings::new(settings.sample_rate as i32);
451 result.block_size = settings.block_size as usize;
452 result.maximum_polyphony = settings.max_polyphony as usize;
453 result.enable_reverb_and_chorus = settings.enable_effects;
454
455 result
456 }
457}
458
459#[derive(Debug, Clone)]
460struct MidiSheet {
461 sample_rate: u32,
462 tempo: f32,
463 pulses: Vec<Pulse>,
465 total_ticks: usize,
466 modified: SystemTime,
467}
468
469impl MidiSheet {
470 fn new(mut file: File, sample_rate: u32) -> Result<Self, Box<dyn Error>> {
471 let modified = file.metadata()?.modified()?;
472 let mut buf = Vec::new();
473 file.read_to_end(&mut buf)?;
474 let Smf { header, tracks } = Smf::parse(&buf)?;
475 let ppqn = match header.timing {
476 Timing::Metrical(n) => u16::from(n),
477 _ => return Err(TimeFormatError.into()),
478 };
479
480 let sheet = match header.format {
481 Format::SingleTrack | Format::Sequential => Sheet::sequential(&tracks),
482 Format::Parallel => Sheet::parallel(&tracks),
483 };
484 let total_ticks = sheet.len();
485
486 let mut duration = Pulse::duration_in_samples(500_000, ppqn as u64, sample_rate as u64);
487 let tempo = sheet
488 .iter()
489 .flat_map(|m| &m.events)
490 .find(|v| matches!(v, NodiEvent::Tempo(_)))
491 .map(|v| match v {
492 NodiEvent::Tempo(tempo) => us_per_beat_to_bpm(*tempo),
493 _ => unreachable!(),
494 })
495 .unwrap_or(120.0f32);
496
497 let pulses: Vec<Pulse> = sheet
498 .iter()
499 .map(|moment| Pulse::from_moment(moment, &mut duration, ppqn, sample_rate))
500 .collect();
501 debug_assert_eq!(pulses.len(), total_ticks);
502
503 Ok(Self {
504 sample_rate,
505 pulses,
506 total_ticks,
507 tempo,
508 modified,
509 })
510 }
511
512 fn duration(&self) -> Duration {
513 let duration: u64 = self.pulses.iter().map(|p| p.duration as u64).sum();
514 let duration = (duration as f64 / self.sample_rate as f64) * 1_000_000.0;
515
516 Duration::from_micros(duration as u64)
517 }
518}
519
520fn us_per_beat_to_bpm(uspb: u32) -> f32 {
521 60.0 / uspb as f32 * 1_000_000.0
522}
523
524#[derive(Debug, Clone)]
525struct Pulse {
526 duration: u32,
528 events: Vec<RawMidiEvent>,
529}
530
531impl Pulse {
532 fn from_moment(moment: &Moment, duration: &mut u32, ppqn: u16, sample_rate: u32) -> Self {
535 moment.events.iter().fold(
536 Pulse {
537 duration: *duration,
539 events: vec![],
540 },
541 |mut result, event| {
542 match event {
543 NodiEvent::Midi(event) => result.events.push((*event).into()),
544 NodiEvent::Tempo(tempo) => {
545 *duration = Self::duration_in_samples(
546 *tempo as u64,
547 ppqn as u64,
548 sample_rate as u64,
549 );
550 result.duration = *duration;
551 }
552 _ => (),
553 }
554 result
555 },
556 )
557 }
558
559 fn duration_in_samples(tempo_us: u64, ppqn: u64, sample_rate: u64) -> u32 {
560 let numerator = (tempo_us * sample_rate) as f64;
561 let denominator = (ppqn * 1_000_000) as f64;
562 (numerator / denominator).round() as u32
563 }
564}
565
566#[derive(Debug, Clone, Copy)]
567struct RawMidiEvent {
568 channel: i32, command: i32,
570 data1: i32,
571 data2: i32,
572}
573
574impl From<MidiEvent> for RawMidiEvent {
575 fn from(event: MidiEvent) -> Self {
576 let channel = event.channel.as_int() as i32;
577
578 let (command, data1, data2) = match event.message {
579 MidiMessage::NoteOn { key, vel } => (0x90, key.as_int() as i32, vel.as_int() as i32),
580 MidiMessage::NoteOff { key, vel } => (0x80, key.as_int() as i32, vel.as_int() as i32),
581 MidiMessage::Aftertouch { key, vel } => {
582 (0xA0, key.as_int() as i32, vel.as_int() as i32)
583 }
584 MidiMessage::Controller { controller, value } => {
585 (0xB0, controller.as_int() as i32, value.as_int() as i32)
586 }
587 MidiMessage::ProgramChange { program } => (0xC0, program.as_int() as i32, 0),
588 MidiMessage::ChannelAftertouch { vel } => (0xD0, vel.as_int() as i32, 0),
589 MidiMessage::PitchBend { bend } => {
590 let midi_value = bend.as_int() as i32 + 8192;
592
593 let lsb = midi_value & 0x7F;
595 let msb = (midi_value >> 7) & 0x7F;
596
597 (0xE0, lsb, msb)
598 }
599 };
600
601 Self {
602 channel,
603 command,
604 data1,
605 data2,
606 }
607 }
608}