diff options
author | Grant Shangreaux <grant@unabridgedsoftware.com> | 2021-11-07 00:37:27 -0500 |
---|---|---|
committer | Grant Shangreaux <grant@unabridgedsoftware.com> | 2021-11-07 00:37:27 -0500 |
commit | ef7fcf7a878143627af178a23d75e0301a40d677 (patch) | |
tree | bdf50a24e6c358a06cae6f8fe20025cceefa97a1 | |
parent | 715f8440e68fea6a7cfcedd409053d0579523e14 (diff) |
Feature: frequency modulation MVP implemented :loud-sound:
-rw-r--r-- | klangfarb/main.gd | 36 | ||||
-rw-r--r-- | klangfarbrs/src/lib.rs | 58 |
2 files changed, 63 insertions, 31 deletions
diff --git a/klangfarb/main.gd b/klangfarb/main.gd index 95c0f8f..4397a7e 100644 --- a/klangfarb/main.gd +++ b/klangfarb/main.gd @@ -18,11 +18,16 @@ export(float, 0.0, 1.0, 0.1) var sustain = 0.5 #Cutoff export(float, 20, 8000, 5) var cutoff = 6000 +# Frequency Modulation +export(bool) var frequency_modulation = false +export(float, 0.0, 100.0, 0.1) var fm_frequency = 1.0 +export(float, 0.0, 10.0, 0.1) var fm_amplitude = 0.1 + # load the GDNative script connected to the rust lib var MonoSynth = preload("res://MonoSynth.gdns") # make an instance of our one "class" in rust lib -var wave = MonoSynth.new() +var synth = MonoSynth.new() # initialize the Godot stream we fill up with samples var playback: AudioStreamPlayback = null @@ -32,25 +37,28 @@ func _fill_buffer() -> void: var to_fill = playback.get_frames_available() if to_fill > 0: # ask Rust to generate N frames at freq - # Array<Vector2> gets pushed to the + # Array<Vector2> gets pushed to the # playback stream buffer - playback.push_buffer(wave.frames(to_fill)) + playback.push_buffer(synth.frames(to_fill)) func _check_waveform(): if waveform == "square": - wave.square() + synth.square() elif waveform == "sine": - wave.sine() + synth.sine() elif waveform == "triangle": - wave.triangle() + synth.triangle() elif waveform == "sawtooth": - wave.sawtooth() + synth.sawtooth() func _process(_delta): if self.is_playing(): - wave.apply_bend(apply_bend) - wave.frequency(freq) - wave.phasor_bend(phasor_bend) + synth.apply_bend(apply_bend) + synth.frequency(freq) + synth.phasor_bend(phasor_bend) + synth.frequency_modulation(frequency_modulation) + synth.fm_frequency(fm_frequency) + synth.fm_depth(fm_amplitude) _check_waveform() _fill_buffer() @@ -58,7 +66,7 @@ func _ready() -> void: # buffer length of 100ms gives us ~realtime response to input changes self.stream.buffer_length = 0.1 # ensure Godot/Sine have the same sample rate - wave.set_sample_rate(self.stream.mix_rate) + synth.set_sample_rate(self.stream.mix_rate) # get our AudioStreamPlayback object playback = self.get_stream_playback() # prefill the stream's sample buffer (which feeds DAC) @@ -70,6 +78,6 @@ func _input(event): if event is InputEventMouseButton: print("Mouse Click/Unclick at: ", event.position) elif event is InputEventMouseMotion: -# freq = event.position.x - phasor_bend.x = event.position.x / 1024 - phasor_bend.y = event.position.y / 600 + freq = event.position.x +# phasor_bend.x = event.position.x / 1024 +# phasor_bend.y = event.position.y / 600 diff --git a/klangfarbrs/src/lib.rs b/klangfarbrs/src/lib.rs index 479885c..07e9cc7 100644 --- a/klangfarbrs/src/lib.rs +++ b/klangfarbrs/src/lib.rs @@ -11,14 +11,15 @@ use gdnative::prelude::*; use gdnative::core_types::TypedArray; use std::f32::consts::TAU; +/// Aliasing some types to distinguish various audio properties. type Sample = f32; -type Samples = f32; +type SamplesPerSecond = f32; type Hz = f32; type Phase = f32; type Amplitude = f32; type Millisecond = u32; -/// The various waveforms the `Synth` can generate. +/// The various waveforms the `MonoSynth` can generate. pub enum Waveform { Sine, Square, @@ -32,30 +33,25 @@ pub enum Waveform { pub struct MonoSynth { pub phasor: Phasor, pub waveform: Waveform, - #[property] - pub sample_rate: Samples, - #[property] + pub sample_rate: SamplesPerSecond, pub frequency: Hz, - #[property] pub apply_bend: bool, - #[property] pub phasor_bend: Vector2, - #[property] pub continuous: bool, - #[property] pub duration: Millisecond, - #[property] + // ADSR amplifier pub attack: Millisecond, - #[property] pub decay: Millisecond, - #[property] pub sustain: Amplitude, - #[property] pub release: Millisecond, - #[property] + // filter pub cutoff: Hz, // pub resonance: // pub modulator: + pub frequency_modulation: bool, + pub fm_frequency: Hz, + pub fm_depth: Amplitude, + fm_phasor: Phasor, } pub struct Osc {} @@ -101,7 +97,7 @@ impl Phasor { /// for a given wave form. Since audio signals are periodic, we can just calculate /// the first cycle of a wave repeatedly. This also prevents pitch drift caused by /// floating point errors over time. - pub fn next_phase(&self, frequency: Hz, sample_rate: Samples ) -> Phase { + pub fn next_phase(&self, frequency: Hz, sample_rate: SamplesPerSecond ) -> Phase { (self.phase + (frequency / sample_rate)) % 1.0 } } @@ -160,6 +156,10 @@ impl MonoSynth { sustain: 0.0, release: 0, cutoff: 0.0, + frequency_modulation: false, + fm_frequency: 30000.0, + fm_depth: 0.1, + fm_phasor: Phasor { phase: 0.0 } } } @@ -209,19 +209,43 @@ impl MonoSynth { } #[export] + fn frequency_modulation(&mut self, _owner: &Node, frequency_modulation: bool) { + self.frequency_modulation = frequency_modulation + } + + #[export] + fn fm_frequency(&mut self, _owner: &Node, fm_frequency: f32) { + self.fm_frequency = fm_frequency + } + + #[export] + fn fm_depth(&mut self, _owner: &Node, fm_depth: f32) { + self.fm_depth = fm_depth + } + + #[export] pub fn frames(&mut self, _owner: &Node, samples: i32) -> TypedArray<Vector2> { let mut frames = TypedArray::new(); for _i in 0..samples { let sample = Osc::generate_sample(&self.waveform, self.phasor.phase); - frames.push(Vector2::new(sample, sample)); + let next_phase : f32; + + if self.frequency_modulation { + let modulation_value = Osc::generate_sample(&Waveform::Sine, self.fm_phasor.phase) * self.fm_depth; + self.fm_phasor.phase = self.fm_phasor.next_phase(self.fm_frequency, self.sample_rate); + next_phase = self.phasor.next_phase(self.frequency * modulation_value, self.sample_rate); + } else { + next_phase = self.phasor.next_phase(self.frequency, self.sample_rate); + } - let next_phase = self.phasor.next_phase(self.frequency, self.sample_rate); if self.apply_bend { self.phasor.phase = Bender::bend(next_phase, self.phasor_bend); } else { self.phasor.phase = next_phase; } + + frames.push(Vector2::new(sample, sample)); } return frames |