From 1adebca8f06334d6b6ad87270eeeae3b08914568 Mon Sep 17 00:00:00 2001 From: pommicket Date: Thu, 6 Oct 2022 20:13:29 -0400 Subject: fixed bad sound --- src/main.rs | 158 +++++++++++++++++++++++++++++-------------------------- src/soundfont.rs | 16 ++++-- 2 files changed, 95 insertions(+), 79 deletions(-) (limited to 'src') diff --git a/src/main.rs b/src/main.rs index 5a226e0..9e05761 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,12 +6,12 @@ mod midi_input; mod soundfont; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; -use std::fs::File; use soundfont::SoundFont; +use std::ffi::c_int; +use std::fs::File; use std::io::Write; use std::sync::{Mutex, MutexGuard}; -use std::time::{Instant, Duration}; -use std::ffi::c_int; +use std::time::{Duration, Instant}; const NOTE_FALLOFF: f32 = 0.1; // falloff when note is released const CHANNEL_COUNT: usize = 17; // 16 MIDI channels + metronome @@ -37,7 +37,7 @@ struct MidiRecording { } struct WavRecording { - data: Vec + data: Vec, } struct NoteInfo { @@ -98,7 +98,7 @@ impl MidiRecording { data: Vec::with_capacity(1000), } } - + fn write(&mut self, byte: u8) { self.data.push(byte); } @@ -119,7 +119,7 @@ impl MidiRecording { self.write((value & 0x7f) as u8); } } - + fn write_event(&mut self, status: u8, data1: Option, data2: Option) { // check data if let Some(b) = data1 { @@ -132,7 +132,7 @@ impl MidiRecording { return; } } - + let now = Instant::now(); let ms_elapsed = now.duration_since(self.last_event_time).as_millis(); self.write_vlq(ms_elapsed as u32); @@ -145,7 +145,7 @@ impl MidiRecording { } self.last_event_time = now; } - + fn save(&self, filename: &str) -> std::io::Result<()> { print!("Saving MIDI recording..."); flush_stdout(); @@ -153,22 +153,22 @@ impl MidiRecording { .write(true) .create_new(true) .open(filename)?; - file.write("MThd".as_bytes())?; + file.write_all("MThd".as_bytes())?; write_u32_be(&mut file, 6)?; // header size write_u16_be(&mut file, 1)?; // = 1 write_u16_be(&mut file, 1)?; // = 1 - // tick = 1ms, quarter note = 600 ticks => 100bpm + // tick = 1ms, quarter note = 600 ticks => 100bpm write_u16_be(&mut file, 600)?; - file.write("MTrk".as_bytes())?; - + file.write_all("MTrk".as_bytes())?; + // MIDI "track end" // (people complain if we don't include this) let track_end = [0, 0xff, 0x2f, 0]; - + write_u32_be(&mut file, (self.data.len() + track_end.len()) as u32)?; // track size - file.write(&self.data)?; - file.write(&track_end)?; - + file.write_all(&self.data)?; + file.write_all(&track_end)?; + file.sync_all()?; println!("\rMIDI recording saved to {}", filename); Ok(()) @@ -181,7 +181,7 @@ impl WavRecording { data: Vec::with_capacity(441000), } } - + fn write(&mut self, samples: &[i16]) { if samples.len() + self.data.len() > (u32::MAX / 2 - 100) as usize { // too much data for wav file @@ -191,25 +191,22 @@ impl WavRecording { self.data.push(*x); } } - - fn save(&self, filename: &str, sample_rate: u32) -> std::io::Result<()> { + + fn save(&self, filename: &str, sample_rate: u32) -> std::io::Result<()> { print!("Saving WAV recording..."); // rust doesn't seem to be able to optimize out // a i16::to_le_bytes loop. annoying. let data8 = unsafe { - std::slice::from_raw_parts( - self.data.as_ptr() as *const u8, - 2 * self.data.len() - ) + std::slice::from_raw_parts(self.data.as_ptr() as *const u8, 2 * self.data.len()) }; flush_stdout(); let mut file = std::fs::OpenOptions::new() .write(true) .create_new(true) .open(filename)?; - file.write("RIFF".as_bytes())?; + file.write_all("RIFF".as_bytes())?; write_u32_le(&mut file, 36 + data8.len() as u32)?; // RIFF chunk size - file.write("WAVEfmt ".as_bytes())?; + file.write_all("WAVEfmt ".as_bytes())?; write_u32_le(&mut file, 16)?; // fmt chunk size write_u16_le(&mut file, 1)?; // PCM write_u16_le(&mut file, 2)?; // 2 channels @@ -217,13 +214,13 @@ impl WavRecording { write_u32_le(&mut file, sample_rate * 4)?; // "byte rate" write_u16_le(&mut file, 4)?; // block align write_u16_le(&mut file, 16)?; // bits per sample - file.write("data".as_bytes())?; + file.write_all("data".as_bytes())?; write_u32_le(&mut file, data8.len() as u32)?; // data chunk size - file.write(data8)?; + file.write_all(data8)?; file.sync_all()?; println!("\rWAV recording saved to {}", filename); Ok(()) - } + } } fn flush_stdout() { @@ -232,18 +229,18 @@ fn flush_stdout() { } } -fn write_u16_be(file: &mut File, n: u16) -> std::io::Result { - file.write(&mut u16::to_be_bytes(n)) +fn write_u16_be(file: &mut File, n: u16) -> std::io::Result<()> { + file.write_all(&u16::to_be_bytes(n)) } -fn write_u32_be(file: &mut File, n: u32) -> std::io::Result { - file.write(&mut u32::to_be_bytes(n)) +fn write_u32_be(file: &mut File, n: u32) -> std::io::Result<()> { + file.write_all(&u32::to_be_bytes(n)) } -fn write_u16_le(file: &mut File, n: u16) -> std::io::Result { - file.write(&mut u16::to_le_bytes(n)) +fn write_u16_le(file: &mut File, n: u16) -> std::io::Result<()> { + file.write_all(&u16::to_le_bytes(n)) } -fn write_u32_le(file: &mut File, n: u32) -> std::io::Result { - file.write(&mut u32::to_le_bytes(n)) +fn write_u32_le(file: &mut File, n: u32) -> std::io::Result<()> { + file.write_all(&u32::to_le_bytes(n)) } fn lock_note_info<'a>() -> MutexGuard<'a, NoteInfo> { @@ -254,7 +251,8 @@ fn lock_soundfont<'a>() -> MutexGuard<'a, Option> { SOUNDFONT.lock().expect("couldn't lock soundfont") } -fn lock_note_info_and_soundfont<'a>() -> (MutexGuard<'a, NoteInfo>, MutexGuard<'a, Option>) { +fn lock_note_info_and_soundfont<'a>( +) -> (MutexGuard<'a, NoteInfo>, MutexGuard<'a, Option>) { let note_info = lock_note_info(); let soundfont = lock_soundfont(); (note_info, soundfont) @@ -315,22 +313,24 @@ 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; for config in supported_configs { - if config.channels() != 2 || config.sample_format() != cpal::SampleFormat::I16 { + if config.channels() != 2 + || config.sample_format() != cpal::SampleFormat::I16 + || config.min_sample_rate().0 > srate + || config.max_sample_rate().0 < srate + { continue; } chosen_config = Some(config); } let chosen_config = match chosen_config { - None => { - return Err("Couldn't configure audio device.".to_string()) - } + None => return Err("Couldn't get desired audio properties.".to_string()), Some(x) => x, }; - // @TODO why do low sample rates sound bad - /// switch to f32 samples? - let supp_config: cpal::SupportedStreamConfig = chosen_config.with_max_sample_rate(); - if supp_config.channels() != 2 {} + + let supp_config: cpal::SupportedStreamConfig = + chosen_config.with_sample_rate(cpal::SampleRate(srate)); let config = supp_config.into(); let stream = audio_device @@ -393,7 +393,7 @@ fn play_note(channel: i64, note: i64, vel: i64) { } let (mut note_info, mut maybe_sf) = lock_note_info_and_soundfont(); - + if channel < 16 { if let Some(recording) = note_info.midi_recording.as_mut() { // note on event @@ -425,14 +425,14 @@ fn release_note(channel: i64, note: i64) { let note = note as u8; let mut note_info = lock_note_info(); - + if channel < 16 { if let Some(recording) = note_info.midi_recording.as_mut() { // note off event recording.write_event(0b1000_0000 | (channel as u8), Some(note), Some(0)); } } - + let pedal_down = note_info.pedal_down; let notes = &mut note_info.notes[channel]; if let Some(n) = notes.iter_mut().find(|n| n.key == note && n.down) { @@ -472,9 +472,6 @@ fn set_pitch_bend(amount: f64) { fn load_soundfont(filename: &str) { if let Ok(sf) = SoundFont::open(filename) { - for i in 0..sf.preset_count() { - println!("{}. {}", i, sf.preset_name(i).unwrap()); - } let mut sflock = lock_soundfont(); *sflock = Some(sf); } else { @@ -482,9 +479,17 @@ fn load_soundfont(filename: &str) { } } +fn print_presets() { + if let Some(sf) = lock_soundfont().as_ref() { + for i in 0..sf.preset_count() { + println!("{}. {}", i, sf.preset_name(i).unwrap()); + } + } +} + fn load_preset(channel: i64, preset: i64) { let preset = preset as usize; - + let (mut note_info, mut soundfont) = lock_note_info_and_soundfont(); if channel == -1 { for p in note_info.presets.iter_mut() { @@ -497,14 +502,12 @@ fn load_preset(channel: i64, preset: i64) { } note_info.presets[channel] = preset; } - + if let Some(sf) = soundfont.as_mut() { if preset >= sf.preset_count() { eprintln!("preset {} out of range", preset); - } else { - if let Err(e) = sf.load_samples_for_preset(preset) { - eprintln!("error loading preset {}: {}", preset, e); - } + } else if let Err(e) = sf.load_samples_for_preset(preset) { + eprintln!("error loading preset {}: {}", preset, e); } } } @@ -542,7 +545,9 @@ fn stop_midi_recording_err() -> std::io::Result<()> { let mut note_info = lock_note_info(); if let Some(recording) = note_info.midi_recording.take() { drop(note_info); - let name = chrono::Local::now().format("%Y-%m-%d-%H-%M-%S.mid").to_string(); + let name = chrono::Local::now() + .format("%Y-%m-%d-%H-%M-%S.mid") + .to_string(); recording.save(&name)?; } Ok(()) @@ -565,7 +570,9 @@ fn stop_wav_recording_err() -> std::io::Result<()> { if let Some(recording) = note_info.wav_recording.take() { let sample_rate = note_info.output_sample_rate; drop(note_info); - let name = chrono::Local::now().format("%Y-%m-%d-%H-%M-%S.wav").to_string(); + let name = chrono::Local::now() + .format("%Y-%m-%d-%H-%M-%S.wav") + .to_string(); recording.save(&name, sample_rate)?; } Ok(()) @@ -595,10 +602,9 @@ fn call_fn_if_exists( } } - const SIGINT: c_int = 2; -type SigHandler = extern "C" fn (c_int); +type SigHandler = extern "C" fn(c_int); extern "C" { fn signal(signum: c_int, handler: SigHandler) -> SigHandler; @@ -612,10 +618,11 @@ extern "C" fn sig_handler(_signum: c_int) { fn main() { unsafe { signal(SIGINT, sig_handler) }; - + let mut engine = rhai::Engine::new(); engine.set_max_expr_depths(0, 0); engine.register_fn("pm_load_soundfont", load_soundfont); + engine.register_fn("pm_print_presets", print_presets); engine.register_fn("pm_load_preset", load_preset); engine.register_fn("pm_play_note", play_note); engine.register_fn("pm_release_note", release_note); @@ -635,26 +642,29 @@ fn main() { let engine = engine; // de-multablify let args: Vec = std::env::args().collect(); let config_filename = match args.len() { - 0 | 1 => "config.rhai", - 2 => &args[1], - _ => { - eprintln!("Usage: {} [config file]", args[0]); - return; - } + 0 | 1 => "config.rhai", + 2 => &args[1], + _ => { + eprintln!("Usage: {} [config file]", args[0]); + return; + } }; let mut ast = match engine.compile_file(config_filename.into()) { Ok(x) => x, Err(e) => { eprintln!("Config error: {}", e); return; - }, + } }; - + if let Err(e) = engine.run_ast(&ast) { - eprintln!("Error running top-level statements in {}: {}", config_filename, e); + eprintln!( + "Error running top-level statements in {}: {}", + config_filename, e + ); return; } - + let mut midi_device; { let mut idx = -1; @@ -689,12 +699,12 @@ fn main() { return; } }; - + { let mut note_info = lock_note_info(); note_info.output_sample_rate = sample_rate; } - + if let Err(e) = stream.play() { eprintln!("Error starting audio stream: {}", e); return; diff --git a/src/soundfont.rs b/src/soundfont.rs index 7d0cba5..efa60dd 100644 --- a/src/soundfont.rs +++ b/src/soundfont.rs @@ -722,7 +722,7 @@ impl SoundFont { if chunk_type == INAM { if chunk_size < 256 { let mut data = vec![0; chunk_size as usize]; - let _nread = file.read(&mut data)?; + file.read_exact(&mut data)?; data.pop(); // null terminator if let Ok(n) = String::from_utf8(data) { name = Some(n); @@ -1164,7 +1164,7 @@ impl SoundFont { for s in sample.data.iter() { let bytes = i16::to_le_bytes(*s); - let _nwritten = out.write(&bytes).unwrap(); + out.write_all(&bytes).unwrap(); } } @@ -1302,15 +1302,21 @@ impl SoundFont { let mut falloff = request.falloff; let falloff_mul = f32::powf(request.falloff_speed, (1.0 / sample_rate) as f32); for i in 0..samples.len() / 2 { - let mut s = (t * tmul) as u64; + let mut s_frac = t * tmul; + let mut s = s_frac as u64; + s_frac -= s as f64; if zone.loops && s >= startloop { s = (s - startloop) % (endloop - startloop) + startloop; } - if s as usize >= data.len() { + let s = s as usize; + if s + 1 >= data.len() { this_held = false; break; } - let mut sample = data[s as usize] as f32; + // interpolate between one sample and the next + let sample1 = data[s] as f32; + let sample2 = data[s + 1] as f32; + let mut sample = sample1 + (sample2 - sample1) * (s_frac as f32); sample *= falloff; falloff *= falloff_mul; -- cgit v1.2.3