diff --git a/README.md b/README.md index 5c97ebb..cdef404 100644 --- a/README.md +++ b/README.md @@ -261,12 +261,15 @@ analyzes pitch of incoming audio and sends out the corresponding midi note params: - `passthrough`: how much of the original audio to be let through +- `min note length`: how many beats a note will last latency should be close to 1024 samples, which is around 20ms at 48k i think. the pitch detection is not excellent and it's monophonic, so if you have a noisy input or a bunch of notes at the same time, it's anyone's guess what the detected note will be. there's also no pitch bending or anything fancy like that, so it'll jump around notes even if the input has portamento aside from that, it works well enough on my quick tests -you can use it to play synths using other instruments, like a guitar, or by whistling! it's quite fun +you can use it to play synths using other instruments, like a guitar or by whistling! it's quite fun + +`min note length` controls the note length, so if you see that the detected note jumps around a bunch, you can raise this so the notes last longer, and it'll be a bit smoother ### reverter diff --git a/crates/transmute_pitch/src/lib.rs b/crates/transmute_pitch/src/lib.rs index 19644df..5e9cd4e 100644 --- a/crates/transmute_pitch/src/lib.rs +++ b/crates/transmute_pitch/src/lib.rs @@ -14,18 +14,26 @@ baseplug::model! { #[model(min = 0.0, max = 1.0)] #[parameter(name = "passthrough")] passthrough: f32, + #[model(min = 0.0, max = 2.0)] + #[parameter(name = "min note length")] + min_note_len: f32, } } impl Default for TransmutePitchModel { fn default() -> Self { - Self { passthrough: 1.0 } + Self { + passthrough: 0.0, + min_note_len: 0.0, + } } } struct TransmutePitch { detector_thread: pitch_detection::PitchDetectorThread, last_note: Option, + last_note_time: Option, + counter: usize, } impl Plugin for TransmutePitch { @@ -44,6 +52,8 @@ impl Plugin for TransmutePitch { Self { detector_thread, last_note: None, + last_note_time: None, + counter: 0, } } @@ -53,7 +63,13 @@ impl Plugin for TransmutePitch { let output = &mut ctx.outputs[0].buffers; let enqueue_midi = &mut ctx.enqueue_event; + let curr_bpm = ctx.musical_time.bpm; + let beat_in_seconds = 60.0 / curr_bpm; + let samples_in_beat = beat_in_seconds * ctx.sample_rate as f64; + for i in 0..ctx.nframes { + self.counter += 1; + output[0][i] = model.passthrough[i] * input[0][i]; output[1][i] = model.passthrough[i] * input[1][i]; @@ -61,13 +77,17 @@ impl Plugin for TransmutePitch { self.detector_thread .write(input[0][i], 0.0, ctx.sample_rate as u32); + let min_len = model.min_note_len[i] as f64 * samples_in_beat; + // Try to get a processed buffer from the processor thread match self.detector_thread.try_get_pitch() { Some((Some(pitch), _)) => { let note = pitch_to_midi_note(pitch); // If note changed - if self.last_note != Some(note) { + if self.last_note != Some(note) + && min_len < (self.counter - self.last_note_time.unwrap_or(0)) as f64 + { // Send note off for last note if let Some(last_note) = self.last_note { let note_off = Event:: { @@ -86,18 +106,24 @@ impl Plugin for TransmutePitch { // Update note self.last_note = Some(note); + self.last_note_time = Some(self.counter); } } Some((None, _)) => { - if let Some(last_note) = self.last_note { - let note_off = Event:: { - frame: i, - data: Data::Midi([0x80, last_note, 0]), - }; - enqueue_midi(note_off); - } + if min_len < (self.counter - self.last_note_time.unwrap_or(0)) as f64 { + if let Some(last_note) = self.last_note { + let note_off = Event:: { + frame: i, + data: Data::Midi([0x80, last_note, 0]), + }; + enqueue_midi(note_off); + } - self.last_note = None; + self.last_note = None; + self.last_note_time = None; + // restart the counter so we don't go super far away ig + self.counter = min_len as usize; + } } _ => {} }