Skip to content

Instantly share code, notes, and snippets.

@crsaracco
Created November 15, 2017 00:56
Show Gist options
  • Save crsaracco/a744db19b7a22bbdf5f27ffc4bc329c5 to your computer and use it in GitHub Desktop.
Save crsaracco/a744db19b7a22bbdf5f27ffc4bc329c5 to your computer and use it in GitHub Desktop.
Sine generator
#[macro_use] extern crate vst2;
use vst2::buffer::AudioBuffer;
use vst2::plugin::{Category, Plugin, Info, CanDo};
use vst2::event::Event;
use vst2::api::{Supported, Events};
use std::f64::consts::PI;
/// Convert the midi note's pitch into the equivalent frequency.
///
/// This function assumes A4 is 440hz.
fn midi_pitch_to_freq(pitch: u8) -> f64 {
const A4_PITCH: i8 = 69;
const A4_FREQ: f64 = 440.0;
(((pitch as i8 - A4_PITCH) as f64) / 12.).exp2() * A4_FREQ
}
struct SineSynth {
sample_rate: f64,
time: f64,
last_frequency: f64,
last_amplitude: f64,
note_duration: f64,
note_end: f64,
note: Option<u8>,
parameter: f32,
parameter_text: String,
}
impl SineSynth {
fn time_per_sample(&self) -> f64 {
1.0 / self.sample_rate
}
/// Process an incoming midi event.
///
/// The midi data is split up like so:
///
/// `data[0]`: Contains the status and the channel. Source: [source]
/// `data[1]`: Contains the supplemental data for the message - so, if this was a NoteOn then
/// this would contain the note.
/// `data[2]`: Further supplemental data. Would be velocity in the case of a NoteOn message.
///
/// [source]: http://www.midimountain.com/midi/midi_status.htm
fn process_midi_event(&mut self, data: [u8; 3]) {
match data[0] {
128 => self.note_off(data[1]),
144 => self.note_on(data[1]),
_ => ()
}
}
fn note_on(&mut self, note: u8) {
self.note_duration = 0.0;
self.note = Some(note)
}
fn note_off(&mut self, note: u8) {
if self.note == Some(note) {
self.note = None
}
}
}
pub const TAU: f64 = PI * 2.0;
pub const ATTACK_DECAY: f64 = 1024.0 * 4.0;
impl Default for SineSynth {
fn default() -> SineSynth {
SineSynth {
sample_rate: 44100.0,
note_duration: 0.0,
note_end: 0.0,
time: 0.0,
last_frequency: 0.0,
last_amplitude: 0.0,
note: None,
parameter: 1.0,
parameter_text: "0.0".to_string(),
}
}
}
impl Plugin for SineSynth {
fn get_info(&self) -> Info {
Info {
name: "SineSynth".to_string(),
vendor: "crsaracco".to_string(),
unique_id: 6667,
category: Category::Synth,
inputs: 2,
outputs: 2,
parameters: 1,
initial_delay: 0,
..Info::default()
}
}
#[allow(unused_variables)]
fn process_events(&mut self, events: &Events) {
for event in events.events() {
match event {
Event::Midi(ev) => self.process_midi_event(ev.data),
// More events can be handled here.
_ => ()
}
}
}
fn set_sample_rate(&mut self, rate: f32) {
self.sample_rate = rate as f64;
}
fn process(&mut self, buffer: &mut AudioBuffer<f32>) {
let samples = buffer.samples();
let per_sample = self.time_per_sample();
for (input_buffer, output_buffer) in buffer.zip() {
let mut t = self.time;
for (_, output_sample) in input_buffer.iter().zip(output_buffer) {
if let Some(current_note) = self.note {
let frequency = midi_pitch_to_freq(current_note);
self.last_frequency = frequency;
let signal = (t * frequency * TAU).sin();
let mut current_amplitude = self.last_amplitude + 1.0 / ATTACK_DECAY;
if current_amplitude > 1.0 {
current_amplitude = 1.0;
}
*output_sample = (signal * current_amplitude * 0.9) as f32;
self.note_duration += 1.0;
self.note_end = 0.0;
self.last_amplitude = current_amplitude;
} else if self.last_amplitude >= 0.0001 {
let signal = (t * self.last_frequency * TAU).sin();
let mut current_amplitude = self.last_amplitude - 1.0 / ATTACK_DECAY;
if current_amplitude < 0.0 {
current_amplitude = 0.0;
}
*output_sample = (signal * current_amplitude * 0.9) as f32;
self.note_end += 1.0;
self.last_amplitude = current_amplitude;
} else {
*output_sample = 0.0;
self.last_amplitude = 0.0;
}
t += per_sample;
}
}
self.time += samples as f64 * per_sample;
}
fn can_do(&self, can_do: CanDo) -> Supported {
match can_do {
CanDo::ReceiveMidiEvent => Supported::Yes,
_ => Supported::Maybe
}
}
fn get_parameter(&self, index: i32) -> f32 {
match index {
0 => self.parameter,
_ => 0.0,
}
}
fn set_parameter(&mut self, index: i32, value: f32) {
match index {
// We don't want to divide by zero, so we'll clamp the value
0 => self.parameter = value.max(0.01),
_ => (),
}
}
fn get_parameter_name(&self, index: i32) -> String {
match index {
0 => "Parameter thing".to_string(),
_ => "".to_string(),
}
}
fn get_parameter_text(&self, index: i32) -> String {
match index {
// Convert to a percentage
0 => format!("{}", self.parameter_text),
_ => "".to_string(),
}
}
fn get_parameter_label(&self, index: i32) -> String {
match index {
0 => "Hz".to_string(),
_ => "".to_string(),
}
}
}
plugin_main!(SineSynth);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment