add some more documentation

This commit is contained in:
2023-08-24 16:27:23 +02:00
parent 2c2c3d6957
commit b9549d0b08
3 changed files with 963 additions and 44 deletions

View File

@ -31,7 +31,7 @@ Press ${key_shortcut(
<pre><code class="language-javascript">
bpm(80)
mod(0.25) :: sound('sawtooth')
.midi(seqbar(
.note(seqbar(
pick(60, 67, 63) - 12, pick(60, 67, 63) - 12,
pick(60, 67, 63) - 12 + 5, pick(60, 67, 63) - 12 + 5,
pick(60, 67, 63) - 12 + 7, pick(60, 67, 63) - 12 + 7) + (sometimes() ? 24 : 12))
@ -322,17 +322,13 @@ mod(0.25) && midi(60)
`;
const sound: string = `
# Sample playback
# Audio engine
The Topos audio engine is based on the [SuperDough](https://www.npmjs.com/package/superdough) audio backend. It is a very powerful and flexible audio backend. It is based on the [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API) and is capable of playing samples, synths, and effects. It is also capable of playing samples and synths in a polyphonic way. It is a very powerful tool to create complex sounds and textures. A set of default sounds are already provided by default but you can also load your own audio samples. They will be loaded through a special URL scheme using the <icode>sample</icode> function.
The Topos audio engine is based on the [SuperDough](https://www.npmjs.com/package/superdough) audio backend, leveraging the [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API). The engine is capable of playing multiple samples, synths and effects at once. It is a very powerful and almost limitless tool to create complex sounds and textures. A set of default sounds are already provided but you can also load your own audio samples and synths!
I recommended you to run the following scripts in the global script (${key_shortcut(
"Ctrl + G"
)}).
## Sound basics
## Audio Engine
The basic function to play a sound is <icode>sound('sample/synth').out()</icode>. If the given sound exists in the database, it will be automatically queried and will start playing once loaded. To play a very basic beat, evaluate the following script:
The basic function to play a sound is... <icode>sound(name: string)</icode> (you can also write <icode>snd</icode> to save some precious time). If the given sound or synthesizer exists in the database, it will be automatically queried/started and will start playing. Evaluate the following script in the global window:
\`\`\`javascript
mod(1) && sound('bd').out()
@ -344,9 +340,43 @@ In plain english, this translates to:
> Every 48 pulses, play a kick drum.
> Every 24 pulses, play a high-hat.
If you remove the **mod** instruction, you will end up with a deluge of kick drums and high-hats. The **mod** instruction is used to filter out pulses. It is a very useful instruction to create basic rhythms. The **mod** function checks if the current pulse is a multiple of the given number. If it is, it returns <icode>true</icode>, otherwise it returns <icode>false</icode>. You will find a lot of these kind of logical functions in Topos.
Let's make it slightly more complex:
## Pick a sample
\`\`\`javascript
mod(1) && sound('bd').coarse(0.25).out()
mod(0.5) && sound('hh').delay(0.25).delaytime(0.125).out()
\`\`\`
Now, it reads as follow:
> Every 48 pulses, play a kick drum with some amount of distortion.
> Every 24 pulses, play a high-hat with 25% of the sound injected in
> a delay unit, with a delay time of 0.125 seconds.
Let's pause for a moment to explain what we just wrote. There are many things to be said:
- If you remove the **mod** instruction, you will end up with a deluge of kick drums and high-hats. The **mod** instruction is used to filter out pulses. It is a very useful instruction to create basic rhythms. Check out the **Time** page if you haven't read it already.
- Playing a sound always ends up with the <icode>.out()</icode> method that gives the instruction to send a message to the audio engine.
- Sounds are **composed** by adding qualifiers that will modify the sound or synthesizer being played (_e.g_ <icode>sound('...').blabla(...)..something(...).out()</icode>.
## Audio Sample Folders / Sample Files
When you type <icode>kick</icode> in the <icode>sound('kick').out()</icode> expression, you are referring to a sample folder containing multiple audio samples. If you look at the sample folder, it would look something like this:
\`\`\`shell
.
├── KICK9.wav
├── kick1.wav
├── kick10.wav
├── kick2-1.wav
├── kick2.wav
├── kick3-1.wav
├── kick3.wav
├── kick4.wav
├── kick5.wav
├── kick6.wav
├── kick7.wav
└── kick8.wav
\`\`\`
The <icode>.n(number)</icode> method can be used to pick a sample from the currently selected sample folder. For instance, the following script will play a random sample from the _kick_ folder:
@ -354,11 +384,18 @@ The <icode>.n(number)</icode> method can be used to pick a sample from the curre
mod(1) && sound('kick').n(pick(1,2,3,4,5,6,7,8)).out()
\`\`\`
Don't worry about the number. If it gets too big, it will be automatically wrapped to the number of samples in the folder.
Don't worry about the number. If it gets too big, it will be automatically wrapped to the number of samples in the folder. You can type any number, it will always fall on a sample. Let's use our mouse to select a sample number in a folder:
## Sound Chains
\`\`\`javascript
// Move your mouse to change the sample being used!
mod(.25) && sound('numbers').n(Math.floor(mouseX())).out()
\`\`\`
The <icode>sound('sample_name')</icode> function can be chained to _specify_ a sound more. For instance, you can add a filter and some effects to your high-hat:
**Note:** the <icode>sound</icode> function can also be used to play synthesizers (see the **Synthesizers** page). In that case, the <icode>.n(n: number)</icode> becomes totally useless!
## Learning about sound modifiers
As we said earlier, the <icode>sound('sample_name')</icode> function can be chained to _specify_ a sound more. For instance, you can add a filter and some effects to your high-hat:
\`\`\`javascript
mod(0.5) && sound('hh')
.sometimes(s=>s.speed(pick(1,5,10)))
@ -367,41 +404,135 @@ mod(0.5) && sound('hh')
.out()
\`\`\`
No sound will play until you add <icode>.out()</icode> at the end of the chain. Chaining sounds makes it easy to compose and think about sound samples and synthesis. There are many possible arguments that you can add to your sounds.
There are many possible arguments that you can add to your sounds. Learning them can take a long time but it will open up a lot of possibilities. Let's try to make it through all of them. They can all be used both with synthesizers and audio samples, which is kind of unconventional with normal / standard electronic music softwares.
## Orbits and audio busses
Topos is inheriting some audio bus management principles taken from the [SuperDirt](https://github.com/musikinformatik/SuperDirt) and [Superdough](https://www.npmjs.com/package/superdough) engine, a WebAudio based recreation of the same engine. Each sound that you play is associated with an audio bus, called an _orbit_. Some effects are affecting **all sounds currently playing on that bus**. These are called **global effects**, to distinguish from **local effects**:
- **global effects**: _reverberation_ and _delay_.
- **local effects**: everything else :smile:
There is a special method to choose the _orbit_ that your sound is going to use:
| Method | Alias | Description |
|----------|-------|------------------------------------------------------------|
| orbit | | Orbit number |
## Amplitude
Simple controls over the amplitude (volume) of a given sound.
| Method | Alias | Description |
|----------|-------|------------------------------------------------------------|
| gain | | Volume of the synth/sample (exponential) |
| velocity | vel | Velocity (amplitude) from 0 to 1. Multipled with gain |
## Amplitude Enveloppe
**Superdough** is applying an **ADSR** envelope to every sound being played. This is a very standard and conventional amplitude envelope composed of four stages: _attack_, _decay_, _sustain_ and _release_. You will find the same parameters on most synthesizers.
| Method | Alias | Description |
|---------|-------|-----------------------------------------------|
| attack | atk | Attack value (time to maximum volume) |
| decay | dec | Decay value (time to decay to sustain level) |
| sustain | sus | Sustain value (gain when sound is held) |
| release | rel | Release value (time for the sound to die off) |
Note that the **sustain** value is not a duration but an amplitude value (how loud). The other values are the time for each stage to take place. Here is a fairly complete example using the <icode>sawtooth</icode> basic waveform.
\`\`\`javascript
mod(4)::sound('sawtooth').note(50).decay(0.5).sustain(0.5).release(2).out();
mod(2)::sound('sawtooth').note(50+7).decay(0.5).sustain(0.6).release(2).out();
mod(1)::sound('sawtooth').note(50+12).decay(0.5).sustain(0.7).release(2).out();
mod(.25)::sound('sawtooth').note(pick(50,57,62)+divseq(2, 12, 24, 0))
.cutoff(5000).sustain(0.5).release(0.1).out()
\`\`\`
## Sample Controls
There are some basic controls over the playback of each sample. This allows you to get into more serious sampling if you take the time to really work with your audio materials.
| Method | Alias | Description |
|---------|-------|--------------------------------------------------------|
| n | | Select a sample in the current folder (from <icode>0</icode> to infinity) |
| begin | | Beginning of the sample playback (between <icode>0</icode> and <icode>1</icode>) |
| end | | End of the sample (between <icode>0</icode> and <icode>1</icode>) |
| speed | | Playback speed (<icode>2</icode> = twice as fast) |
| cut | | Set with <icode>0</icode> or <icode>1</icode>. Will cut the sample as soon as another sample is played on the same bus |
| clip | | Multiply the duration of the sample with the given number |
| pan | | Stereo position of the audio playback (<icode>0</icode> = left, <icode>1</icode> = right)|
## Filters
There are three basic filters: a _lowpass_, _highpass_ and _bandpass_ filters with rather soft slope. Each of them can take up to two arguments. You can also use only the _cutoff_ frequency and the resonance will stay to its default nominal value.
| Method | Alias | Description |
|------------|-------|-----------------------------------------|
| cutoff | lpf | Cutoff frequency of the lowpass filter |
| resonance | lpq | Resonance of the lowpass filter |
| hcutoff | hpf | Cutoff frequency of the highpass filter |
| hresonance | hpq | Resonance of the highpass filter |
| bandf | bpf | Cutoff frequency of the bandpass filter |
| bandq | bpq | Resonance of the bandpass filter |
| vowel | | Formant filter with (vocal quality) |
\`\`\`javascript
mod(.5) && snd('sawtooth')
.cutoff(pick(2000,500)) + usine(.5) * 4000)
.resonance(0.9).freq(pick(100,150))
.out()
\`\`\`
## Reverb
A basic reverberator that you can use to give some depth to your sounds. This simple reverb design has a _LoFI_ quality that can be quite useful on certain sounds.
| Method | Alias | Description |
|------------|-------|---------------------------------|
| room | | The more, the bigger the reverb (between <icode>0</icode> and <icode>1</icode>.|
| size | | Reverberation amount |
\`\`\`javascript
mod(2)::snd('cp').room(1).size(0.9).out()
\`\`\`
## Delay
A good sounding delay unit that can go into feedback territory. Use it without moderation.
| Method | Alias | Description |
|------------|-----------|---------------------------------|
| delay | | Delay _wet/dry_ (between <icode>0</icode> and <icode>1</icode>) |
| delaytime | delayt | Delay time (in milliseconds) |
| delayfeedback| delayfb | Delay feedback (between <icode>0</icode> and <icode>1</icode>) |
\`\`\`javascript
mod(2)::snd('cp').delay(0.5).delaytime(0.75).delayfb(0.8).out()
mod(4)::snd('snare').out()
mod(1)::snd('kick').out()
\`\`\`
## Distorsion, saturation, destruction
| Method | Alias | Description |
|------------|-----------|---------------------------------|
| coarse | | Artificial sample-rate lowering |
| crush | | bitcrushing. <icode>1</icode> is extreme, the more you go up, the less it takes effect. |
| shape | | Waveshaping distortion (between <icode>0</icode> and <icode>1</icode>) |
## Undocumented
| Method | Description |
| -------------------------------------- | ----------- |
| <icode>unit(value: number)</icode> | Sets the unit value |
| <icode>frequency(value: number)</icode>| Sets the playback sample frequency |
| <icode>nudge(value: number)</icode> | Adjusts the start time of the sound by the given value |
| <icode>cut(value: number)</icode>| Cut the sample if it overlaps on the same orbit. |
| <icode>loop(value: number)</icode>| Loops the sample. |
| <icode>clip(value: number)</icode>| Sets the clip value of the sound. |
| <icode>n(value: number)</icode>| Sample number in the sample folder. |
| <icode>note(value: number)</icode>| Sets the note value of the sound. |
| <icode>speed(value: number)</icode>| Sets the playback speed. |
| <icode>begin(value: number)</icode>| Sets the beginning of sample (between <icode>0.0</icode> and <icode>1.0</icode>). |
| <icode>end(value: number)</icode>| Sets the end of sample (between <icode>0.0</icode> and <icode>1.0</icode>). |
| <icode>gain(value: number)</icode>| Sets the gain. |
| <icode>cutoff(value: number)</icode>| Sets the cutoff frequency of the low-pass filter. |
| <icode>resonance(value: number)</icode>| Sets the resonance value of the low-pass filter. |
| <icode>hcutoff(value: number)</icode>| Sets the cutoff frequency value of high-pass filter. |
| <icode>hresonance(value: number)</icode>| Sets the resonance value of high-pass filter. |
| <icode>bandf(value: number)</icode>| Sets the frequency value of the bandpass filter. |
| <icode>bandq(value: number)</icode>| Sets the Q value of the bandpass filter. |
| <icode>coarse(value: number)</icode>| Adds some flavor of saturation. |
| <icode>crush(value: number)</icode>| Adds some amount of bitcrush on the given sound. |
| <icode>shape(value: number)</icode>| Adds some distortion. |
| <icode>pan(value: number)</icode>| Sets the panoramic value of the sound (in stereo, between <icode>0.0</icode> and <icode>1.0</icode>). |
| <icode>vowel(value: number)</icode>| Sets a formant vowel filter on the given sound(<icode>'a'</icode>, <icode>'e'</icode>, <icode>'i'</icode>, <icode>'o'</icode>, <icode>'u'</icode>.). |
| <icode>delay(value: number)</icode>| Sets the delay wet/dry value. |
| <icode>delayfeedback(value: number)</icode>| Sets delay feedback. |
| <icode>delaytime(value: number)</icode>| Sets delay time (in seconds). |
| <icode>orbit(value: number)</icode>| Sets the orbit value of the sound. |
| <icode>room(value: number)</icode>| Sets reverb room. |
| <icode>size(value: number)</icode>| Sets reverb size. |
| <icode>velocity(value: number)</icode>| Sets velocity. |
| <icode>out()</icode> | Returns an object processed by the <icode>superdough</icode> function, using the current values in the <icode>values</icode> object and the <icode>pulse_duration</icode> from the <icode>app.clock</icode>. |
`;
const samples: string = `