summaryrefslogtreecommitdiff
path: root/docs/design.org
blob: d52ce05793c20804abc46701ff4fdf220b3ff922 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
* 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.

* Instrument (basic)

An Instrument is N Sine(?) waves with an Envelope applied to it.

(OscBank, Envelope)

it implements Iterator so that ~next()~ sums the oscillators,
scales them down by 1/N, and multiplies by the Envelope value.