summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.rs27
-rw-r--r--src/midi_input.rs101
-rw-r--r--src/soundfont.rs291
3 files changed, 296 insertions, 123 deletions
diff --git a/src/main.rs b/src/main.rs
index 6c1dd9a..7be93f8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,3 @@
-#![allow(unused_imports)] // @TODO: delete me
extern crate cpal;
use std::io::Write;
@@ -56,7 +55,7 @@ fn midi_in_main() -> Result<(), String> {
while device.is_connected() {
while let Some(event) = device.read_event() {
- println!("{:?}",event);
+ println!("{:?}", event);
}
std::thread::sleep(std::time::Duration::from_millis(140));
if let Some(err) = device.get_error() {
@@ -109,7 +108,7 @@ fn soundfont_main() {
}
Some(x) => x,
};
- let supp_config: cpal::SupportedStreamConfig = chosen_config.with_max_sample_rate().into();
+ let supp_config: cpal::SupportedStreamConfig = chosen_config.with_max_sample_rate();
if supp_config.channels() != 2 {}
let config = supp_config.into();
let mut time = 0.0;
@@ -124,13 +123,20 @@ fn soundfont_main() {
*x = 0;
}
let sample_rate = config.sample_rate.0 as f64;
- match sf.add_samples_interlaced(125, 1.0, key, 60, time, data, sample_rate) {
+ for k in key..key+1 {
+ let mut request = sf.request(125, k, 60, 0.0).expect("ah");
+ request.set_hold_time(time);
+ request.set_volume(0.5);
+ request.set_tune(((key % 2) * 50) as _);
+ request.set_falloff(0.0, 0.01);
+ match sf.add_samples_interlaced(&request, data, sample_rate) {
Ok(false) => {} //{println!("stop")},
Err(e) => eprintln!("{}", e),
_ => {}
}
+ }
time += (data.len() as f64) / (2.0 * sample_rate);
- if time >= 1.0 {
+ if time >= 0.3 {
println!("{}", sf.cache_size());
time = 0.0;
key += 1;
@@ -149,10 +155,9 @@ fn soundfont_main() {
}
fn main() {
- match midi_in_main() {
- Err(e) => println!("{}", e),
- _ => {}
- }
- /*
- */
+ // match midi_in_main() {
+ // Err(e) => println!("{}", e),
+ // _ => {}
+ // }
+ soundfont_main();
}
diff --git a/src/midi_input.rs b/src/midi_input.rs
index 219a8eb..dd663b9 100644
--- a/src/midi_input.rs
+++ b/src/midi_input.rs
@@ -236,15 +236,19 @@ mod windows {
pub const MMSYSERR_NOMEM: u32 = 7;
pub const MMSYSERR_INVALFLAG: u32 = 10;
pub const MMSYSERR_INVALPARAM: u32 = 11;
-
+
pub const MM_MIM_DATA: u32 = 0x3C3;
-
+
pub fn midi_in_dev_get_caps(device_id: u32) -> Option<MidiInCapsW> {
let mut mic = MidiInCapsW {
..Default::default()
};
let result = unsafe {
- midiInGetDevCapsW(device_id, &mut mic as _, std::mem::size_of::<MidiInCapsW>() as u32)
+ midiInGetDevCapsW(
+ device_id,
+ &mut mic as _,
+ std::mem::size_of::<MidiInCapsW>() as u32,
+ )
};
if result == 0 {
Some(mic)
@@ -252,18 +256,17 @@ mod windows {
None
}
}
-
+
// returns u32::MAX on error.
pub fn midi_in_get_id(hmi: HMidiIn) -> u32 {
let mut device_id = u32::MAX;
unsafe { midiInGetID(hmi, (&mut device_id) as *mut u32) };
device_id
}
-
+
// returns empty string on error.
pub fn get_device_name(id: u32) -> String {
if let Some(mic) = midi_in_dev_get_caps(id) {
-
let mut name_len = 0;
while name_len < mic.pname.len() && mic.pname[name_len] != 0 {
name_len += 1;
@@ -273,7 +276,7 @@ mod windows {
String::new()
}
}
-
+
pub type MidiQueue = VecDeque<u8>;
pub static MIDI_QUEUES: Mutex<Vec<(HMidiIn, MidiQueue)>> = Mutex::new(vec![]);
pub extern "C" fn midi_callback(
@@ -287,7 +290,7 @@ mod windows {
// don't care
return;
}
-
+
if let Ok(mut queues) = MIDI_QUEUES.lock() {
for (dev, queue) in queues.iter_mut() {
if *dev == midi_in {
@@ -303,12 +306,12 @@ mod windows {
// events with 2 data bytes
queue.push_back(data1);
queue.push_back(data2);
- },
+ }
0b1100 | 0b1101 => {
// events with 1 data byte
queue.push_back(data1);
- },
- _ => {},
+ }
+ _ => {}
}
}
}
@@ -556,15 +559,8 @@ impl DeviceManager {
{
use windows::*;
let mut hmi = 0 as HMidiIn;
- let result = unsafe {
- midiInOpen(
- (&mut hmi) as _,
- id.id,
- midi_callback,
- 0,
- CALLBACK_FUNCTION,
- )
- };
+ let result =
+ unsafe { midiInOpen((&mut hmi) as _, id.id, midi_callback, 0, CALLBACK_FUNCTION) };
if result != 0 {
let result_str = match result {
@@ -582,15 +578,17 @@ impl DeviceManager {
result_str
)));
}
-
+
let mut queues = MIDI_QUEUES
.lock()
.map_err(|_| DeviceOpenError::Other("failed to lock mutex".to_string()))?;
queues.push((hmi, VecDeque::new()));
drop(queues);
-
- unsafe { midiInStart(hmi); }
-
+
+ unsafe {
+ midiInStart(hmi);
+ }
+
Ok(Device::new(hmi))
}
}
@@ -607,7 +605,7 @@ impl Device {
connected: true,
}
}
-
+
#[cfg(windows)]
fn new(hmi: windows::HMidiIn) -> Self {
let id = windows::midi_in_get_id(hmi);
@@ -619,7 +617,7 @@ impl Device {
last_status: 0,
}
}
-
+
fn read_raw(&mut self, buffer: &mut [u8]) -> usize {
#[cfg(unix)]
{
@@ -686,14 +684,14 @@ impl Device {
pub fn is_connected(&self) -> bool {
#[cfg(unix)]
{
- self.connected
+ self.connected
}
#[cfg(windows)]
{
- // there's no way of telling when a device is connected, it seems.
- // this is the best we can do.
- let name_expected = windows::get_device_name(windows::midi_in_get_id(self.hmi));
- !name_expected.is_empty() && name_expected == self.name
+ // there's no way of telling when a device is connected, it seems.
+ // this is the best we can do.
+ let name_expected = windows::get_device_name(windows::midi_in_get_id(self.hmi));
+ !name_expected.is_empty() && name_expected == self.name
}
}
@@ -723,25 +721,28 @@ impl Device {
// new status
self.last_status = status;
self.buffered_byte = None;
-
+
if (status >> 4) == 0b1111 {
return Some(Event::Other { status, data: None });
}
-
+
byte = self.read_byte()?;
if (byte & 0x80) != 0 {
// no data provided for MIDI event which should have data.
// what can we do...
// returning None is bad because it stops the stream.
- return Some(Event::Other { status, data: Some(byte & 0x7f) });
+ return Some(Event::Other {
+ status,
+ data: Some(byte & 0x7f),
+ });
}
}
-
+
let channel = self.last_status & 0xf;
-
+
// at this point we have a data byte
assert!((byte & 0x80) == 0);
-
+
match self.last_status >> 4 {
0b1000 | 0b1001 | 0b1010 | 0b1011 | 0b1110 => {
// event with two bytes of data.
@@ -755,19 +756,21 @@ impl Device {
note: data1,
vel: data2,
},
- 0b1001 => if data2 == 0 {
- Event::NoteOff {
- channel,
- note: data1,
- vel: data2,
+ 0b1001 => {
+ if data2 == 0 {
+ Event::NoteOff {
+ channel,
+ note: data1,
+ vel: data2,
+ }
+ } else {
+ Event::NoteOn {
+ channel,
+ note: data1,
+ vel: data2,
+ }
}
- } else {
- Event::NoteOn {
- channel,
- note: data1,
- vel: data2,
- }
- },
+ }
0b1010 => Event::NoteAftertouch {
channel,
note: data1,
diff --git a/src/soundfont.rs b/src/soundfont.rs
index 4cddb0b..7329641 100644
--- a/src/soundfont.rs
+++ b/src/soundfont.rs
@@ -11,6 +11,8 @@ IMPORTANT SOUNDFONT TERMINOLOGY:
a preset zone refers to an instrument
an object zone refers to a sample
a SAMPLE is a block of audio data with some properties of how it should be played
+
+modulators are not currently supported
*/
use std::fs::File;
use std::io::{Read, Seek, Write};
@@ -71,8 +73,16 @@ impl SFObject for Preset {
}
}
+#[derive(Debug, Clone, Copy)]
+enum SampleType {
+ Mono,
+ Left,
+ Right,
+}
+
#[derive(Debug)]
struct Sample {
+ r#type: SampleType,
start: u32,
len: u32,
startloop: u32,
@@ -105,6 +115,17 @@ pub enum SampleError {
NoFile,
}
+pub struct SamplesRequest {
+ hold_time: f64,
+ key: u8,
+ vel: u8,
+ falloff_start: f64,
+ falloff: f32,
+ tune: i32,
+ volume: f32,
+ zones: Vec<Zone>
+}
+
impl From<&OpenError> for String {
fn from(err: &OpenError) -> String {
use OpenError::*;
@@ -264,14 +285,13 @@ fn bad_sound_font(s: &str) -> OpenError {
OpenError::BadSoundFont(s.to_string())
}
-fn read_utf8_fixed_len(file: &mut File, len: usize, what: &str) -> Result<String, OpenError> {
+fn read_utf8_fixed_len(file: &mut File, len: usize) -> Result<String, OpenError> {
let mut name_vec = vec![0; len];
file.read_exact(&mut name_vec)?;
while !name_vec.is_empty() && name_vec[name_vec.len() - 1] == 0 {
name_vec.pop();
}
- String::from_utf8(name_vec)
- .map_err(|_| OpenError::BadSoundFont(format!("invalid UTF-8 in {}", what)))
+ Ok(String::from_utf8_lossy(&name_vec).to_string())
}
impl Zone {
@@ -311,6 +331,21 @@ impl Zone {
&& vel >= self.vel_range.0
&& vel <= self.vel_range.1
}
+
+ fn distance_to(&self, key: u8, vel: u8) -> u32 {
+ let key = key as i32;
+ let vel = vel as i32;
+ let key0 = self.key_range.0 as i32;
+ let key1 = self.key_range.1 as i32;
+ let vel0 = self.vel_range.0 as i32;
+ let vel1 = self.vel_range.1 as i32;
+ use std::cmp::min;
+ let key_dist = min((key - key0).abs(), (key - key1).abs()) as u32;
+ let vel_dist = min((vel - vel0).abs(), (vel - vel1).abs()) as u32;
+
+ // key matters more than velocity.
+ key_dist * 100 + vel_dist
+ }
// the zone for a note is generated by adding
fn add(zone1: &Self, zone2: &Self) -> Self {
@@ -513,6 +548,31 @@ fn read_gen_zones<Item: SFObject>(
Ok(())
}
+/// request for sound font samples.
+impl SamplesRequest {
+ /// set amount of time note has been playing for
+ pub fn set_hold_time(&mut self, t: f64) {
+ self.hold_time = t;
+ }
+
+ /// `tune` is in cents
+ pub fn set_tune(&mut self, tune: i32) {
+ self.tune = tune;
+ }
+
+ /// 0 = silent, 1 = max volume.
+ pub fn set_volume(&mut self, volume: f32) {
+ self.volume = volume;
+ }
+
+ /// amplitude will be multiplied by amount ^ (t - start).
+ /// e.g. when a user lets go of a piano key, you might call `set_falloff(release_time, 0.01)`
+ pub fn set_falloff(&mut self, start: f64, amount: f32) {
+ self.falloff = amount;
+ self.falloff_start = start;
+ }
+}
+
impl SoundFont {
/// Open a soundfont.
/// This does not load any sample data, since that would be slow
@@ -575,7 +635,7 @@ impl SoundFont {
if chunk_type == INAM {
if chunk_size < 256 {
let mut data = vec![0; chunk_size as usize];
- file.read(&mut data)?;
+ let _nread = file.read(&mut data)?;
data.pop(); // null terminator
if let Ok(n) = String::from_utf8(data) {
name = Some(n);
@@ -622,9 +682,6 @@ impl SoundFont {
fn new() -> Self {
Chunk { offset: 0, size: 0 }
}
- fn end(&self) -> u64 {
- self.offset + self.size as u64
- }
}
let mut inst = Chunk::new();
let mut ibag = Chunk::new();
@@ -686,30 +743,29 @@ impl SoundFont {
// --- read inst chunk ---
{
file.seek(std::io::SeekFrom::Start(inst.offset))?;
- loop {
- let name = read_utf8_fixed_len(&mut file, 20, "instrument name")?;
- if name.is_empty() {
+ let inst_count = inst.size / 22;
+ if inst_count < 2 || inst.size % 22 != 0 {
+ return Err(OpenError::BadSoundFont(format!("bad INST chunk size ({} should be at least 44, and a multiple of 22)", inst.size)));
+ }
+ for i in 0..inst_count {
+ println!("{:x}",file.stream_position()?);
+ let name = read_utf8_fixed_len(&mut file, 20)?;
+ if name.is_empty() && i != inst_count - 1 {
return Err(bad_sound_font("instrument with no name."));
}
+
+ // oddly, musescore's sf3 file has an empty name for the terminal record.
+ if i == inst_count - 1 && !name.is_empty() && name != "EOI" {
+ return Err(bad_sound_font("no terminal instrument."));
+ }
let bag_ndx = read_u16(&mut file)?;
- let is_eoi = name == "EOI";
-
instruments.push(Instrument {
name,
..Default::default()
});
instrument_bag_indices.push(bag_ndx);
-
- if is_eoi {
- // terminal instrument.
- break;
- }
-
- if file.stream_position()? >= inst.end() {
- return Err(bad_sound_font("No terminal instrument."));
- }
}
}
@@ -742,7 +798,7 @@ impl SoundFont {
)));
}
for _i in 0..phdr.size / 38 {
- let name = read_utf8_fixed_len(&mut file, 20, "preset name")?;
+ let name = read_utf8_fixed_len(&mut file, 20)?;
let _preset = read_u16(&mut file)?;
let _bank = read_u16(&mut file)?;
let bag_ndx = read_u16(&mut file)?;
@@ -778,7 +834,7 @@ impl SoundFont {
let mut samples = Vec::with_capacity(samples_count as usize);
for _i in 0..shdr.size / 46 {
// a sample
- let sample_name = read_utf8_fixed_len(&mut file, 20, "sample name")?;
+ let sample_name = read_utf8_fixed_len(&mut file, 20)?;
if sample_name == "EOS" {
break;
}
@@ -799,9 +855,17 @@ impl SoundFont {
}
let pitch_correction = read_i8(&mut file)?;
let _sample_link = read_u16(&mut file)?;
- let _sample_type = read_u16(&mut file)?;
-
+ let sample_type = read_u16(&mut file)?;
+ let r#type = match sample_type {
+ 2 => SampleType::Left,
+ 4 => SampleType::Right,
+ _ => SampleType::Mono // meh
+ };
+
+ println!("{} {} {}", sample_name,start, startloop);
+
let sample = Sample {
+ r#type: r#type,
start,
len: end - start,
startloop: startloop - start,
@@ -817,6 +881,32 @@ impl SoundFont {
instruments.pop(); // remove EOI
presets.pop(); // remove EOP
+ // check instrument & sample indices
+ for p in presets.iter() {
+ for zone in p.zones.iter() {
+ if let ZoneReference::Instrument(inst) = zone.reference {
+ if inst as usize >= instruments.len() {
+ return Err(OpenError::BadSoundFont(format!(
+ "preset zone references instrument {}, but there are only {} instruments.",
+ inst, instruments.len())));
+ }
+ }
+ }
+ }
+ for i in instruments.iter() {
+ for zone in i.zones.iter() {
+ if let ZoneReference::SampleID(sample) = zone.reference {
+ if sample as usize >= samples.len() {
+ return Err(OpenError::BadSoundFont(format!(
+ "instrument zone references sample {}, but there are only {} samples.",
+ sample,
+ samples.len()
+ )));
+ }
+ }
+ }
+ }
+
Ok(SoundFont {
file: Some(file),
sdta_offset,
@@ -884,14 +974,63 @@ impl SoundFont {
let inst = &self.instruments[i as usize];
for izone in inst.zones.iter() {
if izone.contains(key, vel) {
- zones.push(Zone::add(pzone, izone));
+ if let ZoneReference::SampleID(_) = izone.reference {
+ zones.push(Zone::add(pzone, izone));
+ }
}
}
}
}
}
- // @TODO: find closest zone if zones.len() == 0
+ if zones.is_empty() {
+ // that didn't work. try finding closest zone(s).
+ let mut closest_l = None;
+ let mut closest_l_dist = u32::MAX;
+ let mut closest_m = None;
+ let mut closest_m_dist = u32::MAX;
+ let mut closest_r = None;
+ let mut closest_r_dist = u32::MAX;
+ for pzone in preset.zones.iter() {
+ let d1 = pzone.distance_to(key, vel);
+ if let ZoneReference::Instrument(i) = pzone.reference {
+ let inst = &self.instruments[i as usize];
+ for izone in inst.zones.iter() {
+ let d2 = izone.distance_to(key, vel);
+ let dist = d1 + d2;
+ if let ZoneReference::SampleID(sample) = izone.reference {
+ match self.samples[sample as usize].r#type {
+ SampleType::Mono =>
+ if dist < closest_m_dist {
+ closest_m_dist = dist;
+ closest_m = Some(Zone::add(pzone, izone));
+ },
+ SampleType::Left =>
+ if dist < closest_l_dist {
+ closest_l_dist = dist;
+ closest_l = Some(Zone::add(pzone, izone));
+ },
+ SampleType::Right =>
+ if dist < closest_r_dist {
+ closest_r_dist = dist;
+ closest_r = Some(Zone::add(pzone, izone));
+ },
+ }
+ }
+ }
+ }
+ }
+
+
+ if let Some(m) = closest_m {
+ zones.push(m);
+ } else if let Some(l) = closest_l {
+ if let Some(r) = closest_r {
+ zones.push(l);
+ zones.push(r);
+ }
+ }
+ }
zones
}
@@ -903,7 +1042,7 @@ impl SoundFont {
for s in sample.data.iter() {
let bytes = i16::to_le_bytes(*s);
- out.write(&bytes).unwrap();
+ let _nwritten = out.write(&bytes).unwrap();
}
}
@@ -920,33 +1059,50 @@ impl SoundFont {
println!("{:?}", izone);
}
}
-
+
+ /// create a new sample request.
+ /// this struct is passed to `add_samples_interlaced()`
+ /// you might get slightly better performance if you create one request, and reuse it
+ /// (calling `set_hold_time()` each time), rather than creating a request
+ /// every time you need samples. you're probably fine either way.
+ pub fn request(&self, preset_idx: usize, key: u8, vel: u8, hold_time: f64) -> Result<SamplesRequest, SampleError> {
+ if preset_idx >= self.presets.len() {
+ return Err(SampleError::BadPreset);
+ }
+
+ let zones = self.get_zones(&self.presets[preset_idx], key, vel);
+ if zones.is_empty() {
+ return Err(SampleError::NoSamples);
+ }
+
+ Ok(SamplesRequest {
+ volume: 1.0,
+ key,
+ vel,
+ tune: 0,
+ hold_time,
+ falloff: 1.0,
+ falloff_start: 0.0,
+ zones
+ })
+ }
+
/// adds sample data to `samples` which is an i16 slice containing samples LRLRLRLR...
/// (`samples` should have even length)
/// volume in (0,1) = volume of max velocity note.
/// returns Ok(true) if the note should still be held
pub fn add_samples_interlaced(
&mut self,
- preset_idx: usize,
- volume: f32,
- // falloff: f32, @TODO
- key: u8,
- vel: u8,
- hold_time: f64,
+ request: &SamplesRequest,
samples: &mut [i16],
sample_rate: f64,
) -> Result<bool, SampleError> {
- if preset_idx >= self.presets.len() {
- return Err(SampleError::BadPreset);
- }
-
- let zones = self.get_zones(&self.presets[preset_idx], key, vel);
- if zones.len() == 0 {
- return Err(SampleError::NoSamples);
- }
+ let key = request.key;
+ let vel = request.vel;
+
let mut held = false;
- for zone in zones.iter() {
+ for zone in request.zones.iter() {
let sample = match zone.reference {
ZoneReference::SampleID(id) => &mut self.samples[id as usize],
_ => return Err(SampleError::NoSamples),
@@ -966,6 +1122,7 @@ impl SoundFont {
};
tune += (keynum as i32 - root_key as i32) * zone.scale_tuning as i32;
tune += sample.pitch_correction as i32;
+ tune += request.tune;
let freq_modulation =
f64::powf(1.0005777895065548 /* 2 ^ (1/1200) */, tune as f64);
@@ -982,24 +1139,26 @@ impl SoundFont {
} else {
vel
};
- let amplitude = volume * (velnum as f32) * (1.0 / 127.0);
+ let amplitude = request.volume * (velnum as f32) * (1.0 / 127.0);
- let mut startloop = (zone.startloop_offset as i64 + (sample.startloop as i64)) as usize;
- if startloop > sample.len as usize {
+ let mut startloop = (zone.startloop_offset as i64 + (sample.startloop as i64)) as u64;
+ if startloop > sample.len as u64 {
// uh this is bad
startloop = 0;
}
- let mut endloop = (zone.endloop_offset as i64 + (sample.endloop as i64)) as usize;
- if endloop > sample.len as usize {
+ let mut endloop = (zone.endloop_offset as i64 + (sample.endloop as i64)) as u64;
+ if endloop > sample.len as u64 {
// uh this is bad
- endloop = sample.len as usize;
+ endloop = sample.len as u64;
}
if endloop <= startloop {
// uh this is bad
startloop = 0;
- endloop = sample.len as usize;
+ endloop = sample.len as u64;
}
-
+ let data_start = zone.start_offset.clamp(0, sample.len as _);
+ let data_end = (sample.len as i32 + zone.end_offset).clamp(0, sample.len as _);
+
/*
//i've taken initial attenuation out because i dont want it (better to just control the volume).
//if you want it back in, multiply amplitude by 0.9885530946569389^attenuation = 0.1^(attenuation/200)
@@ -1009,24 +1168,30 @@ impl SoundFont {
// so a 10x larger sample will have 100x the power (and will be 20dB, not 10dB louder).
*/
- let mut t = hold_time;
- let data = &sample.data[..];
+ let mut t = request.hold_time;
+ let t_inc = 1.0 / sample_rate;
+ let data = &sample.data[data_start as usize..data_end as usize];
+ let tmul = freq_modulation * (sample.sample_rate as f64);
+ let mut falloff = f32::powf(request.falloff, (t - request.falloff_start) as f32);
+ let falloff_mul = f32::powf(request.falloff, t_inc as f32);
for i in 0..samples.len() / 2 {
- let mut s = (t * freq_modulation * sample.sample_rate as f64) as usize;
- if zone.loops {
- while s >= endloop {
- s = (s + startloop) - endloop;
- }
+ let mut s = (t * tmul) as u64;
+ if zone.loops && s >= startloop {
+ s = (s - startloop) % (endloop - startloop) + startloop;
}
- if s >= data.len() {
+ if s as usize >= data.len() {
this_held = false;
break;
}
- let sample = data[s] as f32;
-
+ let mut sample = data[s as usize] as f32;
+
+ sample *= falloff;
+ // in theory, this might cause problems because of floating-point precision
+ // in practice, it seems fine. and f32::pow is slow.
+ falloff *= falloff_mul;
samples[2 * i] += (amplitude * sample * (1.0 - pan)) as i16;
samples[2 * i + 1] += (amplitude * sample * pan) as i16;
- t += 1.0 / sample_rate;
+ t += t_inc;
}
held |= this_held;
}