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 --- design.org | 50 -------------------- docs/design.org | 81 +++++++++++++++++++++++++++++++++ docs/diagrams/pd-phasor-oscillator.png | Bin 0 -> 10559 bytes 3 files changed, 81 insertions(+), 50 deletions(-) delete mode 100644 design.org create mode 100644 docs/design.org create mode 100644 docs/diagrams/pd-phasor-oscillator.png diff --git a/design.org b/design.org deleted file mode 100644 index 5e00379..0000000 --- a/design.org +++ /dev/null @@ -1,50 +0,0 @@ -* 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. -- *Phasor* - the shape of the waveform can be dynamically modified by changing the - shape of the phasor driving the oscillator. Given an (x,y) vector, the shape of - the phasor can be "bent" away from it smooth 0-1 slope. -- *Amplitude 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 -- *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? -- *Modulator* - a low frequency oscillator that can effect the frequency or filter 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. - - 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 Binary files /dev/null and b/docs/diagrams/pd-phasor-oscillator.png differ -- cgit v1.2.3