From a873c90ab9c305777f1e7bc4bfa24098cf9650d9 Mon Sep 17 00:00:00 2001 From: Grant Shangreaux Date: Mon, 15 Nov 2021 21:29:19 -0600 Subject: Docs: interface design updates and notes about Signal/Line --- docs/design.org | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 docs/design.org (limited to 'docs/design.org') 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. -- cgit v1.2.3