commit current state of documentation

This commit is contained in:
2023-09-17 19:27:41 +02:00
parent c4deff6a3e
commit b7acee90a1
6 changed files with 280 additions and 43 deletions

View File

@ -33,6 +33,7 @@ export async function loadSamples() {
registerZZFXSounds(), registerZZFXSounds(),
samples("github:Bubobubobubobubo/Dough-Samples/main"), samples("github:Bubobubobubobubo/Dough-Samples/main"),
samples("github:Bubobubobubobubo/Dough-Amiga/main"), samples("github:Bubobubobubobubo/Dough-Amiga/main"),
samples("github:Bubobubobubobubo/Dough-Waveforms/main"),
]); ]);
} }

View File

@ -93,16 +93,23 @@ export class SoundEvent extends AudibleEvent {
public sus = this.sustain; public sus = this.sustain;
public release = (value: number) => this.updateValue("release", value); public release = (value: number) => this.updateValue("release", value);
public rel = this.release; public rel = this.release;
public adsr = (a: number, d: number, s: number, r: number) => {
this.attack(a);
this.decay(d);
this.sustain(s);
this.release(r);
return this;
};
// Lowpass filter // Lowpass filter
public lpenv = (value: number) => this.updateValue("lpenv", value); public lpenv = (value: number) => this.updateValue("lpenv", value);
public lpe = (value: number) => this.updateValue("lpe", value); public lpe = (value: number) => this.updateValue("lpenv", value);
public lpattack = (value: number) => this.updateValue("lpattack", value); public lpattack = (value: number) => this.updateValue("lpattack", value);
public lpa = this.lpattack; public lpa = this.lpattack;
public lbdecay = (value: number) => this.updateValue("lbdecay", value); public lpdecay = (value: number) => this.updateValue("lbdecay", value);
public lbd = this.lbdecay; public lpd = this.lpdecay;
public lpsustain = (value: number) => this.updateValue("lpsustain", value); public lpsustain = (value: number) => this.updateValue("lpsustain", value);
public lpsus = this.lpsustain; public lps = this.lpsustain;
public lprelease = (value: number) => this.updateValue("lprelease", value); public lprelease = (value: number) => this.updateValue("lprelease", value);
public lpr = this.lprelease; public lpr = this.lprelease;
public cutoff = (value: number) => this.updateValue("cutoff", value); public cutoff = (value: number) => this.updateValue("cutoff", value);
@ -110,6 +117,20 @@ export class SoundEvent extends AudibleEvent {
public resonance = (value: number) => public resonance = (value: number) =>
this.updateValue("resonance", Math.min(Math.max(value, 0), 50)); this.updateValue("resonance", Math.min(Math.max(value, 0), 50));
public lpq = this.resonance; public lpq = this.resonance;
public lpadsr = (
depth: number,
a: number,
d: number,
s: number,
r: number
) => {
this.lpenv(depth);
this.lpattack(a);
this.lpdecay(d);
this.lpsustain(s);
this.lprelease(r);
return this;
};
// Highpass filter // Highpass filter
@ -127,6 +148,20 @@ export class SoundEvent extends AudibleEvent {
public hpf = this.hcutoff; public hpf = this.hcutoff;
public hresonance = (value: number) => this.updateValue("hresonance", value); public hresonance = (value: number) => this.updateValue("hresonance", value);
public hpq = this.hresonance; public hpq = this.hresonance;
public hpadsr = (
depth: number,
a: number,
d: number,
s: number,
r: number
) => {
this.hpenv(depth);
this.hpattack(a);
this.hpdecay(d);
this.hpsustain(s);
this.hprelease(r);
return this;
};
// Bandpass filter // Bandpass filter
@ -144,6 +179,20 @@ export class SoundEvent extends AudibleEvent {
public bpf = this.bandf; public bpf = this.bandf;
public bandq = (value: number) => this.updateValue("bandq", value); public bandq = (value: number) => this.updateValue("bandq", value);
public bpq = this.bandq; public bpq = this.bandq;
public bpadsr = (
depth: number,
a: number,
d: number,
s: number,
r: number
) => {
this.bpenv(depth);
this.bpattack(a);
this.bpdecay(d);
this.bpsustain(s);
this.bprelease(r);
return this;
};
public freq = (value: number) => this.updateValue("freq", value); public freq = (value: number) => this.updateValue("freq", value);
public f = this.freq; public f = this.freq;

View File

@ -1,5 +1,6 @@
import { makeExampleFactory, key_shortcut } from "../Documentation"; import { makeExampleFactory, key_shortcut } from "../Documentation";
import { type Editor } from "../main"; import { type Editor } from "../main";
import { examples } from "../examples/excerpts";
export const introduction = (application: Editor): string => { export const introduction = (application: Editor): string => {
const makeExample = makeExampleFactory(application); const makeExample = makeExampleFactory(application);
@ -12,24 +13,9 @@ Welcome to the Topos documentation. These pages are offering you an introduction
${makeExample( ${makeExample(
"Welcome! Eval to get started", "Welcome! Eval to get started",
` examples[Math.floor(Math.random() * examples.length)],
bpm(110)
beat(0.125) && sound('sawtooth')
.note([60, 62, 63, 67, 70].beat(.125) +
[-12,0,12].beat() + [0, 0, 5, 7].bar())
.sustain(0.1).fmi(0.25).fmh(2).room(0.9)
.gain(0.75).cutoff(500 + usine(8) * [500, 1000, 2000].bar())
.delay(0.5).delayt(0.25).delayfb(0.25)
.out();
beat(1) && snd('kick').out();
beat(2) && snd('snare').out();
beat(.5) && snd('hat').out();
`,
true true
)} )}
## What is Topos? ## What is Topos?

View File

@ -6,16 +6,145 @@ export const synths = (application: Editor): string => {
return ` return `
# Synthesizers # Synthesizers
Topos comes with a small number of basic synthesizers. These synths are based on a basic [WebAudio](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API) design. For heavy synthesis duties, please use MIDI and speak to more complex instruments. Topos comes by default with a forever-increasing number of synthesis capabilities. These synths are based on basic [WebAudio](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API) designs. For heavy synthesis duties, I recommend you to user other synthesizers or softwares through MIDI. There is already a lot you can do with the built-in synths though!
# Substractive Synthesis # Timbre, pitch and frequency
The <ic>sound</ic> function can take the name of a synthesizer as first argument.
- <ic>sine</ic>, <ic>sawtooth</ic>,<ic>triangle</ic>, <ic>square</ic> for the waveform selection.
- <ic>cutoff</ic> and <ic>resonance</ic> for adding a low-pass filter with cutoff frequency and filter resonance.
- <ic>hcutoff</ic> or <ic>bandf</ic> to switch to a high-pass or bandpass filter.
- <ic>hresonance</ic> and <ic>bandq</ic> for the resonance parameter of these filters.
The <ic>sound</ic> function can take the name of a synthesizer or waveform as first argument. This has for effect to turn the sampler we all know and love into a synthesizer. <ic>sine</ic>, <ic>sawtooth</ic>,<ic>triangle</ic>, <ic>square</ic> are the names used to select classic oscillator waveforms. Note that you can also make use of filters and envelopes to shape the sound to your liking.
${makeExample(
"Listening to the different waveforms from the sweetest to the harshest",
`
beat(.5) && snd(['sine', 'triangle', 'sawtooth', 'square'].beat()).freq(100).out()
`,
true
)}
Two functions are primarily used to control the frequency of the synthesizer:
- <ic>freq(hz: number)</ic>: sets the frequency of the oscillator.
- <ic>note(note: number)</ic>: sets the MIDI note of the oscillator (MIDI note converted to hertz).
${makeExample(
"Selecting a pitch or note",
`
beat(.5) && snd('triangle').freq([100,200,400].beat(2)).out()
`,
true
)}
## Vibrato
You can also add some amount of vibrato to the sound using the <ic>vib</ic> and <ic>vibmod</ic> methods. These can turn any oscillator into something more lively and/or into a sound effect when used with a high amount of modulation.
${makeExample(
"Different vibrato settings",
`beat(1) :: sound('triangle')
.freq(400).release(0.2)
.vib([1/2, 1, 2, 4].beat())
.vibmod([1,2,4,8].beat(2))
.out()`,
true
)}
## Controlling the amplitude
Controlling the amplitude and duration of the sound can be done using various techniques. The most important thing to learn is probably how set the amplitude (volume) of your synthesizer:
- <ic>gain(gain: number)</ic>: sets the gain of the oscillator.
- <ic>velocity(velocity: number)</ic>: sets the velocity of the oscillator (velocity is a multiple of gain).
${makeExample(
"Setting the gain",
`beat(0.25) :: sound('sawtooth').gain([0.0, 1/8, 1/4, 1/2, 1].beat(0.5)).out()`,
true
)}
${makeExample(
"Setting the velocity",
`beat(0.25) :: sound('sawtooth').velocity([0.0, 1/8, 1/4, 1/2, 1].beat(0.5)).out()`,
true
)}
<div class="mt-4 mb-4 lg:grid lg:grid-cols-4 lg:gap-4">
<img class="col-span-1 lg:ml-12 bg-gray-100 rounded-lg px-2 py-2", src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/ADSR_Envelope_Graph.svg/1280px-ADSR_Envelope_Graph.svg.png" width="400" />
<z class="pl-8 lg:text-2xl text-base text-white lg:mx-6 mx-2 my-4 leading-normal col-span-3 ">Synthesizers typically come with an amplitude envelope that can help you to shape the sound with a slow attack or long release. This is done in Topos using the amplitude envelope, composed of four parameters: <ic>attack</ic>, <ic>decay</ic>, <ic>sustain</ic> and <ic>release</ic>:</z>
</div>
- <ic>attack(attack: number)</ic> / <ic>atk(atk: number)</ic>: sets the attack time of the envelope.
- <ic>decay(decay: number)</ic> / <ic>dec(dec: number)</ic>: sets the decay time of the envelope.
- <ic>sustain(sustain: number)</ic> / <ic>sus(sus: number)</ic>: sets the sustain time of the envelope.
- <ic>release(release: number)</ic> / <ic>rel(rel: number)</ic>: sets the release time of the envelope.
${makeExample(
"Using decay and sustain to set the ADSR envelope",
`
beat(0.5) :: sound('sawtooth')
.cutoff(1000 + usine() * 4000)
.freq([50,100,150,300].pick())
.decay(.1).sustain([0.25,0.5].pick())
.out()`,
true
)}
This ADSR envelope design is important to know because it is used for other aspects of the synthesis engine such as the filters that we are now going to talk about.
## Substractive synthesis using filters
The most basic synthesis technique used since the 1970s is called substractive synthesis. This technique is based on the use of rich sound sources (oscillators) as a base to build rich and moving timbres. Because rich sources contain a lot of different harmonics, you might want to filter some of them to obtain the timbre you are looking for. To do so, Topos comes with a set of basic filters that can be used to shape the sound exactly to your liking. There are three filter types by defaut, with more to be added in the future:
- **lowpass filter**: filters the high frequencies, keeping the low frequencies.
- **highpass filter**: filtering the low frequencies, keeping the high frequencies.
- **bandpass filter**: filters the low and high frequencies around a frequency band, keeping what's in the middle.
${makeExample(
"Filtering the high frequencies of an oscillator",
`beat(.5) :: sound('sawtooth').cutoff(50 + usine(1/8) * 2000).out()`,
true
)}
These filters all come with their own set of parameters. Note that we are describing the parameters of the three different filter types here. Choose the right parameters depending on the filter type you are using:
### Lowpass filter
| Method | Alias | Description |
|------------|-----------|---------------------------------|
| cutoff | lpf | cutoff frequency of the lowpass filter |
| resonance | lpq | resonance of the lowpass filter |
${makeExample(
"Filtering a bass",
`beat(.5) :: sound('jvbass').lpf([250,1000,8000].beat()).out()`,
true
)}
### Highpass filter
| Method | Alias | Description |
|------------|-----------|---------------------------------|
| hcutoff | hpf | cutoff frequency of the highpass filter |
| hresonance | hpq | resonance of the highpass filter |
${makeExample(
"Filtering a noise source",
`beat(.5) :: sound('gtr').hpf([250,1000, 2000, 3000, 4000].beat()).end(0.5).out()`,
true
)}
### Bandpass filter
| Method | Alias | Description |
|------------|-----------|---------------------------------|
| bandf | bpf | cutoff frequency of the bandpass filter |
| bandq | bpq | resonance of the bandpass filter |
${makeExample(
"Sweeping the filter on the same guitar sample",
`beat(.5) :: sound('gtr').bandf(100 + usine(1/8) * 4000).end(0.5).out()`,
true
)}
I also encourage you to study these simple examples to get more familiar with the construction of basic substractive synthesizers:
${makeExample( ${makeExample(
"Simple synthesizer voice with filter", "Simple synthesizer voice with filter",
` `
@ -27,16 +156,6 @@ beat(.5) && snd('sawtooth')
true true
)} )}
${makeExample(
"Listening to the different waveforms from the sweetest to the harshest",
`
beat(.5) && snd(['sine', 'triangle', 'sawtooth', 'square'].beat()).freq(100).out()
.freq(50)
.out()`,
false
)}
${makeExample( ${makeExample(
"Blessed by the square wave", "Blessed by the square wave",
` `
@ -48,7 +167,6 @@ beat(.25) :: sound('square').freq(100*[1,2,4,8].beat()).sustain(0.1).out()`,
false false
)} )}
${makeExample( ${makeExample(
"Ghost carillon", "Ghost carillon",
` `
@ -62,6 +180,79 @@ beat(1/8)::sound('sine')
.out()`, .out()`,
false false
)} )}
## Filter envelopes
The examples we have studied so far are static. They filter the sound around a fixed cutoff frequency. To make the sound more interesting, you can use the ADSR filter envelopes to shape the filter cutoff frequency over time. You will always find amplitude and filter envelopes on most commercial synthesizers. This is done using the following methods:
### Lowpass envelope
| Method | Alias | Description |
|------------|-----------|---------------------------------|
| lpenv | lpe | lowpass frequency modulation amount (negative or positive) |
| lpattack | lpa | attack of the lowpass filter |
| lpdecay | lpd | decay of the lowpass filter |
| lpsustain | lps | sustain of the lowpass filter |
| lprelease | lpr | release of the lowpass filter |
${makeExample(
"Filtering a sawtooth wave dynamically",
`beat(.5) :: sound('sawtooth').note([48,60].beat())
.cutoff(5000).lpa([0.05, 0.25, 0.5].beat(2))
.lpenv(-8).lpq(10).out()`,
true
)}
### Highpass envelope
| Method | Alias | Description |
|------------|-----------|---------------------------------|
| hpenv | hpe | highpass frequency modulation amount (negative or positive) |
| hpattack | hpa | attack of the highpass filter |
| hpdecay | hpd | decay of the highpass filter |
| hpsustain | hps | sustain of the highpass filter |
| hprelease | hpr | release of the highpass filter |
${makeExample(
"Let's use another filter using the same example",
`beat(.5) :: sound('sawtooth').note([48,60].beat())
.hcutoff(1000).hpa([0.05, 0.25, 0.5].beat(2))
.hpenv(8).hpq(10).out()`,
true
)}
### Bandpass envelope
| Method | Alias | Description |
|------------|-----------|---------------------------------|
| bpenv | bpe | bandpass frequency modulation amount (negative or positive) |
| bpattack | bpa | attack of the bandpass filter |
| bpdecay | bpd | decay of the bandpass filter |
| bpsustain | bps | sustain of the bandpass filter |
| bprelease | bpr | release of the bandpass filter |
${makeExample(
"And the bandpass filter, just for fun",
`beat(.5) :: sound('sawtooth').note([48,60].beat())
.bandf([500,1000,2000].beat(2))
.bpa([0.25, 0.125, 0.5].beat(2) * 4)
.bpenv(-4).release(2).out()
`,
true
)}
## Wavetable synthesis
${makeExample(
"Acidity test",
`
beat(.25) :: sound('wt_symetric:8').note([50,55,57,60].beat(.25) - [12,0].pick()).ftype('12db').adsr(0.05/4, 1/16, 0.25/4, 0)
.cutoff(1500 + usine(1/8) * 5000).lpadsr(16, 0.2, 0.2, 0.125/2, 0).room(0.9).size(0.9).resonance(20).gain(0.3).out()
beat(1) :: sound('kick').n(4).out()
beat(2) :: sound('snare').out()
beat(.5) :: sound('hh').out()`
)}
# Frequency Modulation Synthesis (FM) # Frequency Modulation Synthesis (FM)

View File

@ -1,4 +1,15 @@
export const examples = [ export const examples = [
`// Acidity test - BuboBuboBubo
beat(.25) :: sound('wt_symetric:8')
.note([50,55,57,60].beat(.25) - [12,0].pick())
.ftype('12db').adsr(0.05/4, 1/16, 0.25/4, 0)
.cutoff(1500 + usine(1/8) * 5000)
.lpadsr(16, 0.2, 0.2, 0.125/2, 0)
.room(0.9).size(0.9).resonance(20)
.gain(0.3).out()
beat(1) :: sound('kick').n(4).out()
beat(2) :: sound('snare').out()
beat(.5) :: sound('hh').out()`,
`// Entering the secret room - Bubobubobubo `// Entering the secret room - Bubobubobubo
let frequencies = [200,400,600,800,1000,2000].beat(2); let frequencies = [200,400,600,800,1000,2000].beat(2);
beat(2) :: sound('sine').freq(frequencies) beat(2) :: sound('sine').freq(frequencies)

View File

@ -35,6 +35,7 @@ import { makeStringExtensions } from "./StringExtensions";
const classMap = { const classMap = {
h1: "text-white lg:text-4xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-4 mb-4 bg-neutral-900 rounded-lg py-2 px-2", h1: "text-white lg:text-4xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-4 mb-4 bg-neutral-900 rounded-lg py-2 px-2",
h2: "text-white lg:text-3xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-4 mb-4 bg-neutral-900 rounded-lg py-2 px-2", h2: "text-white lg:text-3xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-4 mb-4 bg-neutral-900 rounded-lg py-2 px-2",
h3: "text-white lg:text-2xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-4 mb-4 bg-neutral-700 rounded-lg py-2 px-2 lg:mt-16",
ul: "text-underline pl-6", ul: "text-underline pl-6",
li: "list-disc lg:text-2xl text-base text-white lg:mx-4 mx-2 my-4 my-2 leading-normal", li: "list-disc lg:text-2xl text-base text-white lg:mx-4 mx-2 my-4 my-2 leading-normal",
p: "lg:text-2xl text-base text-white lg:mx-6 mx-2 my-4 leading-normal", p: "lg:text-2xl text-base text-white lg:mx-6 mx-2 my-4 leading-normal",
@ -51,7 +52,7 @@ const classMap = {
"lg:mx-12 py-2 px-6 lg:text-2xl text-white rounded-lg bg-neutral-600", "lg:mx-12 py-2 px-6 lg:text-2xl text-white rounded-lg bg-neutral-600",
summary: "font-semibold text-xl", summary: "font-semibold text-xl",
table: table:
"justify-center lg:my-8 my-2 lg:mx-8 mx-2 lg:text-2xl text-base w-full text-left text-white border-collapse", "justify-center lg:my-12 my-2 lg:mx-12 mx-2 lg:text-2xl text-base w-full text-left text-white border-collapse",
thead: thead:
"text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400", "text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400",
th: "", th: "",
@ -367,9 +368,7 @@ export class Editor {
} }
// Shift + Ctrl + Enter: Evaluate the currently visible code block // Shift + Ctrl + Enter: Evaluate the currently visible code block
if ( if (event.key === "Enter" && event.shiftKey && event.ctrlKey) {
(event.key === "Enter" && event.shiftKey && event.ctrlKey))
{
event.preventDefault(); event.preventDefault();
this.currentFile().candidate = this.view.state.doc.toString(); this.currentFile().candidate = this.view.state.doc.toString();
tryEvaluate(this, this.currentFile()); tryEvaluate(this, this.currentFile());