Compare commits

...

2 Commits

Author SHA1 Message Date
annieversary bdd8f43ac3 [velociter] implement repetition 2021-09-18 17:45:17 +02:00
annieversary bdddffe119 [transmute pitch] add min_note_len 2021-09-18 17:44:59 +02:00
3 changed files with 72 additions and 26 deletions

View File

@ -261,12 +261,15 @@ analyzes pitch of incoming audio and sends out the corresponding midi note
params: params:
- `passthrough`: how much of the original audio to be let through - `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 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 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 ### reverter

View File

@ -14,18 +14,26 @@ baseplug::model! {
#[model(min = 0.0, max = 1.0)] #[model(min = 0.0, max = 1.0)]
#[parameter(name = "passthrough")] #[parameter(name = "passthrough")]
passthrough: f32, passthrough: f32,
#[model(min = 0.0, max = 2.0)]
#[parameter(name = "min note length")]
min_note_len: f32,
} }
} }
impl Default for TransmutePitchModel { impl Default for TransmutePitchModel {
fn default() -> Self { fn default() -> Self {
Self { passthrough: 1.0 } Self {
passthrough: 0.0,
min_note_len: 0.0,
}
} }
} }
struct TransmutePitch { struct TransmutePitch {
detector_thread: pitch_detection::PitchDetectorThread<BUFFER_LEN>, detector_thread: pitch_detection::PitchDetectorThread<BUFFER_LEN>,
last_note: Option<u8>, last_note: Option<u8>,
last_note_time: Option<usize>,
counter: usize,
} }
impl Plugin for TransmutePitch { impl Plugin for TransmutePitch {
@ -44,6 +52,8 @@ impl Plugin for TransmutePitch {
Self { Self {
detector_thread, detector_thread,
last_note: None, 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 output = &mut ctx.outputs[0].buffers;
let enqueue_midi = &mut ctx.enqueue_event; 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 { for i in 0..ctx.nframes {
self.counter += 1;
output[0][i] = model.passthrough[i] * input[0][i]; output[0][i] = model.passthrough[i] * input[0][i];
output[1][i] = model.passthrough[i] * input[1][i]; output[1][i] = model.passthrough[i] * input[1][i];
@ -61,13 +77,17 @@ impl Plugin for TransmutePitch {
self.detector_thread self.detector_thread
.write(input[0][i], 0.0, ctx.sample_rate as u32); .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 // Try to get a processed buffer from the processor thread
match self.detector_thread.try_get_pitch() { match self.detector_thread.try_get_pitch() {
Some((Some(pitch), _)) => { Some((Some(pitch), _)) => {
let note = pitch_to_midi_note(pitch); let note = pitch_to_midi_note(pitch);
// If note changed // 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 // Send note off for last note
if let Some(last_note) = self.last_note { if let Some(last_note) = self.last_note {
let note_off = Event::<TransmutePitch> { let note_off = Event::<TransmutePitch> {
@ -86,18 +106,24 @@ impl Plugin for TransmutePitch {
// Update note // Update note
self.last_note = Some(note); self.last_note = Some(note);
self.last_note_time = Some(self.counter);
} }
} }
Some((None, _)) => { Some((None, _)) => {
if let Some(last_note) = self.last_note { if min_len < (self.counter - self.last_note_time.unwrap_or(0)) as f64 {
let note_off = Event::<TransmutePitch> { if let Some(last_note) = self.last_note {
frame: i, let note_off = Event::<TransmutePitch> {
data: Data::Midi([0x80, last_note, 0]), frame: i,
}; data: Data::Midi([0x80, last_note, 0]),
enqueue_midi(note_off); };
} 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;
}
} }
_ => {} _ => {}
} }

View File

@ -13,6 +13,9 @@ baseplug::model! {
#[model(min = 0.0, max = 127.9)] #[model(min = 0.0, max = 127.9)]
#[parameter(name = "max_vel")] #[parameter(name = "max_vel")]
max_vel: f32, max_vel: f32,
#[model(min = 0.0, max = 10.)]
#[parameter(name = "repetition")]
repetition: f32,
} }
} }
@ -21,12 +24,15 @@ impl Default for VelociterModel {
Self { Self {
min_vel: 0.0, min_vel: 0.0,
max_vel: 127.9, max_vel: 127.9,
repetition: 0.0,
} }
} }
} }
struct Velociter { struct Velociter {
notes: Vec<[u8; 3]>, notes: Vec<[u8; 3]>,
last_val: u8,
counter: u8,
} }
impl Plugin for Velociter { impl Plugin for Velociter {
@ -43,6 +49,8 @@ impl Plugin for Velociter {
fn new(_sample_rate: f32, _model: &VelociterModel) -> Self { fn new(_sample_rate: f32, _model: &VelociterModel) -> Self {
Self { Self {
notes: Vec::with_capacity(300), notes: Vec::with_capacity(300),
last_val: 0,
counter: u8::MAX,
} }
} }
@ -55,23 +63,32 @@ impl Plugin for Velociter {
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
let mut rng = thread_rng(); let mut rng = thread_rng();
let min = model.min_vel[0];
let max = model.max_vel[0];
// make sure they're right
let (min, max) = (min.min(max), max.max(min));
for mut note in self.notes.drain(0..) {
let val = if model.repetition[0] < self.counter as f32 {
self.counter = 0;
rng.gen_range(min..max).trunc() as u8
} else {
self.counter += 1;
self.last_val
};
self.last_val = val;
note[2] = val;
let note = Event::<Velociter> {
frame: 0,
data: Data::Midi(note),
};
enqueue_midi(note);
}
for i in 0..ctx.nframes { for i in 0..ctx.nframes {
output[0][i] = input[0][i]; output[0][i] = input[0][i];
output[1][i] = input[1][i]; output[1][i] = input[1][i];
let min = model.min_vel[i];
let max = model.max_vel[i];
// make sure they're right
let (min, max) = (min.min(max), max.max(min));
for mut note in self.notes.drain(0..) {
note[2] = rng.gen_range(min..max).trunc() as u8;
let note = Event::<Velociter> {
frame: 0,
data: Data::Midi(note),
};
enqueue_midi(note);
}
} }
} }
} }
@ -81,4 +98,4 @@ impl MidiReceiver for Velociter {
} }
} }
baseplug::vst2!(Velociter, b"tAnE"); baseplug::vst2!(Velociter, b"iter");