summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrant Shangreaux <grant@unabridgedsoftware.com>2021-11-07 00:37:27 -0500
committerGrant Shangreaux <grant@unabridgedsoftware.com>2021-11-07 00:37:27 -0500
commitef7fcf7a878143627af178a23d75e0301a40d677 (patch)
treebdf50a24e6c358a06cae6f8fe20025cceefa97a1
parent715f8440e68fea6a7cfcedd409053d0579523e14 (diff)
Feature: frequency modulation MVP implemented :loud-sound:
-rw-r--r--klangfarb/main.gd36
-rw-r--r--klangfarbrs/src/lib.rs58
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