Compare commits
2 Commits
a09d9f83a4
...
bdd8f43ac3
Author | SHA1 | Date |
---|---|---|
annieversary | bdd8f43ac3 | |
annieversary | bdddffe119 |
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
Loading…
Reference in New Issue