summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorGrant Shangreaux <grant@unabridgedsoftware.com>2021-11-15 21:29:19 -0600
committerGrant Shangreaux <grant@unabridgedsoftware.com>2021-11-15 21:29:19 -0600
commita873c90ab9c305777f1e7bc4bfa24098cf9650d9 (patch)
tree71740158fecf940473c279d3c6b2ce11490c16f1 /docs
parentc7e88b7d605fd19d9ff54681a42fd69d079d92b1 (diff)
Docs: interface design updates and notes about Signal/Line
Diffstat (limited to 'docs')
-rw-r--r--docs/design.org81
-rw-r--r--docs/diagrams/pd-phasor-oscillator.pngbin0 -> 10559 bytes
2 files changed, 81 insertions, 0 deletions
diff --git a/docs/design.org b/docs/design.org
new file mode 100644
index 0000000..4f5e701
--- /dev/null
+++ b/docs/design.org
@@ -0,0 +1,81 @@
+* Mono Synth MVP
+
+A Rust library that drives a GDNative script and exposes an interface for a
+monophonic Synth object. This object will produce audio frames which will
+fill Godot's ~AudioStreamPlayback~ buffer, leveraging the cross-platform
+audio I/O that Godot provides. The Rust dynamic library will need to be built
+for each platform, but it will leverage Rust's perfomance optimizations to
+enable realtime audio interactions.
+
+** Godot Synth Interface
+
+From Godot, we're primarily interested in manipulating various aspects of
+the Mono Synth. Frequency, modulation, amplitude, and any other expressive
+aspect of a single tone we can hook up. Overall volume will be controlled
+by Godot.
+
+*** Input
+
+- *Sample Rate* - Godot must send its I/O sample rate to the synth when initialized
+ This is the number of audio samples played back per second. So we need to generate
+ samples at the same rate.
+- *Frequency* - range from 20Hz to ½ the sample rate. increment may need to change
+ over the range. Human ear detects small changes at lower frequencies, but not as
+ much in the higher frequencies.
+- *Continuous* - whether or not to produce a continuous tone or use ADSR envelope
+- *Waveform* - a selection of basic waveforms will be implemented in Rust. We can
+ control which one is being generated via a switch in Godot.
+- *Envelope* - this controls the volume of sound over time. Typical envelopes
+ have parameters for:
+ - *Attack* - amount of time before the waveform is at its peak
+ - *Decay* - amount of time it takes to go from peak to sustained level
+ - *Sustain* - the level (amplitude) to sustain while a note is held
+ - *Release* - the amount of time to go to 0 from sustain when note is released
+- *Frequency Modulator* - an oscillator that modulates the main tone generator which
+ has the effect of producing more complex sounds with sidebands of the main signal.
+ It has two controllable parameters:
+ - *Frequency multiple* integers create harmonic sidebands while non-integers make
+ inharmonic tones similar to a bell or metallic sound.
+ - *FM Depth* the amount of modulation to apply. Increasing by a lot can make some
+ interesting effects
+
+Not implemented (yet):
+
+- *Filter* - a low pass filter with a two parameters:
+ - *Cutoff* the frequency where the filter begins
+ - *Resonance* ? i think its like an amplitude boost at the cutoff?
+
+*** Output
+
+Currently, the output is an array of (sample, sample) vectors, where each sample is
+a float between -1.0 and 1.0. You can request a certain number of frames, and get back
+the calculated samples to fill them. We can think of this as essentially the stereo
+audio jack from a synthesizer.
+
+* Signal Generator Model
+
+In Puredata, one of the key abstractions is the Signal object, which all of the
+audio specific related bits implement. Signals abstract away time, and you can build
+an audio pipleine by connecting them together. A phasor drives an oscillator which
+sends its signal through a line amplitude signal to shape its volume.
+
+ [[file:diagrams/pd-phasor-oscillator.png]]
+
+This is roughly what we've built with Rust as iterators. Each ~.next()~ is like a
+tick of the =phasor~= object. =cos~= calculates the cosine value like our the
+~impl Iterator for Osc~ does in its ~.next()~ function.
+
+=line~=, when triggered by an input, produces a ramp from 0 to 1 over 1000 ms.
+It also "ticks" at the same rate as the other signals (the underlying sample rate).
+Each sample value from the cosine is multiplied by the values from the line signal.
+At the end of the 1000ms, the =*~= object is now constantly at the max amplitude.
+
+** Iterator as Signal ?
+
+Assuming we're calling ~next()~ on each part of our signal chain in the same cycle,
+at the same rate, we can utilize Rust's =Iterator= trait as our version of the Signal.
+
+This is how its working now. It is possible this will only get us so far, but
+I think we can roll with it. The currently implemented [[../klangfarbrs/src/envelope.rs][Envelope]] module is actually
+wrapping three of the =line~= type objects. ~attack~ ~decay~ and ~release~ could
+all be described by a ~Line~ struct that has a target amplitude and a duration.
diff --git a/docs/diagrams/pd-phasor-oscillator.png b/docs/diagrams/pd-phasor-oscillator.png
new file mode 100644
index 0000000..a89a7c0
--- /dev/null
+++ b/docs/diagrams/pd-phasor-oscillator.png
Binary files differ