diff --git a/index.html b/index.html
index a01e8d3..805f571 100644
--- a/index.html
+++ b/index.html
@@ -99,6 +99,8 @@
Time
Sound
Samples
+ Synths
+
MIDI
Functions
Shortcuts
diff --git a/src/Documentation.ts b/src/Documentation.ts
index 4180dde..ec900a3 100644
--- a/src/Documentation.ts
+++ b/src/Documentation.ts
@@ -355,6 +355,51 @@ ${injectAvailableSamples()}
`;
+const synths: string = `
+# 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.
+
+# Substractive Synthesis
+
+The sound function can take the name of a synthesizer as first argument.
+- sine, sawtooth,triangle, square for the waveform selection.
+- cutoff and resonance for adding a low-pass filter with cutoff frequency and filter resonance.
+ - hcutoff or bandf to switch to a high-pass or bandpass filter.
+ - hresonance and bandq for the resonance parameter of these filters.
+
+Here is a simple example of a substractive synth:
+
+\`\`\`javascript
+mod(.5) && snd('sawtooth')
+ .cutoff(pick(2000,500)) + usine(.5) * 4000)
+ .resonance(0.9).freq(pick(100,150))
+ .out()
+\`\`\`
+
+
+# Frequency Modulation Synthesis (FM)
+
+The same basic waveforms can take additional methods to switch to a basic two operators FM synth design (with _carrier_ and _modulator_). FM Synthesis is a complex topic but take this advice: simple ratios will yield stable and harmonic sounds, complex ratios will generate noises, percussions and gritty sounds.
+
+- fmi (_frequency modulation index_): a floating point value between 1 and n.
+- fmh (_frequency modulation harmonic ratio_): a floating point value between 1 and n.
+
+And here is a simple example:
+
+\`\`\`javascript
+mod(.25) && snd('sine')
+ .fmi(pick(1,2,4,8))
+ .fmh(divseq(2, 1,2,4,8))
+ .freq(pick(100,150))
+ .sustain(0.1)
+ .out()
+\`\`\`
+
+**Note::** you can also set the _modulation index_ and the _harmonic ratio_ with the fm argument. You will have to feed both as a string: fm('2:4'). If you only feed one number, only the _modulation index_ will be updated.
+
+`;
+
const about: string = `
# About Topos
@@ -570,6 +615,7 @@ export const documentation = {
time: time,
sound: sound,
samples: samples,
+ synths: synths,
midi: midi,
functions: functions,
reference: reference,
diff --git a/src/classes/Sound.ts b/src/classes/Sound.ts
index 889bcfc..8a0beba 100644
--- a/src/classes/Sound.ts
+++ b/src/classes/Sound.ts
@@ -1,6 +1,6 @@
-import { type Editor } from '../main';
-import { SoundEvent } from './Event';
-import { midiToFreq, noteFromPc } from 'zifferjs';
+import { type Editor } from "../main";
+import { SoundEvent } from "./Event";
+import { midiToFreq, noteFromPc } from "zifferjs";
import {
superdough,
@@ -8,235 +8,244 @@ import {
} from "superdough";
export class Sound extends SoundEvent {
+ constructor(sound: string | object, public app: Editor) {
+ super(app);
+ if (typeof sound === "string") this.values = { s: sound, dur: 0.5 };
+ else this.values = sound;
+ }
- constructor(sound: string|object, public app: Editor) {
- super(app);
- if (typeof sound === 'string') this.values = { 's': sound, 'dur': 0.5 };
- else this.values = sound;
+ attack = (value: number): this => {
+ this.values["attack"] = value;
+ return this;
+ };
+ atk = this.attack;
+
+ decay = (value: number): this => {
+ this.values["decay"] = value;
+ return this;
+ };
+ dec = this.decay;
+
+ release = (value: number): this => {
+ this.values["release"] = value;
+ return this;
+ };
+ rel = this.release;
+
+ unit = (value: number): this => {
+ this.values["unit"] = value;
+ return this;
+ };
+
+ freq = (value: number): this => {
+ this.values["freq"] = value;
+ return this;
+ };
+
+ fm = (value: number | string): this => {
+ if (typeof value === "number") {
+ this.values["fmi"] = value;
+ } else {
+ let values = value.split(":");
+ this.values["fmi"] = parseFloat(values[0]);
+ if (values.length > 1) this.values["fmh"] = parseFloat(values[1]);
}
+ return this;
+ };
- attack = (value: number): this => {
- this.values["attack"] = value;
- return this;
- };
- atk = this.attack;
+ sound = (value: string): this => {
+ this.values["s"] = value;
+ return this;
+ };
- decay = (value: number): this => {
- this.values["decay"] = value;
- return this;
- };
- dec = this.decay;
+ fmi = (value: number): this => {
+ this.values["fmi"] = value;
+ return this;
+ };
- release = (value: number): this => {
- this.values["release"] = value;
- return this;
- };
- rel = this.release;
+ fmh = (value: number): this => {
+ this.values["fmh"] = value;
+ return this;
+ };
- unit = (value: number): this => {
- this.values["unit"] = value;
- return this;
- };
+ nudge = (value: number): this => {
+ this.values["nudge"] = value;
+ return this;
+ };
- freq = (value: number): this => {
- this.values["freq"] = value;
- return this;
- };
+ cut = (value: number): this => {
+ this.values["cut"] = value;
+ return this;
+ };
- fm = (value: number | string): this => {
- if (typeof value === "number") {
- this.values["fmi"] = value;
- } else {
- let values = value.split(":");
- this.values["fmi"] = parseFloat(values[0]);
- if (values.length > 1) this.values["fmh"] = parseFloat(values[1]);
- }
- return this;
- };
+ loop = (value: number): this => {
+ this.values["loop"] = value;
+ return this;
+ };
- sound = (value: string): this => {
- this.values['s'] = value
- return this;
- };
+ clip = (value: number): this => {
+ this.values["clip"] = value;
+ return this;
+ };
- fmi = (value: number): this => {
- this.values["fmi"] = value;
- return this;
- };
+ n = (value: number): this => {
+ this.values["n"] = value;
+ return this;
+ };
- fmh = (value: number): this => {
- this.values["fmh"] = value;
- return this;
- };
+ note = (value: number): this => {
+ this.values["note"] = value;
+ return this;
+ };
- nudge = (value: number): this => {
- this.values["nudge"] = value;
- return this;
- };
+ speed = (value: number): this => {
+ this.values["speed"] = value;
+ return this;
+ };
- cut = (value: number): this => {
- this.values["cut"] = value;
- return this;
- };
+ begin = (value: number): this => {
+ this.values["begin"] = value;
+ return this;
+ };
- loop = (value: number): this => {
- this.values["loop"] = value;
- return this;
- };
+ end = (value: number): this => {
+ this.values["end"] = value;
+ return this;
+ };
- clip = (value: number): this => {
- this.values["clip"] = value;
- return this;
- };
+ gain = (value: number): this => {
+ this.values["gain"] = value;
+ return this;
+ };
- n = (value: number): this => {
- this.values["n"] = value;
- return this;
- };
+ cutoff = (value: number): this => {
+ this.values["cutoff"] = value;
+ return this;
+ };
- note = (value: number): this => {
- this.values["note"] = value;
- return this;
- };
+ resonance = (value: number): this => {
+ this.values["resonance"] = value;
+ return this;
+ };
- speed = (value: number): this => {
- this.values["speed"] = value;
- return this;
- };
+ hcutoff = (value: number): this => {
+ this.values["hcutoff"] = value;
+ return this;
+ };
- begin = (value: number): this => {
- this.values["begin"] = value;
- return this;
- };
+ hresonance = (value: number): this => {
+ this.values["hresonance"] = value;
+ return this;
+ };
- end = (value: number): this => {
- this.values["end"] = value;
- return this;
- };
+ bandf = (value: number): this => {
+ this.values["bandf"] = value;
+ return this;
+ };
- gain = (value: number): this => {
- this.values["gain"] = value;
- return this;
- };
+ bandq = (value: number): this => {
+ this.values["bandq"] = value;
+ return this;
+ };
- cutoff = (value: number): this => {
- this.values["cutoff"] = value;
- return this;
- };
+ coarse = (value: number): this => {
+ this.values["coarse"] = value;
+ return this;
+ };
- resonance = (value: number): this => {
- this.values["resonance"] = value;
- return this;
- };
+ crush = (value: number): this => {
+ this.values["crush"] = value;
+ return this;
+ };
- hcutoff = (value: number): this => {
- this.values["hcutoff"] = value;
- return this;
- };
+ shape = (value: number): this => {
+ this.values["shape"] = value;
+ return this;
+ };
- hresonance = (value: number): this => {
- this.values["hresonance"] = value;
- return this;
- };
+ pan = (value: number): this => {
+ this.values["pan"] = value;
+ return this;
+ };
- bandf = (value: number): this => {
- this.values["bandf"] = value;
- return this;
- };
+ vowel = (value: number): this => {
+ this.values["vowel"] = value;
+ return this;
+ };
- bandq = (value: number): this => {
- this.values["bandq"] = value;
- return this;
- };
+ delay = (value: number): this => {
+ this.values["delay"] = value;
+ return this;
+ };
- coarse = (value: number): this => {
- this.values["coarse"] = value;
- return this;
- };
+ delayfeedback = (value: number): this => {
+ this.values["delayfeedback"] = value;
+ return this;
+ };
- crush = (value: number): this => {
- this.values["crush"] = value;
- return this;
- };
+ delaytime = (value: number): this => {
+ this.values["delaytime"] = value;
+ return this;
+ };
- shape = (value: number): this => {
- this.values["shape"] = value;
- return this;
- };
+ orbit = (value: number): this => {
+ this.values["orbit"] = value;
+ return this;
+ };
- pan = (value: number): this => {
- this.values["pan"] = value;
- return this;
- };
+ room = (value: number): this => {
+ this.values["room"] = value;
+ return this;
+ };
- vowel = (value: number): this => {
- this.values["vowel"] = value;
- return this;
- };
+ size = (value: number): this => {
+ this.values["size"] = value;
+ return this;
+ };
- delay = (value: number): this => {
- this.values["delay"] = value;
- return this;
- };
+ velocity = (value: number): this => {
+ this.values["velocity"] = value;
+ return this;
+ };
- delayfeedback = (value: number): this => {
- this.values["delayfeedback"] = value;
- return this;
- };
-
- delaytime = (value: number): this => {
- this.values["delaytime"] = value;
- return this;
- };
-
- orbit = (value: number): this => {
- this.values["orbit"] = value;
- return this;
- };
-
- room = (value: number): this => {
- this.values["room"] = value;
- return this;
- };
-
- size = (value: number): this => {
- this.values["size"] = value;
- return this;
- };
-
- velocity = (value: number): this => {
- this.values["velocity"] = value;
- return this;
- };
-
- modify = (func: Function): this => {
- const funcResult = func(this);
- if(funcResult instanceof Object) return funcResult;
- else {
- func(this.values);
- return this;
- }
- };
-
- // NOTE: Sustain of the sound. duration() from the superclass Event is used to change the note length.
- sustain = (value: number): this => {
- this.values["dur"] = value;
- return this;
- };
-
- update = (): void => {
- if(this.values.key && this.values.pitch && this.values.parsedScale && this.values.octave) {
- const [note,_] = noteFromPc(this.values.key, this.values.pitch, this.values.parsedScale, this.values.octave);
- this.values.freq = midiToFreq(note);
- }
+ modify = (func: Function): this => {
+ const funcResult = func(this);
+ if (funcResult instanceof Object) return funcResult;
+ else {
+ func(this.values);
+ return this;
}
+ };
- out = (): object => {
- return superdough(
- this.values,
- this.app.clock.pulse_duration,
- this.values.dur
- );
- };
+ // NOTE: Sustain of the sound. duration() from the superclass Event is used to change the note length.
+ sustain = (value: number): this => {
+ this.values["dur"] = value;
+ return this;
+ };
+ sus = this.sustain;
-}
\ No newline at end of file
+ update = (): void => {
+ if (
+ this.values.key &&
+ this.values.pitch &&
+ this.values.parsedScale &&
+ this.values.octave
+ ) {
+ const [note, _] = noteFromPc(
+ this.values.key,
+ this.values.pitch,
+ this.values.parsedScale,
+ this.values.octave
+ );
+ this.values.freq = midiToFreq(note);
+ }
+ };
+
+ out = (): object => {
+ return superdough(
+ this.values,
+ this.app.clock.pulse_duration,
+ this.values.dur
+ );
+ };
+}
diff --git a/src/main.ts b/src/main.ts
index 9321c89..ede47bc 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -559,6 +559,7 @@ export class Editor {
"time",
"sound",
"samples",
+ "synths",
"midi",
"functions",
"reference",