From e37faff13a553b6d594e274aa9ef2f23103d4322 Mon Sep 17 00:00:00 2001 From: pommicket Date: Fri, 7 Oct 2022 15:03:35 -0400 Subject: switched to floating-point samples --- README.md | 8 ++------ src/main.rs | 53 +++++++++++++++++++++++++++++++++++++++-------------- src/midi_input.rs | 2 ++ src/soundfont.rs | 13 +++++++------ 4 files changed, 50 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 6c440d2..36f693f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # progmidi -A programmable MIDI keyboard audio synthesizer. +A programmable MIDI keyboard-controlled audio synthesizer. Check out the [releases](https://github.com/pommicket/progmidi/releases) for Windows and Linux executables. @@ -21,11 +21,7 @@ Musescore has a very extensive soundfont file. According to [this web page](https://musescore.org/en/handbook/3/soundfonts-and-sfz-files), after installing musescore, the soundfont will be located here: -Windows x86 (32-bit) / MuseScore x86: `%ProgramFiles%\MuseScore 3\sound\MuseScore_General.sf3` - -Windows x64 (64-bit) / MuseScore x86: `%ProgramFiles(x86)%\MuseScore 3\sound\MuseScore_General.sf3` - -Windows x64 (64-bit) / MuseScore x86\_64: `%ProgramFiles%\MuseScore 3\sound\MuseScore_General.sf3` +Windows: `%ProgramFiles%\MuseScore 3\sound\MuseScore_General.sf3` macOS: `/Applications/MuseScore 3.app/Contents/Resources/sound/MuseScore_General.sf3` diff --git a/src/main.rs b/src/main.rs index de6da99..20b40b7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -185,13 +185,13 @@ impl WavRecording { } } - fn write(&mut self, samples: &[i16]) { + fn write(&mut self, samples: &[f32]) { if samples.len() + self.data.len() > (u32::MAX / 2 - 100) as usize { // too much data for wav file return; } for x in samples { - self.data.push(*x); + self.data.push(((*x) * 32767.0) as i16); } } @@ -316,22 +316,46 @@ fn get_audio_stream() -> Result<(cpal::Stream, u32), String> { .supported_output_configs() .expect("error while querying configs"); let mut chosen_config = None; - let srate = 44100; + + // get audio configuration with 2-channel float audio, + // and as close to 44100Hz sample rate as possible. + let desired_srate = 44100; + let best_dist = u32::MAX; for config in supported_configs { if config.channels() != 2 - || config.sample_format() != cpal::SampleFormat::I16 - || config.min_sample_rate().0 > srate - || config.max_sample_rate().0 < srate - { + || config.sample_format() != cpal::SampleFormat::F32 { continue; } - chosen_config = Some(config); + let min_srate = config.min_sample_rate().0; + let max_srate = config.max_sample_rate().0; + let dist = if min_srate > desired_srate { + min_srate - desired_srate + } else if max_srate < desired_srate { + desired_srate - max_srate + } else { + 0 + }; + if dist < best_dist { + chosen_config = Some(config); + } } let chosen_config = match chosen_config { None => return Err("Couldn't get desired audio properties.".to_string()), Some(x) => x, }; - + + let srate = { + let min_srate = chosen_config.min_sample_rate().0; + let max_srate = chosen_config.max_sample_rate().0; + if min_srate > desired_srate { + min_srate + } else if max_srate < desired_srate { + max_srate + } else { + desired_srate + } + }; + let supp_config: cpal::SupportedStreamConfig = chosen_config.with_sample_rate(cpal::SampleRate(srate)); let config = supp_config.into(); @@ -339,12 +363,12 @@ fn get_audio_stream() -> Result<(cpal::Stream, u32), String> { let stream = audio_device .build_output_stream( &config, - move |data: &mut [i16], _: &cpal::OutputCallbackInfo| { + move |data: &mut [f32], _: &cpal::OutputCallbackInfo| { let (mut note_info, mut maybe_sf) = lock_note_info_and_soundfont(); if let Some(sf) = maybe_sf.as_mut() { let sample_rate = config.sample_rate.0 as f64; for x in data.iter_mut() { - *x = 0; + *x = 0.0; } let pitch_bend = note_info.pitch_bend; for channel in 0..CHANNEL_COUNT { @@ -782,9 +806,6 @@ fn main() { }; } - let mut this = rhai::Dynamic::from(rhai::Map::new()); - call_fn_if_exists(&engine, &ast, &mut this, "pm_start", ()); - let (stream, sample_rate) = match get_audio_stream() { Ok(s) => s, Err(e) => { @@ -798,6 +819,10 @@ fn main() { note_info.output_sample_rate = sample_rate; } + + let mut this = rhai::Dynamic::from(rhai::Map::new()); + call_fn_if_exists(&engine, &ast, &mut this, "pm_start", ()); + if let Err(e) = stream.play() { eprintln!("Error starting audio stream: {}", e); return; diff --git a/src/midi_input.rs b/src/midi_input.rs index dd663b9..649cead 100644 --- a/src/midi_input.rs +++ b/src/midi_input.rs @@ -1,3 +1,4 @@ +#[cfg(unix)] use std::ffi::{c_char, c_int, c_void, CStr, CString}; /// Query and read from MIDI input devices. /// Basic usage: @@ -22,6 +23,7 @@ use std::sync::Mutex; // a big deal to add it as a dependency // (we only really need it for size_t, ssize_t) extern crate libc; +#[cfg(unix)] use libc::{free, size_t, ssize_t}; // (snd_rawmidi_t is an opaque struct) diff --git a/src/soundfont.rs b/src/soundfont.rs index 7d3628b..0c9a857 100644 --- a/src/soundfont.rs +++ b/src/soundfont.rs @@ -1215,14 +1215,14 @@ impl SoundFont { }) } - /// adds sample data to `samples` which is an i16 slice containing samples LRLRLRLR... + /// adds sample data to `samples` which is an f32 slice containing samples LRLRLRLR... /// (`samples` should have even length) /// volume (0 to 1) = volume of max velocity note. /// returns `Ok(true)` if the note should still be held. increments `request.hold_time` as needed. pub fn add_samples_interlaced( &mut self, request: &mut SamplesRequest, - samples: &mut [i16], + samples: &mut [f32], sample_rate: f64, ) -> Result { let key = request.key; @@ -1320,19 +1320,20 @@ impl SoundFont { // interpolate between one sample and the next // note: it's okay to do this even for samples where endloop = end, because // we added an additional sample to the end -- see (***) - let sample1 = data[s] as f32; - let sample2 = data[s + 1] as f32; + let sample1 = (data[s] as f32) * (1.0 / 32767.0); + let sample2 = (data[s + 1] as f32) * (1.0 / 32767.0); let mut sample = sample1 + (sample2 - sample1) * (s_frac as f32); sample *= falloff; falloff *= falloff_mul; - samples[2 * i] += (amplitude * sample * (1.0 - pan)) as i16; - samples[2 * i + 1] += (amplitude * sample * pan) as i16; + samples[2 * i] += amplitude * sample * (1.0 - pan); + samples[2 * i + 1] += amplitude * sample * pan; t += t_inc; } request.falloff = falloff; if falloff * request.volume < 1.0 / 32767.0 { + // no longer audible this_held = false; } final_t = f64::max(final_t, t); -- cgit v1.2.3