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",