Merge pull request #91 from Bubobubobubobubo/documentation-work
Pre-gig
31
index.html
@ -76,7 +76,8 @@
|
||||
<svg id="topos-logo" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-10 h-10 text-black p-2 bg-white rounded-full" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" />
|
||||
</svg>
|
||||
<span id="universe-viewer" class="hidden xl:block ml-4 text-2xl text-white">Topos</span>
|
||||
<input id="universe-viewer" class="hidden bg-transparent xl:block ml-4 text-2xl text-white placeholder-white" id="renamer" type="text" placeholder="Topos">
|
||||
|
||||
</a>
|
||||
<nav class="py-2 flex flex-wrap items-center text-base absolute right-0">
|
||||
<a title="Play button (Ctrl+P)" id="play-button-1" class="flex flex-row mr-2 hover:bg-gray-800 px-2 py-2 rounded-lg">
|
||||
@ -146,6 +147,8 @@
|
||||
<details class="space-y-2" open=true>
|
||||
<summary class="font-semibold lg:text-xl pb-1 pt-1 text-orange-300">Learning</summary>
|
||||
<div class="flex flex-col">
|
||||
|
||||
<!-- Time -->
|
||||
<details class="space-y-2" open=false>
|
||||
<summary class="ml-2 lg:text-xl pb-1 pt-1 text-white">Time</summary>
|
||||
<div class="flex flex-col">
|
||||
@ -153,13 +156,31 @@
|
||||
<p rel="noopener noreferrer" id="docs_linear" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Time & Transport</p>
|
||||
<p rel="noopener noreferrer" id="docs_cyclic" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Time & Cycles</p>
|
||||
<p rel="noopener noreferrer" id="docs_longform" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Time & Structure</p>
|
||||
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<!-- Samples -->
|
||||
<details class="space-y-2" open=false>
|
||||
<summary class="ml-2 lg:text-xl pb-1 pt-1 text-white">Audio Engine</summary>
|
||||
<div class="flex flex-col">
|
||||
<p rel="noopener noreferrer" id="docs_audio_basics" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Playing a sound</p>
|
||||
<p rel="noopener noreferrer" id="docs_amplitude" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Amplitude</p>
|
||||
<p rel="noopener noreferrer" id="docs_sampler" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Sampler</p>
|
||||
<p rel="noopener noreferrer" id="docs_synths" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Synths</p>
|
||||
<p rel="noopener noreferrer" id="docs_reverb_delay" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Effects</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<!-- Audio Engine -->
|
||||
<details class="space-y-2" open=false>
|
||||
<summary class="ml-2 lg:text-xl pb-1 pt-1 text-white">Samples</summary>
|
||||
<div class="flex flex-col">
|
||||
<p rel="noopener noreferrer" id="docs_sample_list" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">List of samples</p>
|
||||
<p rel="noopener noreferrer" id="docs_loading_samples" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Loading Samples</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<p rel="noopener noreferrer" id="docs_patterns" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Patterns</p>
|
||||
<p rel="noopener noreferrer" id="docs_sound" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Audio Engine</p>
|
||||
<p rel="noopener noreferrer" id="docs_samples" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Samples</p>
|
||||
<p rel="noopener noreferrer" id="docs_synths" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Synths</p>
|
||||
<p rel="noopener noreferrer" id="docs_midi" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">MIDI</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
"postcss": "^8.4.27",
|
||||
"showdown": "^2.1.0",
|
||||
"showdown-highlight": "^3.1.0",
|
||||
"superdough": "^0.9.10",
|
||||
"superdough": "^0.9.11",
|
||||
"tailwind-highlightjs": "^2.0.1",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tone": "^14.8.49",
|
||||
|
||||
8
samples
Normal file
@ -0,0 +1,8 @@
|
||||
import { type Editor } from "../../main";
|
||||
import { makeExampleFactory } from "../../Documentation";
|
||||
|
||||
export const loading_samples = (application: Editor): string => {
|
||||
// @ts-ignore
|
||||
const makeExample = makeExampleFactory(application);
|
||||
return `# Loading samples`
|
||||
}
|
||||
26
src/API.ts
@ -30,6 +30,7 @@ import { getScaleNotes } from "zifferjs";
|
||||
import { OscilloscopeConfig, blinkScript } from "./AudioVisualisation";
|
||||
import { SkipEvent } from "./classes/SkipEvent";
|
||||
import { AbstractEvent, EventOperation } from "./classes/AbstractEvents";
|
||||
import drums from "./tidal-drum-machines.json";
|
||||
|
||||
interface ControlChange {
|
||||
channel: number;
|
||||
@ -40,15 +41,16 @@ interface ControlChange {
|
||||
export async function loadSamples() {
|
||||
return Promise.all([
|
||||
initAudioOnFirstClick(),
|
||||
samples("github:tidalcycles/Dirt-Samples/master").then(() =>
|
||||
samples("github:tidalcycles/Dirt-Samples/master", undefined, { tag: "Tidal" }).then(() =>
|
||||
registerSynthSounds()
|
||||
),
|
||||
registerZZFXSounds(),
|
||||
samples("github:Bubobubobubobubo/Dough-Fox/main"),
|
||||
samples("github:Bubobubobubobubo/Dough-Samples/main"),
|
||||
samples("github:Bubobubobubobubo/Dough-Amiga/main"),
|
||||
samples("github:Bubobubobubobubo/Dough-Amen/main"),
|
||||
samples("github:Bubobubobubobubo/Dough-Waveforms/main"),
|
||||
samples(drums, "github:ritchse/tidal-drum-machines/main/machines/", { tag: "Machines" }),
|
||||
samples("github:Bubobubobubobubo/Dough-Fox/main", undefined, { tag: "FoxDot" }),
|
||||
samples("github:Bubobubobubobubo/Dough-Samples/main", undefined, { tag: "Pack" }),
|
||||
samples("github:Bubobubobubobubo/Dough-Amiga/main", undefined, { tag: "Amiga" }),
|
||||
samples("github:Bubobubobubobubo/Dough-Amen/main", undefined, { tag: "Amen" }),
|
||||
samples("github:Bubobubobubobubo/Dough-Waveforms/main", undefined, { tag: "Waveforms" }),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -1279,7 +1281,7 @@ export class UserAPI {
|
||||
const results: boolean[] = nArray.map(
|
||||
(value) =>
|
||||
(this.app.clock.pulses_since_origin - Math.floor(nudge * this.ppqn())) %
|
||||
Math.floor(value * this.ppqn()) ===
|
||||
Math.floor(value * this.ppqn()) ===
|
||||
0
|
||||
);
|
||||
return results.some((value) => value === true);
|
||||
@ -1299,7 +1301,7 @@ export class UserAPI {
|
||||
const results: boolean[] = nArray.map(
|
||||
(value) =>
|
||||
(this.app.clock.pulses_since_origin - nudgeInPulses) %
|
||||
Math.floor(value * barLength) ===
|
||||
Math.floor(value * barLength) ===
|
||||
0
|
||||
);
|
||||
return results.some((value) => value === true);
|
||||
@ -1899,8 +1901,8 @@ export class UserAPI {
|
||||
// =============================================================
|
||||
|
||||
register = (name: string, operation: EventOperation<AbstractEvent>): void => {
|
||||
AbstractEvent.prototype[name] = function (this: AbstractEvent, ...args: any[]) {
|
||||
return operation(this, ...args);
|
||||
AbstractEvent.prototype[name] = function(this: AbstractEvent, ...args: any[]) {
|
||||
return operation(this, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
@ -2092,7 +2094,7 @@ export class UserAPI {
|
||||
return this.app.clock.nudge;
|
||||
};
|
||||
|
||||
public bpm = (n?: number): number => {
|
||||
public tempo = (n?: number): number => {
|
||||
/**
|
||||
* Sets or returns the current bpm.
|
||||
*
|
||||
@ -2105,7 +2107,7 @@ export class UserAPI {
|
||||
this.app.clock.bpm = n;
|
||||
return n;
|
||||
};
|
||||
tempo = this.bpm;
|
||||
// tempo = this.bpm;
|
||||
|
||||
public bpb = (n?: number): number => {
|
||||
/**
|
||||
|
||||
@ -1,30 +1,37 @@
|
||||
import { type Editor } from "./main";
|
||||
import { introduction } from "./documentation/introduction";
|
||||
import { oscilloscope } from "./documentation/oscilloscope";
|
||||
import { synchronisation } from "./documentation/synchronisation";
|
||||
import { samples } from "./documentation/samples";
|
||||
// Basics
|
||||
import { introduction } from "./documentation/basics/welcome";
|
||||
import { loading_samples } from "./documentation/samples/loading_samples";
|
||||
import { amplitude } from "./documentation/audio_engine/amplitude";
|
||||
import { reverb } from "./documentation/audio_engine/reverb_delay";
|
||||
import { sampler } from "./documentation/audio_engine/sampler";
|
||||
import { sample_banks } from "./documentation/samples/sample_banks";
|
||||
import { audio_basics } from "./documentation/audio_engine/audio_basics";
|
||||
import { sample_list } from "./documentation/samples/sample_list";
|
||||
import { software_interface } from "./documentation/basics/interface";
|
||||
import { shortcuts } from "./documentation/basics/keyboard";
|
||||
import { code } from "./documentation/basics/code";
|
||||
import { mouse } from "./documentation/basics/mouse";
|
||||
// More
|
||||
import { oscilloscope } from "./documentation/more/oscilloscope";
|
||||
import { synchronisation } from "./documentation/more/synchronisation";
|
||||
import { about } from "./documentation/more/about";
|
||||
import { bonus } from "./documentation/more/bonus";
|
||||
import { chaining } from "./documentation/chaining";
|
||||
import { software_interface } from "./documentation/interface";
|
||||
import { interaction } from "./documentation/interaction";
|
||||
import { time } from "./documentation/time";
|
||||
import { linear_time } from "./documentation/linear_time";
|
||||
import { cyclical_time } from "./documentation/cyclical_time";
|
||||
import { time } from "./documentation/time/time";
|
||||
import { linear_time } from "./documentation/time/linear_time";
|
||||
import { cyclical_time } from "./documentation/time/cyclical_time";
|
||||
import { long_forms } from "./documentation/long_forms";
|
||||
import { midi } from "./documentation/midi";
|
||||
import { code } from "./documentation/code";
|
||||
import { about } from "./documentation/about";
|
||||
import { sound } from "./documentation/engine";
|
||||
import { shortcuts } from "./documentation/keyboard";
|
||||
import { mouse } from "./documentation/mouse";
|
||||
import { patterns } from "./documentation/patterns";
|
||||
import { functions } from "./documentation/functions";
|
||||
import { variables } from "./documentation/variables";
|
||||
import { probabilities } from "./documentation/probabilities";
|
||||
import { lfos } from "./documentation/lfos";
|
||||
import { ziffers } from "./documentation/ziffers";
|
||||
import { reference } from "./documentation/reference";
|
||||
import { synths } from "./documentation/synths";
|
||||
import { bonus } from "./documentation/bonus";
|
||||
|
||||
// Setting up the Markdown converter with syntax highlighting
|
||||
import showdown from "showdown";
|
||||
@ -76,7 +83,6 @@ export const documentation_factory = (application: Editor) => {
|
||||
cyclic: cyclical_time(application),
|
||||
longform: long_forms(application),
|
||||
sound: sound(application),
|
||||
samples: samples(application),
|
||||
synths: synths(application),
|
||||
chaining: chaining(application),
|
||||
patterns: patterns(application),
|
||||
@ -86,12 +92,18 @@ export const documentation_factory = (application: Editor) => {
|
||||
variables: variables(application),
|
||||
probabilities: probabilities(application),
|
||||
functions: functions(application),
|
||||
reference: reference(),
|
||||
shortcuts: shortcuts(application),
|
||||
amplitude: amplitude(application),
|
||||
reverb_delay: reverb(application),
|
||||
sampler: sampler(application),
|
||||
mouse: mouse(application),
|
||||
oscilloscope: oscilloscope(application),
|
||||
audio_basics: audio_basics(application),
|
||||
synchronisation: synchronisation(application),
|
||||
bonus: bonus(application),
|
||||
sample_list: sample_list(application),
|
||||
sample_banks: sample_banks(application),
|
||||
loading_samples: loading_samples(application),
|
||||
about: about(),
|
||||
};
|
||||
};
|
||||
|
||||
@ -9,7 +9,9 @@ export type ElementMap = {
|
||||
| HTMLInputElement
|
||||
| HTMLSelectElement
|
||||
| HTMLCanvasElement
|
||||
| HTMLFormElement;
|
||||
| HTMLFormElement
|
||||
| HTMLInputElement
|
||||
;
|
||||
};
|
||||
|
||||
export const singleElements = {
|
||||
|
||||
@ -263,7 +263,7 @@ export const initializeSelectedUniverse = (app: Editor): void => {
|
||||
app.universes[app.selected_universe] = structuredClone(template_universe);
|
||||
}
|
||||
}
|
||||
app.interface.universe_viewer.innerHTML = `Topos: ${app.selected_universe}`;
|
||||
(app.interface.universe_viewer as HTMLInputElement).placeholder! = `${app.selected_universe}`;
|
||||
};
|
||||
|
||||
export const emptyUrl = () => {
|
||||
@ -334,7 +334,7 @@ export const loadUniverse = (
|
||||
// Updating references to the currently selected universe
|
||||
app.settings.selected_universe = selectedUniverse;
|
||||
app.selected_universe = selectedUniverse;
|
||||
app.interface.universe_viewer.innerHTML = `Topos: ${selectedUniverse}`;
|
||||
(app.interface.universe_viewer as HTMLInputElement).placeholder! = `${selectedUniverse}`;
|
||||
// Updating the editor View to reflect the selected universe
|
||||
app.updateEditorView();
|
||||
// Evaluating the initialisation script for the selected universe
|
||||
|
||||
@ -64,7 +64,7 @@ export class MidiConnection {
|
||||
constructor(api: UserAPI, settings: AppSettings) {
|
||||
this.api = api;
|
||||
this.settings = settings;
|
||||
this.lastBPM = api.bpm();
|
||||
this.lastBPM = api.tempo();
|
||||
this.roundedBPM = this.lastBPM;
|
||||
this.initializeMidiAccess();
|
||||
}
|
||||
@ -519,7 +519,7 @@ export class MidiConnection {
|
||||
const estimatedBPM = this.estimatedBPM();
|
||||
if (estimatedBPM !== this.roundedBPM) {
|
||||
console.log("Estimated BPM: ", estimatedBPM);
|
||||
this.api.bpm(estimatedBPM);
|
||||
this.api.tempo(estimatedBPM);
|
||||
this.roundedBPM = estimatedBPM;
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,6 +116,25 @@ export const installInterfaceLogic = (app: Editor) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.interface.universe_viewer.addEventListener("keydown", (event: any) => {
|
||||
if (event.key === "Enter") {
|
||||
let content = (app.interface.universe_viewer as HTMLInputElement).value.trim();
|
||||
if (content.length > 2 && content.length < 40) {
|
||||
if (content !== app.selected_universe) {
|
||||
Object.defineProperty(app.universes, content,
|
||||
// @ts-ignore
|
||||
Object.getOwnPropertyDescriptor(app.universes, app.selected_universe));
|
||||
delete app.universes[app.selected_universe];
|
||||
}
|
||||
app.selected_universe = content;
|
||||
loadUniverse(app, app.selected_universe);
|
||||
(app.interface.universe_viewer as HTMLInputElement).placeholder = content;
|
||||
(app.interface.universe_viewer as HTMLInputElement).value = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
app.interface.audio_nudge_range.addEventListener("input", () => {
|
||||
app.clock.nudge = parseInt(
|
||||
(app.interface.audio_nudge_range as HTMLInputElement).value
|
||||
@ -460,6 +479,10 @@ export const installInterfaceLogic = (app: Editor) => {
|
||||
|
||||
[
|
||||
"introduction",
|
||||
"sampler",
|
||||
"amplitude",
|
||||
"audio_basics",
|
||||
"reverb_delay",
|
||||
"interface",
|
||||
"interaction",
|
||||
"code",
|
||||
@ -467,8 +490,7 @@ export const installInterfaceLogic = (app: Editor) => {
|
||||
"linear",
|
||||
"cyclic",
|
||||
"longform",
|
||||
"sound",
|
||||
"samples",
|
||||
// "sound",
|
||||
"synths",
|
||||
"chaining",
|
||||
"patterns",
|
||||
@ -478,15 +500,17 @@ export const installInterfaceLogic = (app: Editor) => {
|
||||
"lfos",
|
||||
"probabilities",
|
||||
"variables",
|
||||
// "reference",
|
||||
"synchronisation",
|
||||
"mouse",
|
||||
"shortcuts",
|
||||
"about",
|
||||
"bonus",
|
||||
"oscilloscope",
|
||||
"sample_list",
|
||||
"loading_samples",
|
||||
].forEach((e) => {
|
||||
let name = `docs_` + e;
|
||||
console.log(name)
|
||||
document.getElementById(name)!.addEventListener("click", async () => {
|
||||
if (name !== "docs_samples") {
|
||||
app.currentDocumentationPane = e;
|
||||
|
||||
@ -39,6 +39,7 @@ export class SoundEvent extends AudibleEvent {
|
||||
volume: ["volume", "vol"],
|
||||
zrand: ["zrand", "zr"],
|
||||
curve: ["curve"],
|
||||
bank: ["bank"],
|
||||
slide: ["slide", "sld"],
|
||||
deltaSlide: ["deltaSlide", "dslide"],
|
||||
pitchJump: ["pitchJump", "pj"],
|
||||
@ -62,6 +63,10 @@ export class SoundEvent extends AudibleEvent {
|
||||
fmrelease: ["fmrelease", "fmrel"],
|
||||
fmvelocity: ["fmvelocity", "fmvel"],
|
||||
fmwave: ["fmwave", "fmw"],
|
||||
phaser: ["phaser", "phas"],
|
||||
phaserDepth: ["phaserDepth", "phasdepth"],
|
||||
phaserSweep: ["phaserSweep", "phassweep"],
|
||||
phaserCenter: ["phaserCenter", "phascenter"],
|
||||
fmadsr: (a: number, d: number, s: number, r: number) => {
|
||||
this.updateValue("fmattack", a);
|
||||
this.updateValue("fmdecay", d);
|
||||
@ -254,7 +259,7 @@ export class SoundEvent extends AudibleEvent {
|
||||
roomfade: ["roomfade", "rfade"],
|
||||
roomlp: ["roomlp", "rlp"],
|
||||
roomdim: ["roomdim", "rdim"],
|
||||
sound: ["s","sound"],
|
||||
sound: ["s", "sound"],
|
||||
size: (value: number) => {
|
||||
this.updateValue("roomsize", value);
|
||||
return this;
|
||||
@ -431,7 +436,8 @@ export class SoundEvent extends AudibleEvent {
|
||||
}
|
||||
};
|
||||
|
||||
out = (): void => {
|
||||
out = (orbit?: number | number[]): void => {
|
||||
if (orbit) this.values["orbit"] = orbit;
|
||||
const events = objectWithArraysToArrayOfObjects(this.values, [
|
||||
"parsedScale",
|
||||
]);
|
||||
@ -442,7 +448,7 @@ export class SoundEvent extends AudibleEvent {
|
||||
// const filteredEvent = filterObject(event, ["analyze","note","dur","freq","s"]);
|
||||
const filteredEvent = event;
|
||||
// No need for note if there is freq
|
||||
if(filteredEvent.freq) { delete filteredEvent.note; }
|
||||
if (filteredEvent.freq) { delete filteredEvent.note; }
|
||||
superdough(filteredEvent, this.nudge - this.app.clock.deviation, filteredEvent.dur);
|
||||
}
|
||||
};
|
||||
|
||||
78
src/documentation/audio_engine/amplitude.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { type Editor } from "../../main";
|
||||
import { makeExampleFactory } from "../../Documentation";
|
||||
|
||||
export const amplitude = (application: Editor): string => {
|
||||
// @ts-ignore
|
||||
const makeExample = makeExampleFactory(application);
|
||||
return `# Amplitude
|
||||
|
||||
Controlling the volume is probably the most important concept you need to know about. Let's learn the basics.
|
||||
|
||||
## Volume / Gain
|
||||
|
||||
| Method | Alias | Description |
|
||||
|----------|-------|------------------------------------------------------------------------------------|
|
||||
| <ic>gain</ic> | | Volume of the synth/sample (exponential). |
|
||||
| <ic>velocity</ic> | vel | Velocity (amplitude) from <ic>0</ic> to <ic>1</ic>. Multipled with gain. |
|
||||
| <ic>dbgain</ic> | db | Attenuation in dB from <ic>-inf</ic> to <ic>+10</ic> (acts as a sound mixer fader).|
|
||||
|
||||
${makeExample(
|
||||
"Velocity manipulated by a counter",
|
||||
`
|
||||
beat(.5)::snd('cp').vel($(1)%10 / 10).out()`,
|
||||
true
|
||||
)}
|
||||
|
||||
## 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 |
|
||||
|---------|-------|-----------------------------------------------|
|
||||
| <ic>attack</ic> | atk | Attack value (time to maximum volume) |
|
||||
| <ic>decay</ic> | dec | Decay value (time to decay to sustain level) |
|
||||
| <ic>sustain</ic> | sus | Sustain value (gain when sound is held) |
|
||||
| <ic>release</ic> | 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 <ic>sawtooth</ic> basic waveform.
|
||||
|
||||
${makeExample(
|
||||
"Simple synthesizer",
|
||||
`
|
||||
let smooth = (sound) => {
|
||||
return sound.cutoff(r(100,500))
|
||||
.lpadsr(usaw(1/8) * 8, 0.05, .125, 0, 0)
|
||||
.gain(r(0.25, 0.4)).adsr(0, r(.2,.4), r(0,0.5), 0)
|
||||
.room(0.9).size(2).o(2).vib(r(2,8)).vibmod(0.125)
|
||||
}
|
||||
beat(.25)::smooth(sound('sawtooth')
|
||||
.note([50,57,55,60].beat(1))).out();
|
||||
beat(.25)::smooth(sound('sawtooth')
|
||||
.note([50,57,55,60].add(12).beat(1.5))).out();
|
||||
`,
|
||||
true
|
||||
)};
|
||||
|
||||
Sometimes, using a full ADSR envelope is a bit overkill. There are other simpler controls to manipulate the envelope like the <ic>.ad</ic> method:
|
||||
|
||||
|
||||
${makeExample(
|
||||
"Replacing .adsr by .ad",
|
||||
`
|
||||
let smooth = (sound) => {
|
||||
return sound.cutoff(r(100,500))
|
||||
.lpadsr(usaw(1/8) * 8, 0.05, .125, 0, 0)
|
||||
.gain(r(0.25, 0.4)).ad(0, .25)
|
||||
.room(0.9).size(2).o(2).vib(r(2,8)).vibmod(0.125)
|
||||
}
|
||||
beat(.25)::smooth(sound('sawtooth')
|
||||
.note([50,57,55,60].beat(1))).out();
|
||||
beat(.25)::smooth(sound('sawtooth')
|
||||
.note([50,57,55,60].add(12).beat(1.5))).out();
|
||||
`,
|
||||
true
|
||||
)};
|
||||
|
||||
`}
|
||||
|
||||
|
||||
177
src/documentation/audio_engine/audio_basics.ts
Normal file
@ -0,0 +1,177 @@
|
||||
import { type Editor } from "../../main";
|
||||
import { makeExampleFactory } from "../../Documentation";
|
||||
|
||||
export const audio_basics = (application: Editor): string => {
|
||||
// @ts-ignore
|
||||
const makeExample = makeExampleFactory(application);
|
||||
return `# Audio Basics
|
||||
|
||||
The Topos audio engine is [SuperDough](https://www.npmjs.com/package/superdough). This audio engine that takes advantage of the [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API). The engine is capable of many things such as playing samples, synths and effects all 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 if you wish! Let's learn the basics.
|
||||
|
||||
## Sound basics
|
||||
|
||||
Use the <ic>sound(name: string)</ic> function to play a sound. You can also write <ic>snd</ic>. A sound can be:
|
||||
- an audio sample: taken from the samples currently loaded
|
||||
- a synthesizer: the name of a specific waveform
|
||||
|
||||
Whatever you choose, the syntax stays the same. See the following example:
|
||||
|
||||
${makeExample(
|
||||
"Playing sounds is easy",
|
||||
`
|
||||
beat(1) && sound('bd').out()
|
||||
beat(0.5) && sound('hh').out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
These commands, in plain english, can be translated to:
|
||||
|
||||
> Every beat, play a kick drum.
|
||||
> Every half-beat, play a high-hat.
|
||||
|
||||
Let's make this example a bit more complex:
|
||||
|
||||
${makeExample(
|
||||
"Adding some effects",
|
||||
`
|
||||
beat(1) && sound('bd').coarse(0.25).room(0.5).orbit(2).out();
|
||||
beat(0.5) && sound('hh').delay(0.25).delaytime(0.125).out();
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
Now, it translates as follows:
|
||||
|
||||
> Every beat, play a kick drum with some amount of distortion on the third audio bus.
|
||||
> Every half-beat, play a high-hat with 25% of the sound injected in
|
||||
> a delay unit, with a delay time of 0.125 seconds.
|
||||
|
||||
If you remove <ic>beat</ic> instruction, you will end up with a deluge of kick drums and high-hats. <ic>beat</ic> in that case, is used to filter time. It is a very useful instruction to create basic rhythms. Check out the **Time** section if you haven't read it already.
|
||||
|
||||
## The <ic>.out</ic> method
|
||||
|
||||
To play a sound, you always need the <ic>.out()</ic> method at the end of your chain. THis method tells **Topos** to send the chain to the audio engine. The <ic>.out</ic> method can take an optional argument to send the sound to a numbered effect bus, from <ic>0</ic> to <ic>n</ic> :
|
||||
|
||||
${makeExample("Using the .out method",
|
||||
`
|
||||
// Playing a clap on the third bus (0-indexed)
|
||||
beat(1)::sound('cp').out(2)
|
||||
`, true
|
||||
)}
|
||||
|
||||
Try to remove <ic>.out</ic>. You will see that no sound is playing at all!
|
||||
|
||||
## Sound chains
|
||||
|
||||
- Sounds are **composed** by adding qualifiers/parameters that modify the sound or synthesizer you have picked (_e.g_ <ic>sound('...').blabla(...)..something(...).out()</ic>. Think of it as _audio chains_.
|
||||
|
||||
${makeExample(
|
||||
'Complex sonic object',
|
||||
`
|
||||
beat(1) :: sound('pad').n(1)
|
||||
.begin(rand(0, 0.4))
|
||||
.freq([50,52].beat())
|
||||
.size(0.9).room(0.9)
|
||||
.velocity(0.25)
|
||||
.pan(usine()).release(2).out()`,
|
||||
true
|
||||
)}
|
||||
|
||||
## Picking a specific sound
|
||||
|
||||
If you choose the sound <ic>kick</ic>, you are asking for the first sample in the <ic>kick</ic> folder. All the sample names are folders. Synthesizers are not affected by this. Here is what the <ic>kick</ic>might be looking like:
|
||||
|
||||
\`\`\`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 <ic>.n(number)</ic> 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:
|
||||
${makeExample(
|
||||
"Picking a sample",
|
||||
`
|
||||
beat(1) && sound('kick').n([1,2,3,4,5,6,7,8].pick()).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
You can also use the <ic>:</ic> to pick a sample number directly from the <ic>sound</ic> function:
|
||||
|
||||
${makeExample(
|
||||
"Picking a sample using :",
|
||||
`
|
||||
beat(1) && sound('kick:3').out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
You can use any number to pick a sound. Don't be afraid of using a number too big. If the number exceeds the number of available samples, it will simply wrap around and loop infinitely over the folder. Let's demonstrate this by using the mouse over a very large sample folder:
|
||||
|
||||
${makeExample(
|
||||
"Picking a sample... with the mouse!",
|
||||
`
|
||||
// Move your mouse to change the sample being used!
|
||||
beat(.25) && sound('ST09').n(Math.floor(mouseX())).out()`,
|
||||
true
|
||||
)}
|
||||
|
||||
|
||||
The <ic>.n</ic> method is also used for synthesizers but it behaves differently. When using a synthesizer, this method can help you determine the number of harmonics in your waveform. See the **Synthesizers** section to learn more about this.
|
||||
|
||||
## 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 |
|
||||
|----------|-------|------------------------------------------------------------|
|
||||
| <ic>orbit</ic> | o | Orbit number |
|
||||
|
||||
You can play a sound _dry_ and another sound _wet_. Take a look at this example where the reverb is only affecting one of the sounds:
|
||||
|
||||
${makeExample("Dry and wet", `
|
||||
|
||||
// This sound is dry
|
||||
beat(1)::sound('hh').out()
|
||||
|
||||
// This sound is wet (reverb)
|
||||
beat(2)::sound('cp').orbit(2).room(0.5).size(8).out()
|
||||
`, true)}
|
||||
|
||||
## The art of chaining
|
||||
|
||||
Learning to create complex chains is very important when using **Topos**. It can take some time to learn all the possible parameters. Don't worry, it's actually rather easy to learn.
|
||||
|
||||
${makeExample(
|
||||
"Complex chain",
|
||||
`
|
||||
beat(0.25) && sound('fhh')
|
||||
.sometimes(s=>s.speed([2, 0.5].pick()))
|
||||
.room(0.9).size(0.9).gain(1)
|
||||
.cutoff(usine(1/2) * 5000)
|
||||
.out()`,
|
||||
true
|
||||
)}
|
||||
|
||||
Most audio parameters can be used both for samples and synthesizers. This is quite unconventional if you are familiar with a more traditional music software.
|
||||
`}
|
||||
|
||||
|
||||
31
src/documentation/audio_engine/distortion.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { type Editor } from "../../main";
|
||||
import { makeExampleFactory } from "../../Documentation";
|
||||
|
||||
export const distortion = (application: Editor): string => {
|
||||
// @ts-ignore
|
||||
const makeExample = makeExampleFactory(application);
|
||||
return `# Distortion
|
||||
|
||||
## Distorsion, saturation, destruction
|
||||
|
||||
Three additional effects that are easy enough to understand. These effects are deteriorating the signal, making it easy to get digital or gritty audio sample playback or synthesizers destroyed beyond recognition. Be careful with your signal level!
|
||||
|
||||
| Method | Alias | Description |
|
||||
|------------|-----------|---------------------------------|
|
||||
| <ic>coarse</ic> | | Artificial sample-rate lowering |
|
||||
| <ic>crush</ic> | | bitcrushing. <ic>1</ic> is extreme, the more you go up, the less it takes effect. |
|
||||
| <ic>shape</ic> | | Waveshaping distortion (between <ic>0</ic> and <ic>1</ic>) |
|
||||
|
||||
|
||||
${makeExample(
|
||||
"Crunch... crunch... crunch!",
|
||||
`
|
||||
beat(.5)::snd('pad').coarse($(1) % 16).clip(.5).out(); // Comment me
|
||||
beat(.5)::snd('pad').crush([16, 8, 4].beat(2)).clip(.5).out()
|
||||
`,
|
||||
true
|
||||
)};
|
||||
|
||||
`}
|
||||
|
||||
|
||||
86
src/documentation/audio_engine/reverb_delay.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { type Editor } from "../../main";
|
||||
import { makeExampleFactory } from "../../Documentation";
|
||||
|
||||
export const reverb = (application: Editor): string => {
|
||||
// @ts-ignore
|
||||
const makeExample = makeExampleFactory(application);
|
||||
return `
|
||||
|
||||
# Audio effects
|
||||
|
||||
There is a growing collection of audio effects you can use directly baked in the engine. You can create a wide variety of sonic effects by being creative with the parameters they offer.
|
||||
|
||||
## Reverb
|
||||
|
||||
A good sounding reverb. This reverb unit is using a convolution that gets updated everytime you change a parameter.
|
||||
For that reason, it is often a good idea to set fixed reverb values per orbit. Do not try to pattern the reverb too much.
|
||||
|
||||
| Method | Alias | Description |
|
||||
|------------|-------|---------------------------------|
|
||||
| <ic>room</ic> | rm | Reverb level (between <ic>0</ic> and <ic>1</ic> |
|
||||
| <ic>size</ic> | sz | Reverb room size of the reverb, between <ic>0</ic> and <ic>n</ic> |
|
||||
| <ic>roomfade</ic> | | Reverb fade time, in seconds |
|
||||
| <ic>roomlp</ic> | | Reverb lowpass starting frequency (in hertz) |
|
||||
| <ic>roomdim</ic> | | Reverb lowpass frequency at -60db (in hertz) |
|
||||
|
||||
${makeExample(
|
||||
"Clapping in the cavern",
|
||||
`
|
||||
beat(2)::snd('cp').room(0.5).size(4).out()
|
||||
`,
|
||||
true
|
||||
)};
|
||||
|
||||
## Delay
|
||||
|
||||
A good sounding delay unit that can go into feedback territory. Use it without moderation.
|
||||
|
||||
| Method | Alias | Description |
|
||||
|------------|-----------|---------------------------------|
|
||||
| <ic>delay</ic> | | Delay _wet/dry_ (between <ic>0</ic> and <ic>1</ic>) |
|
||||
| <ic>delaytime</ic> | delayt | Delay time (in milliseconds) |
|
||||
| <ic>delayfeedback</ic> | delayfb | Delay feedback (between <ic>0</ic> and <ic>1</ic>) |
|
||||
|
||||
${makeExample(
|
||||
"Who doesn't like delay?", `
|
||||
beat(2)::snd('cp').delay(0.5).delaytime(0.75).delayfb(0.8).out()
|
||||
beat(4)::snd('snare').out()
|
||||
beat(1)::snd('kick').out()`, true)}
|
||||
|
||||
## Phaser
|
||||
|
||||
| Method | Alias | Description |
|
||||
|------------|-----------|---------------------------------|
|
||||
| <ic>phaser</ic> | <ic>phas</ic> | Phaser speed, between <ic>1</ic> and <ic>n</ic> |
|
||||
| <ic>phaserDepth</ic> | <ic>phasdepth</ic> | How much of the signal goes through phaser (<ic>0</ic> to <ic>1</ic>) |
|
||||
| <ic>phaserSweep</ic> | <ic>phassweep</ic> | Phaser frequency sweep (in hertz) |
|
||||
| <ic>phaserCenter</ic> | <ic>phascenter</ic> | Phaser center frequency (default to 1000) |
|
||||
|
||||
${makeExample("Super cool phaser lick", `
|
||||
rhythm(.5, 7, 8)::sound('wt_stereo')
|
||||
.phaser(0.75).phaserSweep(3000)
|
||||
.phaserCenter(1500).phaserDepth(1)
|
||||
.note([0, 1, 2, 3, 4, 5, 6].scale('pentatonic', 50).beat(0.25))
|
||||
.room(0.5).size(4).out()
|
||||
`, true)}
|
||||
|
||||
## Distorsion, saturation, destruction
|
||||
|
||||
Three additional effects that are easy enough to understand. These effects are deteriorating the signal, making it easy to get digital or gritty audio sample playback or synthesizers destroyed beyond recognition. Be careful with your signal level!
|
||||
|
||||
| Method | Alias | Description |
|
||||
|------------|-----------|---------------------------------|
|
||||
| <ic>coarse</ic> | | Artificial sample-rate lowering |
|
||||
| <ic>crush</ic> | | bitcrushing. <ic>1</ic> is extreme, the more you go up, the less it takes effect. |
|
||||
| <ic>shape</ic> | | Waveshaping distortion (between <ic>0</ic> and <ic>1</ic>) |
|
||||
|
||||
|
||||
${makeExample(
|
||||
"Crunch... crunch... crunch!",
|
||||
`
|
||||
beat(.5)::snd('pad').coarse($(1) % 16).clip(.5).out(); // Comment me
|
||||
beat(.5)::snd('pad').crush([16, 8, 4].beat(2)).clip(.5).out()
|
||||
`,
|
||||
true
|
||||
)};
|
||||
`}
|
||||
144
src/documentation/audio_engine/sampler.ts
Normal file
@ -0,0 +1,144 @@
|
||||
import { type Editor } from "../../main";
|
||||
import { makeExampleFactory } from "../../Documentation";
|
||||
|
||||
export const sampler = (application: Editor): string => {
|
||||
// @ts-ignore
|
||||
const makeExample = makeExampleFactory(application);
|
||||
return `# Sampler
|
||||
|
||||
The sampler is a rather complex beast. There is a lot you can do by manipulating samples, from simple playback to complex granulation, wavetable synthesis and concrete music.
|
||||
|
||||
|
||||
| Method | Alias | Description |
|
||||
|---------|--------|--------------------------------------------------------|
|
||||
| <ic>n</ic> | | Select a sample in the current folder (from <ic>0</ic> to infinity) |
|
||||
| <ic>begin</ic> | | Beginning of the sample playback (between <ic>0</ic> and <ic>1</ic>) |
|
||||
| <ic>end</ic> | | End of the sample (between <ic>0</ic> and <ic>1</ic>) |
|
||||
| <ic>loopBegin</ic> | | Beginning of the loop section (between <ic>0</ic> and <ic>1</ic>) |
|
||||
| <ic>loopEnd</ic> | | End of the loop section (between <ic>0</ic> and <ic>1</ic>) |
|
||||
| <ic>loop</ic> | | Whether to loop or not the audio sample |
|
||||
| <ic>stretch</ic> | | Stretches the audio playback rate of a sample over <ic>n</ic> beats |
|
||||
| <ic>speed</ic> | | Playback speed (<ic>2</ic> = twice as fast) |
|
||||
| <ic>cut</ic> | | Set with <ic>0</ic> or <ic>1</ic>. Will cut the sample as soon as another sample is played on the same bus |
|
||||
| <ic>clip</ic> | | Multiply the duration of the sample with the given number |
|
||||
| <ic>pan</ic> | | Stereo position of the audio playback (<ic>0</ic> = left, <ic>1</ic> = right)|
|
||||
| <ic>vib</ic> | | vibrato speed (in hertz)|
|
||||
| <ic>vibmod</ic> | | vibrato depth (from <ic>0</ic> to <ic>n</ic>)|
|
||||
|
||||
Let's apply some of these methods naïvely. We will then break everything using simpler examples.
|
||||
|
||||
${makeExample(
|
||||
"Complex sampling duties",
|
||||
`
|
||||
// Using some of the modifiers described above :)
|
||||
beat(.5)::snd('pad').begin(0.2)
|
||||
.speed([1, 0.9, 0.8].beat(4))
|
||||
.n(2).pan(usine(.5))
|
||||
.end(rand(0.3,0.8))
|
||||
.room(0.8).size(0.5)
|
||||
.clip(1).out()
|
||||
`,
|
||||
true
|
||||
)};
|
||||
|
||||
|
||||
## Playback speed / pitching samples
|
||||
|
||||
Let's play with the <ic>speed</ic> parameter to control the pitch of sample playback:
|
||||
|
||||
${makeExample("Controlling the playback speed", `
|
||||
beat(0.5)::sound('notes')
|
||||
.speed([1,2,3,4].palindrome().beat(0.5)).out()
|
||||
`, true)}
|
||||
|
||||
It also works by using negative values. It reverses the playback:
|
||||
|
||||
${makeExample("Playing samples backwards", `
|
||||
beat(0.5)::sound('notes')
|
||||
.speed(-[1,2,3,4].palindrome().beat(0.5)).out()
|
||||
`, true)}
|
||||
|
||||
Of course you can play melodies using samples:
|
||||
|
||||
|
||||
${makeExample("Playing melodies using samples", `
|
||||
beat(0.5)::sound('notes')
|
||||
.room(0.5).size(4)
|
||||
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.5)).out()
|
||||
`, true)}
|
||||
|
||||
## Panning
|
||||
|
||||
To pan samples, use the <ic>.pan</ic> method with a number between <ic>0</ic> and <ic>1</ic>.
|
||||
|
||||
|
||||
${makeExample("Playing melodies using samples", `
|
||||
beat(0.25)::sound('notes')
|
||||
.room(0.5).size(4).pan(r(0, 1))
|
||||
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.25)).out()
|
||||
`, true)}
|
||||
|
||||
|
||||
## Looping over a sample
|
||||
|
||||
Using <ic>loop</ic> (<ic>1</ic> for looping), <ic>loopBegin</ic> and <ic>loopEnd</ic> (between <ic>0</ic> and <ic>1</ic>), you can loop over the length of a sample. It can be super effective to create granular effects.
|
||||
|
||||
|
||||
${makeExample("Granulation using loop", `
|
||||
beat(0.25)::sound('fikea').loop(1)
|
||||
.lpf(ir(2000, 5000))
|
||||
.loopBegin(0).loopEnd(r(0, 1))
|
||||
.room(0.5).size(4).pan(r(0, 1))
|
||||
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.25)).out()
|
||||
`, true)}
|
||||
|
||||
## Stretching a sample
|
||||
|
||||
The <ic>stretch</ic> parameter can help you to stretch long samples like amen breaks:
|
||||
|
||||
${makeExample(
|
||||
"Playing an amen break",
|
||||
`
|
||||
// Note that stretch has the same value as beat
|
||||
beat(4) :: sound('amen1').n(11).stretch(4).out()
|
||||
beat(1) :: sound('kick').shape(0.35).out()`,
|
||||
true,
|
||||
)};
|
||||
|
||||
## Cutting samples
|
||||
|
||||
Sometimes, you will find it necessary to cut a sample. It can be because the sample is too long but also because it takes too much CPU to play too many samples at the same time.
|
||||
|
||||
Know about the <ic>begin</ic> and <ic>end</ic> parameters. They are not related to the sampler itself, but to the length of the event you are playing. Let's cut the granular example:
|
||||
|
||||
${makeExample("Cutting a sample using end", `
|
||||
beat(0.25)::sound('notes')
|
||||
.end(usine(1/2)/0.5)
|
||||
.room(0.5).size(4).pan(r(0, 1))
|
||||
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.25)).out()
|
||||
`, true)}
|
||||
|
||||
You can also use <ic>clip</ic> to cut the sample everytime a new sample comes in:
|
||||
|
||||
|
||||
${makeExample("Cutting a sample using end", `
|
||||
beat(0.125)::sound('notes')
|
||||
.cut(1)
|
||||
.room(0.5).size(4).pan(r(0, 1))
|
||||
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.125)
|
||||
+ [-12,12].beat()).out()
|
||||
`, true)}
|
||||
|
||||
## Adding vibrato to samples
|
||||
|
||||
You can add vibrato to any sample using <ic>vib</ic> and <ic>vibmod</ic>:
|
||||
|
||||
${makeExample("Adding vibrato to a sample", `
|
||||
|
||||
beat(1)::sound('fhang').vib([1, 2, 4].bar()).vibmod([0.5, 2].beat()).out()
|
||||
`, true)}
|
||||
|
||||
|
||||
`}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { type Editor } from "../main";
|
||||
import { makeExampleFactory, key_shortcut } from "../Documentation";
|
||||
import { type Editor } from "../../main";
|
||||
import { makeExampleFactory, key_shortcut } from "../../Documentation";
|
||||
|
||||
export const code = (application: Editor): string => {
|
||||
const makeExample = makeExampleFactory(application);
|
||||
@ -1,5 +1,5 @@
|
||||
import { key_shortcut, makeExampleFactory } from "../Documentation";
|
||||
import { type Editor } from "../main";
|
||||
import { key_shortcut, makeExampleFactory } from "../../Documentation";
|
||||
import { type Editor } from "../../main";
|
||||
import topos_arch from "./topos_arch.svg";
|
||||
import many_universes from "./many_universes.svg";
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { key_shortcut } from "../Documentation";
|
||||
import { type Editor } from "../main";
|
||||
import { makeExampleFactory } from "../Documentation";
|
||||
import { key_shortcut } from "../../Documentation";
|
||||
import { type Editor } from "../../main";
|
||||
import { makeExampleFactory } from "../../Documentation";
|
||||
|
||||
export const shortcuts = (app: Editor): string => {
|
||||
let makeExample = makeExampleFactory(app);
|
||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
@ -1,5 +1,5 @@
|
||||
import { type Editor } from "../main";
|
||||
import { makeExampleFactory } from "../Documentation";
|
||||
import { type Editor } from "../../main";
|
||||
import { makeExampleFactory } from "../../Documentation";
|
||||
|
||||
export const mouse = (app: Editor): string => {
|
||||
let makeExample = makeExampleFactory(app);
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
@ -1,6 +1,6 @@
|
||||
import { makeExampleFactory, key_shortcut } from "../Documentation";
|
||||
import { type Editor } from "../main";
|
||||
import { examples } from "../examples/excerpts";
|
||||
import { makeExampleFactory, key_shortcut } from "../../Documentation";
|
||||
import { type Editor } from "../../main";
|
||||
import { examples } from "../../examples/excerpts";
|
||||
|
||||
export const introduction = (application: Editor): string => {
|
||||
const makeExample = makeExampleFactory(application);
|
||||
@ -4,179 +4,8 @@ import { makeExampleFactory } from "../Documentation";
|
||||
export const sound = (application: Editor): string => {
|
||||
const makeExample = makeExampleFactory(application);
|
||||
return `
|
||||
# Audio engine
|
||||
|
||||
The Topos audio engine is based on the [SuperDough](https://www.npmjs.com/package/superdough) audio backend that takes advantage of the [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API). The engine is capable of many things such as playing samples, synths and effects all 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 if you wish!
|
||||
|
||||
## Sound basics
|
||||
|
||||
The basic function to play a sound is... <ic>sound(name: string)</ic> (you can also write <ic>snd</ic> to save some precious time). If the given sound (or synthesizer) is already declared, it will be automatically queried/started and will start playing. Evaluate the following script in the global window:
|
||||
|
||||
${makeExample(
|
||||
"Playing sounds is easy",
|
||||
`
|
||||
beat(1) && sound('bd').out()
|
||||
beat(0.5) && sound('hh').out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
In plain english, this translates to:
|
||||
|
||||
> Every beat, play a kick drum.
|
||||
> Every half-beat, play a high-hat.
|
||||
|
||||
Let's make it slightly more complex:
|
||||
|
||||
${makeExample(
|
||||
"Adding some effects",
|
||||
`
|
||||
beat(1) && sound('bd').coarse(0.25).room(0.5).orbit(2).out();
|
||||
beat(0.5) && sound('hh').delay(0.25).delaytime(0.125).out();
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
Now, it reads as follow:
|
||||
|
||||
> Every beat, play a kick drum with some amount of distortion.
|
||||
> Every half-beat, 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 and explain what is going on:
|
||||
|
||||
- If you remove <ic>beat</ic> instruction, you will end up with a deluge of kick drums and high-hats. <ic>beat</ic> in that case, is used to filter time. 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 <ic>.out()</ic> method that gives the instruction to send a message to the audio engine.
|
||||
- Sounds are **composed** by adding qualifiers/parameters that will modify the sound or synthesizer being played (_e.g_ <ic>sound('...').blabla(...)..something(...).out()</ic>. Think of it as _audio chains_.
|
||||
|
||||
${makeExample(
|
||||
'"Composing" a complex sonic object by making a sound chain',
|
||||
`
|
||||
beat(1) :: sound('pad').n(1)
|
||||
.begin(rand(0, 0.4))
|
||||
.freq([50,52].beat())
|
||||
.size(0.9).room(0.9)
|
||||
.velocity(0.25)
|
||||
.pan(usine()).release(2).out()`,
|
||||
true
|
||||
)}
|
||||
|
||||
## Audio Sample Folders / Sample Files
|
||||
|
||||
When you type <ic>kick</ic> in the <ic>sound('kick').out()</ic> 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 <ic>.n(number)</ic> 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:
|
||||
${makeExample(
|
||||
"Picking a sample",
|
||||
`
|
||||
beat(1) && sound('kick').n([1,2,3,4,5,6,7,8].pick()).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
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:
|
||||
|
||||
${makeExample(
|
||||
"Picking a sample... with your mouse!",
|
||||
`
|
||||
// Move your mouse to change the sample being used!
|
||||
beat(.25) && sound('numbers').n(Math.floor(mouseX())).out()`,
|
||||
true
|
||||
)}
|
||||
|
||||
**Note:** the <ic>sound</ic> function can also be used to play synthesizers (see the **Synthesizers** page). In that case, the <ic>.n(n: number)</ic> becomes totally useless!
|
||||
|
||||
## Learning about sound modifiers
|
||||
|
||||
As we said earlier, the <ic>sound('sample_name')</ic> function can be chained to _specify_ a sound more. For instance, you can add a filter and some effects to your high-hat:
|
||||
${makeExample(
|
||||
"Let's make something more complex",
|
||||
`
|
||||
beat(0.25) && sound('jvbass')
|
||||
.sometimes(s=>s.speed([2, 0.5].pick()))
|
||||
.room(0.9).size(0.9).gain(1)
|
||||
.cutoff(usine(1/2) * 5000)
|
||||
.out()`,
|
||||
true
|
||||
)}
|
||||
|
||||
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 |
|
||||
|----------|-------|------------------------------------------------------------|
|
||||
| <ic>orbit</ic> | o | Orbit number |
|
||||
|
||||
|
||||
## Amplitude
|
||||
|
||||
Simple controls over the amplitude (volume) of a given sound.
|
||||
|
||||
| Method | Alias | Description |
|
||||
|----------|-------|------------------------------------------------------------------------------------|
|
||||
| <ic>gain</ic> | | Volume of the synth/sample (exponential) |
|
||||
| <ic>velocity</ic> | vel | Velocity (amplitude) from <ic>0</ic> to <ic>1</ic>. Multipled with gain |
|
||||
| <ic>dbgain</ic> | db | Attenuation in dB from <ic>-inf</ic> to <ic>+10</ic> (acts as a sound mixer fader) |
|
||||
|
||||
${makeExample(
|
||||
"Velocity manipulated by a counter",
|
||||
`
|
||||
beat(.5)::snd('cp').vel($(1)%10 / 10).out()`,
|
||||
true
|
||||
)}
|
||||
|
||||
## 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 |
|
||||
|---------|-------|-----------------------------------------------|
|
||||
| <ic>attack</ic> | atk | Attack value (time to maximum volume) |
|
||||
| <ic>decay</ic> | dec | Decay value (time to decay to sustain level) |
|
||||
| <ic>sustain</ic> | sus | Sustain value (gain when sound is held) |
|
||||
| <ic>release</ic> | 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 <ic>sawtooth</ic> basic waveform.
|
||||
|
||||
${makeExample(
|
||||
"Simple synthesizer",
|
||||
`
|
||||
let smooth = (sound) => {
|
||||
return sound.cutoff(r(100,500))
|
||||
.lpadsr(usaw(1/8) * 8, 0.05, .125, 0, 0)
|
||||
.gain(r(0.25, 0.4)).adsr(0, r(.2,.4), r(0,0.5), 0)
|
||||
.room(0.9).size(2).o(2).vib(r(2,8)).vibmod(0.125)
|
||||
}
|
||||
beat(.25)::smooth(sound('sawtooth').note([50,57,55,60].beat(1))).out();
|
||||
beat(.25)::smooth(sound('sawtooth').note([50,57,55,60].add(12).beat(1.5))).out();
|
||||
`,
|
||||
true
|
||||
)};
|
||||
|
||||
## 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.
|
||||
@ -244,47 +73,6 @@ beat(.5) && snd('sawtooth')
|
||||
true
|
||||
)};
|
||||
|
||||
## Reverb
|
||||
|
||||
A good sounding reverb. This reverb unit is using a convolution that gets updated everytime you change a parameter.
|
||||
For that reason, it is often a good idea to set fixed reverb values per orbit. Do not try to pattern the reverb too much.
|
||||
|
||||
| Method | Alias | Description |
|
||||
|------------|-------|---------------------------------|
|
||||
| <ic>room</ic> | rm | Reverb level (between <ic>0</ic> and <ic>1</ic> |
|
||||
| <ic>size</ic> | sz | Reverb room size of the reverb, between <ic>0</ic> and <ic>n</ic> |
|
||||
| <ic>roomfade</ic> | | Reverb fade time, in seconds |
|
||||
| <ic>roomlp</ic> | | Reverb lowpass starting frequency (in hertz) |
|
||||
| <ic>roomdim</ic> | | Reverb lowpass frequency at -60db (in hertz) |
|
||||
|
||||
${makeExample(
|
||||
"Clapping in the cavern",
|
||||
`
|
||||
beat(2)::snd('cp').room(0.5).size(4).out()
|
||||
`,
|
||||
true
|
||||
)};
|
||||
|
||||
|
||||
## Delay
|
||||
|
||||
A good sounding delay unit that can go into feedback territory. Use it without moderation.
|
||||
|
||||
| Method | Alias | Description |
|
||||
|------------|-----------|---------------------------------|
|
||||
| <ic>delay</ic> | | Delay _wet/dry_ (between <ic>0</ic> and <ic>1</ic>) |
|
||||
| <ic>delaytime</ic> | delayt | Delay time (in milliseconds) |
|
||||
| <ic>delayfeedback</ic> | delayfb | Delay feedback (between <ic>0</ic> and <ic>1</ic>) |
|
||||
|
||||
${makeExample(
|
||||
"Who doesn't like delay?",
|
||||
`
|
||||
beat(2)::snd('cp').delay(0.5).delaytime(0.75).delayfb(0.8).out()
|
||||
beat(4)::snd('snare').out()
|
||||
beat(1)::snd('kick').out()
|
||||
`,
|
||||
true
|
||||
)};
|
||||
|
||||
## Compression
|
||||
|
||||
@ -299,24 +87,5 @@ This effect is leveraging the basic WebAudio compressor. More information can be
|
||||
| <ic>compRelease</ic> | cmpr | In seconds, time to increase the gain by 10db |
|
||||
|
||||
|
||||
## Distorsion, saturation, destruction
|
||||
|
||||
Three additional effects that are easy enough to understand. These effects are deteriorating the signal, making it easy to get digital or gritty audio sample playback or synthesizers destroyed beyond recognition. Be careful with your signal level!
|
||||
|
||||
| Method | Alias | Description |
|
||||
|------------|-----------|---------------------------------|
|
||||
| <ic>coarse</ic> | | Artificial sample-rate lowering |
|
||||
| <ic>crush</ic> | | bitcrushing. <ic>1</ic> is extreme, the more you go up, the less it takes effect. |
|
||||
| <ic>shape</ic> | | Waveshaping distortion (between <ic>0</ic> and <ic>1</ic>) |
|
||||
|
||||
|
||||
${makeExample(
|
||||
"Crunch... crunch... crunch!",
|
||||
`
|
||||
beat(.5)::snd('pad').coarse($(1) % 16).clip(.5).out(); // Comment me
|
||||
beat(.5)::snd('pad').crush([16, 8, 4].beat(2)).clip(.5).out()
|
||||
`,
|
||||
true
|
||||
)};
|
||||
`;
|
||||
};
|
||||
|
||||
@ -555,19 +555,12 @@ const completionDatabase: CompletionDatabase = {
|
||||
"Base function to play audio (samples / synths). Alias for <code>sound<code>.",
|
||||
example: "sound('bd').out()",
|
||||
},
|
||||
bpm: {
|
||||
name: "bpm",
|
||||
category: "time",
|
||||
description: "Get or set the current beats per minute.",
|
||||
example: "bpm(135) // set the bpm to 135",
|
||||
},
|
||||
tempo: {
|
||||
name: "tempo",
|
||||
category: "time",
|
||||
description: "Get or set the current beats per minute.",
|
||||
example: "tempo(135) // set the bpm to 135",
|
||||
},
|
||||
|
||||
out: {
|
||||
name: "out",
|
||||
category: "audio",
|
||||
|
||||
@ -140,7 +140,7 @@ flipbar(2)
|
||||
${makeExample(
|
||||
"Using onbar for filler drums",
|
||||
`
|
||||
bpm(150);
|
||||
tempo(150);
|
||||
// Only play on the third and fourth bar of the cycle.
|
||||
onbar([3,4], 4)::beat(.25)::snd('hh').out();
|
||||
// Using JavaScript regular control flow
|
||||
|
||||
@ -22,22 +22,22 @@ Your web browser is capable of sending and receiving MIDI information through th
|
||||
|
||||
|
||||
${makeExample(
|
||||
"Listing MIDI outputs",
|
||||
`
|
||||
"Listing MIDI outputs",
|
||||
`
|
||||
midi_outputs()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
- <ic>midi_output(output_name: string)</ic>: enter your desired output to connect to it.
|
||||
|
||||
${makeExample(
|
||||
"Changing MIDI output",
|
||||
`
|
||||
"Changing MIDI output",
|
||||
`
|
||||
midi_output("MIDI Rocket-Trumpet")
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
That's it! You are now ready to play with MIDI.
|
||||
|
||||
@ -48,69 +48,69 @@ The most basic MIDI event is the note. MIDI notes traditionally take three param
|
||||
- <ic>midi(note: number|object)</ic>: send a MIDI Note. This function is quite bizarre. It can be written and used in many different ways. You can pass form one up to three arguments in different forms.
|
||||
|
||||
${makeExample(
|
||||
"MIDI note using one parameter: note",
|
||||
`
|
||||
"MIDI note using one parameter: note",
|
||||
`
|
||||
// Configure your MIDI first!
|
||||
// => midi_output("MIDI Bus 1")
|
||||
rhythm(.5, 5, 8) :: midi(50).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"MIDI note using three parameters: note, velocity, channel",
|
||||
`
|
||||
"MIDI note using three parameters: note, velocity, channel",
|
||||
`
|
||||
// MIDI Note 50, Velocity 50 + LFO, Channel 0
|
||||
rhythm(.5, 5, 8) :: midi(50, 50 + usine(.5) * 20, 0).out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
false
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"MIDI note by passing an object",
|
||||
`
|
||||
"MIDI note by passing an object",
|
||||
`
|
||||
// MIDI Note 50, Velocity 50 + LFO, Channel 0
|
||||
rhythm(.5, 5, 8) :: midi({note: 50, velocity: 50 + usine(.5) * 20, channel: 0}).out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
false
|
||||
)}
|
||||
|
||||
We can now have some fun and starting playing a small piano piece:
|
||||
|
||||
${makeExample(
|
||||
"Playing some piano",
|
||||
`
|
||||
bpm(80) // Setting a default BPM
|
||||
"Playing some piano",
|
||||
`
|
||||
tempo(80) // Setting a default BPM
|
||||
beat(.5) && midi(36 + [0,12].beat()).sustain(0.02).out()
|
||||
beat(.25) && midi([64, 76].pick()).sustain(0.05).out()
|
||||
beat(.75) && midi([64, 67, 69].beat()).sustain(0.05).out()
|
||||
beat(.25) && midi([64, 67, 69].beat() + 24).sustain(0.05).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
## Control and Program Changes
|
||||
|
||||
- <ic>control_change({control: number, value: number, channel: number})</ic>: send a MIDI Control Change. This function takes a single object argument to specify the control message (_e.g._ <ic>control_change({control: 1, value: 127, channel: 1})</ic>).
|
||||
|
||||
${makeExample(
|
||||
"Imagine that I am tweaking an hardware synthesizer!",
|
||||
`
|
||||
"Imagine that I am tweaking an hardware synthesizer!",
|
||||
`
|
||||
control_change({control: [24,25].pick(), value: irand(1,120), channel: 1})
|
||||
control_change({control: [30,35].pick(), value: irand(1,120) / 2, channel: 1})
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
- <ic>program_change(program: number, channel: number)</ic>: send a MIDI Program Change. This function takes two arguments to specify the program and the channel (_e.g._ <ic>program_change(1, 1)</ic>).
|
||||
|
||||
${makeExample(
|
||||
"Crashing old synthesizers: a hobby",
|
||||
`
|
||||
"Crashing old synthesizers: a hobby",
|
||||
`
|
||||
program_change([1,2,3,4,5,6,7,8].pick(), 1)
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
|
||||
## System Exclusive Messages
|
||||
@ -119,44 +119,44 @@ program_change([1,2,3,4,5,6,7,8].pick(), 1)
|
||||
|
||||
|
||||
${makeExample(
|
||||
"Nobody can say that we don't support Sysex messages!",
|
||||
`
|
||||
"Nobody can say that we don't support Sysex messages!",
|
||||
`
|
||||
sysex(0x90, 0x40, 0x7f)
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
## Clock
|
||||
|
||||
- <ic>midi_clock()</ic>: send a MIDI Clock message. This function is used to synchronize Topos with other MIDI devices or DAWs.
|
||||
|
||||
${makeExample(
|
||||
"Tic, tac, tic, tac...",
|
||||
`
|
||||
"Tic, tac, tic, tac...",
|
||||
`
|
||||
beat(.25) && midi_clock() // Sending clock to MIDI device from the global buffer
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
## Using midi with ziffers
|
||||
|
||||
Ziffers offers some shorthands for defining channels within the patterns. See Ziffers for more information.
|
||||
|
||||
${makeExample(
|
||||
"Using midi with ziffers",
|
||||
`
|
||||
"Using midi with ziffers",
|
||||
`
|
||||
z1('0 2 e 5 2 q 4 2').midi().port(2).channel(4).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Setting the channel within the pattern",
|
||||
`
|
||||
"Setting the channel within the pattern",
|
||||
`
|
||||
z1('(0 2 e 5 2):0 (4 2):1').midi().out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
`;
|
||||
};
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { type Editor } from "../main";
|
||||
import { key_shortcut, makeExampleFactory } from "../Documentation";
|
||||
import { type Editor } from "../../main";
|
||||
import { key_shortcut, makeExampleFactory } from "../../Documentation";
|
||||
|
||||
export const bonus = (application: Editor): string => {
|
||||
const makeExample = makeExampleFactory(application);
|
||||
@ -1,5 +1,5 @@
|
||||
import { type Editor } from "../main";
|
||||
import { makeExampleFactory } from "../Documentation";
|
||||
import { type Editor } from "../../main";
|
||||
import { makeExampleFactory } from "../../Documentation";
|
||||
|
||||
export const oscilloscope = (application: Editor): string => {
|
||||
const makeExample = makeExampleFactory(application);
|
||||
@ -8,8 +8,8 @@ export const oscilloscope = (application: Editor): string => {
|
||||
You can turn on the oscilloscope to generate interesting visuals or to inspect audio. Use the <ic>scope()</ic> function to turn it on and off. The oscilloscope is off by default.
|
||||
|
||||
${makeExample(
|
||||
"Oscilloscope configuration",
|
||||
`
|
||||
"Oscilloscope configuration",
|
||||
`
|
||||
scope({
|
||||
enabled: true, // off by default
|
||||
color: "#fdba74", // any valid CSS color or "random"
|
||||
@ -23,12 +23,12 @@ scope({
|
||||
refresh: 1 // refresh rate (in pulses)
|
||||
})
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Demo with multiple scope mode",
|
||||
`
|
||||
"Demo with multiple scope mode",
|
||||
`
|
||||
rhythm(.5, [4,5].dur(4*3, 4*1), 8)::sound('fhardkick').out()
|
||||
beat(0.25)::sound('square').freq([
|
||||
[250, 250/2, 250/4].pick(),
|
||||
@ -44,8 +44,8 @@ scope({enabled: true, thickness: 8,
|
||||
color: ['purple', 'green', 'random'].beat(),
|
||||
size: 0.5, fftSize: 2048})
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
Note that these values can be patterned as well! You can transform the oscilloscope into its own light show if you want. The picture is not stable anyway so you won't have much use of it for precision work :)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { type Editor } from "../main";
|
||||
import { makeExampleFactory } from "../Documentation";
|
||||
import { type Editor } from "../../main";
|
||||
import { makeExampleFactory } from "../../Documentation";
|
||||
|
||||
export const synchronisation = (app: Editor): string => {
|
||||
// @ts-ignore
|
||||
@ -113,7 +113,7 @@ ${makeExample(
|
||||
beat(0.5)::sound('notes').n([1,2].dur(1, 2))
|
||||
.room(0.5).size(8).delay(0.125).delayt(1/8)
|
||||
.speed(0.5).ad(0, .125).out()
|
||||
// Kick (3 beats), Snare (1 beat)
|
||||
// Kick (3 beats), Snare (1bpm beat)
|
||||
beat(1)::sound(['kick', 'fsnare'].dur(3, 1))
|
||||
.n([0,3].dur(3, 1)).out()
|
||||
`,
|
||||
|
||||
@ -9,8 +9,8 @@ export const probabilities = (application: Editor): string => {
|
||||
|
||||
There are some simple functions to play with probabilities.
|
||||
|
||||
- <ic>rand(min: number, max:number)</ic>: returns a random number between <ic>min</ic> and <ic>max</ic>. Shorthand _r()_.
|
||||
- <ic>irand(min: number, max:number)</ic>: returns a random integer between <ic>min</ic> and <ic>max</ic>. Shorthands _ir()_ or _rI()_.
|
||||
- <ic>rand(min: number, max:number)</ic>: returns a random number between <ic>min</ic> and <ic>max</ic>. Shorthand <ic>r()</ic>.
|
||||
- <ic>irand(min: number, max:number)</ic>: returns a random integer between <ic>min</ic> and <ic>max</ic>. Shorthands <ic>ir()</ic> or <ic>rI()</ic>.
|
||||
|
||||
${makeExample(
|
||||
"Bleep bloop, what were you expecting?",
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
export const reference = (): string => {
|
||||
return ``;
|
||||
};
|
||||
@ -1,98 +0,0 @@
|
||||
import { type Editor } from "../main";
|
||||
import { makeExampleFactory } from "../Documentation";
|
||||
|
||||
export const samples_to_markdown = (application: Editor) => {
|
||||
let samples = application.api._all_samples();
|
||||
let markdownList = "";
|
||||
let keys = Object.keys(samples);
|
||||
let i = -1;
|
||||
while (i++ < keys.length - 1) {
|
||||
//@ts-ignore
|
||||
if (!samples[keys[i]].data) continue;
|
||||
//@ts-ignore
|
||||
if (!samples[keys[i]].data.samples) continue;
|
||||
//markdownList += `**${keys[i]}** (_${
|
||||
// //@ts-ignore
|
||||
// samples[keys[i]].data.samples.length
|
||||
//}_) `;
|
||||
//
|
||||
|
||||
// Adding new examples for each sample folder!
|
||||
const codeId = `sampleExample${i}`;
|
||||
application.api.codeExamples[
|
||||
codeId
|
||||
] = `sound("${keys[i]}").n(irand(1, 5)).end(1).out()`;
|
||||
// @ts-ignore
|
||||
const howMany = samples[keys[i]].data.samples.length;
|
||||
|
||||
markdownList += `
|
||||
<button
|
||||
class="hover:bg-neutral-500 inline px-4 py-2 bg-neutral-700 text-orange-300 text-xl"
|
||||
onclick="app.api._playDocExampleOnce(app.api.codeExamples['${codeId}'])"
|
||||
>
|
||||
${keys[i]}
|
||||
<b class="text-white">(${howMany})</b>
|
||||
</button>`;
|
||||
}
|
||||
return markdownList;
|
||||
};
|
||||
|
||||
export const injectAvailableSamples = (application: Editor): string => {
|
||||
let generatedPage = samples_to_markdown(application);
|
||||
return generatedPage;
|
||||
};
|
||||
|
||||
export const samples = (application: Editor): string => {
|
||||
const makeExample = makeExampleFactory(application);
|
||||
return `
|
||||
# Audio Samples
|
||||
|
||||
Audio samples are dynamically loaded from the web. By default, Topos is providing some samples coming from the classic [Dirt-Samples](https://github.com/tidalcycles/Dirt-Samples) but also from the [Topos-Samples](https://github.com/Bubobubobubobubo/Topos-Samples) repository. You can contribute to the latter if you want to share your samples with the community! For each sample folder, we are indicating how many of them are available in parentheses. The samples starting with <ic>ST</ic> are coming from [a wonderful collection](https://archive.org/details/AmigaSoundtrackerSamplePacksst-xx) of Ultimate Tracker Amiga audio samples released by Karsten Obarski. They are very high-pitched as was usual in the tracker era. Pitch them down using <ic>.speed(0.5)</ic>.
|
||||
|
||||
## Available audio samples
|
||||
|
||||
<b class="flex lg:pl-6 lg:pr-6 text-bold mb-8">Samples can take a few seconds to load. Please wait if you are not hearing anything. Lower your volume, take it slow. Some sounds might be harsh.</b>
|
||||
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
||||
${injectAvailableSamples(application)}
|
||||
</div>
|
||||
|
||||
# Loading custom samples
|
||||
|
||||
Topos is exposing the <ic>samples</ic> function that you can use to load your own set of samples. Samples are loaded on-the-fly from the web. Topos is a web application living in the browser. It is running in a sandboxed environment. Thus, it cannot have access to the files stored on your local system. Loading samples requires building a _map_ of the audio files, where a name is associated to a specific file:
|
||||
|
||||
${makeExample(
|
||||
"Loading samples from a map",
|
||||
`samples({
|
||||
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav'],
|
||||
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
|
||||
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');`,
|
||||
true
|
||||
)}
|
||||
|
||||
This example is loading two samples from each folder declared in the original repository (in the <ic>strudel.json</ic> file). You can then play with them using the syntax you are already used to:
|
||||
|
||||
${makeExample(
|
||||
"Playing with the loaded samples",
|
||||
`rhythm(.5, 5, 8)::sound('bd').n(ir(1,2)).end(1).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
Internally, Topos is loading samples using a different technique where sample maps are directly taken from the previously mentioned <ic>strudel.json</ic> file that lives in each repository:
|
||||
|
||||
${makeExample(
|
||||
"This is how Topos is loading its own samples",
|
||||
`
|
||||
// Visit the concerned repos and search for 'strudel.json'
|
||||
samples("github:tidalcycles/Dirt-Samples/master");
|
||||
samples("github:Bubobubobubobubo/Dough-Samples/main");
|
||||
samples("github:Bubobubobubobubo/Dough-Amiga/main");
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
To learn more about the audio sample loading mechanism, please refer to [this page](https://strudel.tidalcycles.org/learn/samples) written by Felix Roos who has implemented the sample loading mechanism. The API is absolutely identic in Topos!
|
||||
|
||||
`;
|
||||
};
|
||||
65
src/documentation/samples/loading_samples.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { type Editor } from "../../main";
|
||||
import { makeExampleFactory } from "../../Documentation";
|
||||
|
||||
export const loading_samples = (application: Editor): string => {
|
||||
// @ts-ignore
|
||||
const makeExample = makeExampleFactory(application);
|
||||
return `# Loading custom samples
|
||||
|
||||
|
||||
Topos is exposing the <ic>samples</ic> function that you can use to load your own set of samples.
|
||||
|
||||
Samples are loaded on-the-fly from the web. Topos is a web application living in the browser. It is running in a sandboxed environment. Thus, it cannot have access to the files stored on your local system. Loading samples requires building a _map_ of the audio files, where a name is associated to a specific file:
|
||||
|
||||
${makeExample(
|
||||
"Loading samples from a map",
|
||||
`samples({
|
||||
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav'],
|
||||
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
|
||||
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');`,
|
||||
true
|
||||
)}
|
||||
|
||||
This example is loading two samples from each folder declared in the original repository (in the <ic>strudel.json</ic> file). You can then play with them using the syntax you are already used to:
|
||||
|
||||
${makeExample(
|
||||
"Playing with the loaded samples",
|
||||
`rhythm(.5, 5, 8)::sound('bd').n(ir(1,2)).end(1).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
Internally, Topos is loading samples using a different technique where sample maps are directly taken from the previously mentioned <ic>strudel.json</ic> file that lives in each repository:
|
||||
|
||||
${makeExample(
|
||||
"This is how Topos is loading its own samples",
|
||||
`
|
||||
// Visit the concerned repos and search for 'strudel.json'
|
||||
samples("github:tidalcycles/Dirt-Samples/master");
|
||||
samples("github:Bubobubobubobubo/Dough-Samples/main");
|
||||
samples("github:Bubobubobubobubo/Dough-Amiga/main");
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
To learn more about the audio sample loading mechanism, please refer to [this page](https://strudel.tidalcycles.org/learn/samples) written by Felix Roos who has implemented the sample loading mechanism. The API is absolutely identic in Topos!
|
||||
|
||||
# Loading sounds using Shabda
|
||||
|
||||
You can load samples coming from [Freesound](https://freesound.org/) using the [Shabda](https://shabda.ndre.gr/) API. To do so, study the following example:
|
||||
|
||||
${makeExample(
|
||||
"Loading samples from shabda",
|
||||
`
|
||||
// Prepend the sample you want with 'shabda:'
|
||||
samples("shabda:ocean")
|
||||
|
||||
// Use the sound without 'shabda:'
|
||||
beat(1)::sound('ocean').clip(1).out()
|
||||
`, true
|
||||
)}
|
||||
|
||||
You can also use the <ic>.n</ic> attribute like usual to load a different sample.
|
||||
`
|
||||
}
|
||||
13
src/documentation/samples/sample_banks.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { type Editor } from "../../main";
|
||||
import { makeExampleFactory } from "../../Documentation";
|
||||
|
||||
export const sample_banks = (application: Editor): string => {
|
||||
// @ts-ignore
|
||||
const makeExample = makeExampleFactory(application);
|
||||
return `# Sample Banks
|
||||
|
||||
There is a <ic>bank</ic> attribute that can help you to sort audio samples from large collections.
|
||||
|
||||
**AJKPercusyn**, **AkaiLinn**, **AkaiMPC60**, **AkaiXR10**, **AlesisHR16**, **AlesisSR16**, **BossDR110**, **BossDR220**, **BossDR55**, **BossDR550**, **BossDR660**, **CasioRZ1**, **CasioSK1**, **CasioVL1**, **DoepferMS404**, **EmuDrumulator**, **EmuModular**, **EmuSP12**, **KorgDDM110**, **KorgKPR77**, **KorgKR55**, **KorgKRZ**, **KorgM1**, **KorgMinipops**, **KorgPoly800**, **KorgT3**, **Linn9000**, **LinnDrum**, **LinnLM1**, **LinnLM2**, **MFB512**, **MPC1000**, **MoogConcertMateMG1**, **OberheimDMX**, **RhodesPolaris**, **RhythmAce**, **RolandCompurhythm1000**, **RolandCompurhythm78**, **RolandCompurhythm8000**, **RolandD110**, **RolandD70**, **RolandDDR30**, **RolandJD990**, **RolandMC202**, **RolandMC303**, **RolandMT32**, **RolandR8**, **RolandS50**, **RolandSH09**, **RolandSystem100**, **RolandTR505**, **RolandTR606**, **RolandTR626**, **RolandTR707**, **RolandTR727**, **RolandTR808**, **RolandTR909**, **SakataDPM48**, **SequentialCircuitsDrumtracks**, **SequentialCircuitsTom**, **SergeModular**, **SimmonsSDS400**, **SimmonsSDS5**, **SoundmastersR88**, **UnivoxMicroRhythmer12**, **ViscoSpaceDrum**, **XdrumLM8953**, **YamahaRM50**, **YamahaRX21**, **YamahaRX5**, **YamahaRY30**, **YamahaTG33**.
|
||||
`
|
||||
}
|
||||
134
src/documentation/samples/sample_list.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import { type Editor } from "../../main";
|
||||
import { makeExampleFactory } from "../../Documentation";
|
||||
|
||||
export const samples_to_markdown = (application: Editor, tag_filter?: string) => {
|
||||
let samples = application.api._all_samples();
|
||||
let markdownList = "";
|
||||
let keys = Object.keys(samples);
|
||||
let i = -1;
|
||||
while (i++ < keys.length - 1) {
|
||||
//@ts-ignore
|
||||
if (!samples[keys[i]].data) continue;
|
||||
//@ts-ignore
|
||||
if (!samples[keys[i]].data.samples) continue;
|
||||
// @ts-ignore
|
||||
if (samples[keys[i]].data.tag !== tag_filter) continue;
|
||||
//markdownList += `**${keys[i]}** (_${
|
||||
// //@ts-ignore
|
||||
// samples[keys[i]].data.samples.length
|
||||
//}_) `;
|
||||
//
|
||||
|
||||
// Adding new examples for each sample folder!
|
||||
const codeId = `sampleExample${i}`;
|
||||
application.api.codeExamples[
|
||||
codeId
|
||||
] = `sound("${keys[i]}").n(irand(1, 5)).end(1).out()`;
|
||||
// @ts-ignore
|
||||
const howMany = samples[keys[i]].data.samples.length;
|
||||
|
||||
markdownList += `
|
||||
<button
|
||||
class="hover:bg-neutral-500 inline px-4 py-2 bg-neutral-700 text-orange-300 text-xl"
|
||||
onclick="app.api._playDocExampleOnce(app.api.codeExamples['${codeId}'])"
|
||||
>
|
||||
${keys[i]}
|
||||
<b class="text-white">(${howMany})</b>
|
||||
</button>`;
|
||||
}
|
||||
return markdownList;
|
||||
};
|
||||
|
||||
export const injectAllSamples = (application: Editor): string => {
|
||||
let generatedPage = samples_to_markdown(application, "Topos");
|
||||
return generatedPage;
|
||||
};
|
||||
|
||||
|
||||
export const injectDrumMachineSamples = (application: Editor): string => {
|
||||
let generatedPage = samples_to_markdown(application, "Machines");
|
||||
return generatedPage;
|
||||
};
|
||||
|
||||
|
||||
export const sample_list = (application: Editor): string => {
|
||||
// @ts-ignore
|
||||
const makeExample = makeExampleFactory(application);
|
||||
return `
|
||||
# Available audio samples
|
||||
|
||||
On this page, you will find an exhaustive list of all the samples currently loaded by default by the system. Samples are sorted by **sample packs**. I am gradually adding more of them.
|
||||
|
||||
## Waveforms
|
||||
|
||||
A very large collection of wavetables for wavetable synthesis. This collection has been released by Kristoffer Ekstrand: [AKWF Waveforms](https://www.adventurekid.se/akrt/waveforms/adventure-kid-waveforms/). Every sound sample that starts with <ic>wt_</ic> will be looped. Look at this demo:
|
||||
|
||||
${makeExample("Wavetable synthesis made easy :)", `
|
||||
beat(0.5)::sound('wt_stereo').n([0, 1].pick()).ad(0, .25).out()
|
||||
`, true)}
|
||||
|
||||
|
||||
Pick one folder and spend some time exploring it. There is a lot of different waveforms.
|
||||
|
||||
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
||||
${samples_to_markdown(application, "Waveforms")}
|
||||
</div>
|
||||
|
||||
## Drum machines sample pack
|
||||
|
||||
A set of 72 classic drum machines created by **Geikha**: [Geikha Drum Machines](https://github.com/geikha/tidal-drum-machines). To use them efficiently, it is best to use the <ic>.bank()</ic> parameter like so:
|
||||
|
||||
${makeExample(
|
||||
"Using a classic drum machine", `
|
||||
beat(0.5)::sound(['bd', 'cp'].pick()).bank("AkaiLinn").out()
|
||||
`, true)}
|
||||
|
||||
Here is the complete list of available machines:
|
||||
|
||||
|
||||
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
||||
${samples_to_markdown(application, "Machines")}
|
||||
</div>
|
||||
|
||||
## FoxDot sample pack
|
||||
|
||||
The default sample pack used by Ryan Kirkbride's [FoxDot](https://github.com/Qirky/FoxDot). It is a nice curated sample pack that covers all the basic sounds you could want.
|
||||
|
||||
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
||||
${samples_to_markdown(application, "FoxDot")}
|
||||
</div>
|
||||
|
||||
## Amiga sample pack
|
||||
|
||||
This set of audio samples is taken from [this wonderful collection](https://archive.org/details/AmigaSoundtrackerSamplePacksst-xx) of **Ultimate Tracker Amiga samples**. They were initially made by Karsten Obarski. These files were processed: pitched down one octave, gain down 6db. The audio has been processed with [SoX](https://github.com/chirlu/sox). The script used to do so is also included in this repository.
|
||||
|
||||
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
||||
${samples_to_markdown(application, "Amiga")}
|
||||
</div>
|
||||
|
||||
## Amen break sample pack
|
||||
|
||||
A collection of many different amen breaks. Use <ic>.stretch()</ic> to play with these:
|
||||
|
||||
${makeExample(
|
||||
"Stretching an amen break", `
|
||||
beat(4)::sound('amen1').stretch(4).out()
|
||||
`, true,
|
||||
)}
|
||||
|
||||
The stretch should be adapted based on the length of each amen break.
|
||||
|
||||
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
||||
${samples_to_markdown(application, "Amen")}
|
||||
</div>
|
||||
|
||||
## TidalCycles sample library
|
||||
|
||||
Many live coders are expecting to find the Tidal sample library wherever they go, so here it is :)
|
||||
|
||||
|
||||
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
||||
${samples_to_markdown(application, "Tidal")}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@ -78,7 +78,7 @@ You can also add some amount of vibrato to the sound using the <ic>vib</ic> and
|
||||
${makeExample(
|
||||
"Different vibrato settings",
|
||||
`
|
||||
bpm(140);
|
||||
tempo(140);
|
||||
beat(1) :: sound('triangle')
|
||||
.freq(400).release(0.2)
|
||||
.vib([1/2, 1, 2, 4].beat())
|
||||
@ -94,7 +94,7 @@ A certain amount of brown noise can be added by using the <ic>.noise</ic> key:
|
||||
${makeExample(
|
||||
"Different vibrato settings",
|
||||
`
|
||||
bpm(140);
|
||||
tempo(140);
|
||||
beat(1) :: sound('triangle')
|
||||
.freq(400).release(0.2)
|
||||
.noise([0.2,0.4,0.5].bar())
|
||||
@ -609,7 +609,7 @@ ${makeExample(
|
||||
${makeExample(
|
||||
"Live coded poetry with array and string chaining",
|
||||
`
|
||||
bpm(70)
|
||||
tempo(70)
|
||||
|
||||
const croissant = [
|
||||
"Volant", "Arc-en-ciel", "Chocolat", "Dansant",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { type Editor } from "../main";
|
||||
import { makeExampleFactory } from "../Documentation";
|
||||
import { type Editor } from "../../main";
|
||||
import { makeExampleFactory } from "../../Documentation";
|
||||
|
||||
export const cyclical_time = (app: Editor): string => {
|
||||
// @ts-ignore
|
||||
@ -16,17 +16,17 @@ Time as a cycle. A cycle can be quite long (a few bars) or very short (a few pul
|
||||
- <ic>offset</ic>: offset (in beats) to apply. An offset of <ic>0.5</ic> will return true against the beat.
|
||||
|
||||
${makeExample(
|
||||
"Using different mod values",
|
||||
`
|
||||
"Using different mod values",
|
||||
`
|
||||
// This code is alternating between different mod values
|
||||
beat([1,1/2,1/4,1/8].beat(2)) :: sound('hat').n(0).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Some sort of ringtone",
|
||||
`
|
||||
"Some sort of ringtone",
|
||||
`
|
||||
// Blip generator :)
|
||||
let blip = (freq) => {
|
||||
return sound('wt_piano')
|
||||
@ -41,16 +41,16 @@ beat(1/3) :: blip(400).pan(r(0,1)).out();
|
||||
flip(3) :: beat(1/6) :: blip(800).pan(r(0,1)).out();
|
||||
beat([1,0.75].beat(2)) :: blip([50, 100].beat(2)).pan(r(0,1)).out();
|
||||
`,
|
||||
false
|
||||
)}
|
||||
false
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Beat can match multiple values",
|
||||
`
|
||||
"Beat can match multiple values",
|
||||
`
|
||||
beat([.5, 1.25])::sound('hat').out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
false
|
||||
)}
|
||||
|
||||
- <ic>pulse(n: number | number[] = 1, offset: number = 1)</ic>: return true every _n_ pulses. A pulse is the tiniest possible rhythmic value.
|
||||
- <ic>number</ic>: if <ic>number = 1</ic>, the function will return <ic>true</ic> every pulse. Lists can be used too.
|
||||
@ -58,21 +58,21 @@ beat([.5, 1.25])::sound('hat').out()
|
||||
|
||||
|
||||
${makeExample(
|
||||
"Intriguing rhythms",
|
||||
`
|
||||
"Intriguing rhythms",
|
||||
`
|
||||
pulse([24, 16])::sound('hat').ad(0, .02).out()
|
||||
pulse([48, [36,24].dur(4, 1)])::sound('fhardkick').ad(0, .1).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
${makeExample(
|
||||
"pulse is the OG rhythmic function in Topos",
|
||||
`
|
||||
"pulse is the OG rhythmic function in Topos",
|
||||
`
|
||||
pulse([48, 24, 16].beat(4)) :: sound('linnhats').out()
|
||||
beat(1)::snd(['bd', '808oh'].beat(1)).out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
false
|
||||
)}
|
||||
|
||||
|
||||
- <ic>bar(n: number | number[] = 1, offset: number = 1)</ic>: return true every _n_ bars.
|
||||
@ -80,37 +80,37 @@ beat(1)::snd(['bd', '808oh'].beat(1)).out()
|
||||
- <ic>offset</ic>: offset (in bars) to apply.
|
||||
|
||||
${makeExample(
|
||||
"Four beats per bar: proof",
|
||||
`
|
||||
"Four beats per bar: proof",
|
||||
`
|
||||
bar(1)::sound('kick').out()
|
||||
beat(1)::sound('hat').speed(2).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
|
||||
${makeExample(
|
||||
"Offsetting beat and bar",
|
||||
`
|
||||
"Offsetting beat and bar",
|
||||
`
|
||||
bar(1)::sound('kick').out()
|
||||
beat(1)::sound('hat').speed(2).out()
|
||||
beat(1, 0.5)::sound('hat').speed(4).out()
|
||||
bar(1, 0.5)::sound('sn').out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
false
|
||||
)}
|
||||
|
||||
- <ic>onbeat(...n: number[])</ic>: The <ic>onbeat</ic> function allows you to lock on to a specific beat from the clock to execute code. It can accept multiple arguments. It's usage is very straightforward and not hard to understand. You can pass either integers or floating point numbers. By default, topos is using a <ic>4/4</ic> bar meaning that you can target any of these beats (or in-between) with this function.
|
||||
|
||||
${makeExample(
|
||||
"Some simple yet detailed rhythms",
|
||||
`
|
||||
"Some simple yet detailed rhythms",
|
||||
`
|
||||
onbeat(1,2,3,4)::snd('kick').out() // Bassdrum on each beat
|
||||
onbeat(2,4)::snd('snare').n([8,4].beat(4)).out() // Snare on acccentuated beats
|
||||
onbeat(1.5,2.5,3.5, 3.75)::snd('hat').gain(r(0.9,1.1)).out() // Cool high-hats
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
## XOX Style sequencers
|
||||
|
||||
@ -119,32 +119,32 @@ onbeat(1.5,2.5,3.5, 3.75)::snd('hat').gain(r(0.9,1.1)).out() // Cool high-hats
|
||||
- <ic>duration: number</ic>: an optional duration (in beats) like <ic>1</ic> or </ic>4</ic>. It can be patterned.
|
||||
|
||||
${makeExample(
|
||||
"Sequence built using a classic XOX sequencer style",
|
||||
`
|
||||
"Sequence built using a classic XOX sequencer style",
|
||||
`
|
||||
seq('xoxo')::sound('fhardkick').out()
|
||||
seq('ooxo')::sound('fsoftsnare').out()
|
||||
seq('xoxo', 0.25)::sound('fhh').out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Another sequence using more complex parameters",
|
||||
`
|
||||
"Another sequence using more complex parameters",
|
||||
`
|
||||
seq('xoxooxxoo', [0.5, 0.25].dur(2, 1))::sound('fhardkick').out()
|
||||
seq('ooxo', [1, 2].bar())::sound('fsoftsnare').speed(0.5).out()
|
||||
seq(['xoxoxoxx', 'xxoo'].bar())::sound('fhh').out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
- <ic>fullseq(expr: string, duration: number = 0.5): boolean</ic> : a variant. Will return <ic>true</ic> or <ic>false</ic> for a whole period, depending on the symbol. Useful for long structure patterns.
|
||||
- <ic>expr: string</ic>: any string composed of <ic>x</ic> or <ic>o</ic> like so: <ic>"xooxoxxoxoo"</ic>.
|
||||
- <ic>duration: number</ic>: an optional duration (in beats) like <ic>1</ic> or </ic>4</ic>. It can be patterned.
|
||||
|
||||
${makeExample(
|
||||
"Long structured patterns",
|
||||
`
|
||||
"Long structured patterns",
|
||||
`
|
||||
function simplePat() {
|
||||
log('Simple pattern playing!')
|
||||
seq('xoxooxxoo', [0.5, 0.25].dur(2, 1))::sound('fhardkick').out()
|
||||
@ -159,8 +159,8 @@ function complexPat() {
|
||||
}
|
||||
fullseq('xooxooxx', 4) ? simplePat() : complexPat()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
|
||||
|
||||
@ -171,8 +171,8 @@ We included a bunch of popular rhythm generators in Topos such as the euclidian
|
||||
|
||||
- <ic>rhythm(divisor: number, pulses: number, length: number, rotate: number): boolean</ic>: generates <ic>true</ic> or <ic>false</ic> values from an euclidian rhythm sequence. This is another version of <ic>euclid</ic> that does not take an iterator.
|
||||
${makeExample(
|
||||
"rhythm is a beginner friendly rhythmic function!",
|
||||
`
|
||||
"rhythm is a beginner friendly rhythmic function!",
|
||||
`
|
||||
rhythm(.5, 4, 8)::sound('sine')
|
||||
.fmi(2)
|
||||
.room(0.5).size(8)
|
||||
@ -181,38 +181,38 @@ rhythm(.5, 7, 8)::sound('sine')
|
||||
.freq(125).ad(0, .2).out()
|
||||
rhythm(.5, 3, 8)::sound('sine').freq(500).ad(0, .5).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
|
||||
- <ic>oneuclid(pulses: number, length: number, rotate: number): boolean</ic>: generates <ic>true</ic> or <ic>false</ic> values from an euclidian rhythm sequence. This is another version of <ic>euclid</ic> that does not take an iterator.
|
||||
${makeExample(
|
||||
"Using oneuclid to create a rhythm without iterators",
|
||||
`
|
||||
"Using oneuclid to create a rhythm without iterators",
|
||||
`
|
||||
// Change speed using bpm
|
||||
bpm(250)
|
||||
oneuclid(5, 9) :: snd('kick').out()
|
||||
oneuclid(7,16) :: snd('east').end(0.5).n(irand(3,5)).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
- <ic>bin(iterator: number, n: number): boolean</ic>: a binary rhythm generator. It transforms the given number into its binary representation (_e.g_ <ic>34</ic> becomes <ic>100010</ic>). It then returns a boolean value based on the iterator in order to generate a rhythm.
|
||||
- <ic>binrhythm(divisor: number, n: number): boolean: boolean</ic>: iterator-less version of the binary rhythm generator.
|
||||
|
||||
${makeExample(
|
||||
"Change the integers for a surprise rhythm!",
|
||||
`
|
||||
"Change the integers for a surprise rhythm!",
|
||||
`
|
||||
bpm(135);
|
||||
beat(.5) && bin($(1), 12) && snd('kick').n([4,9].beat(1.5)).out()
|
||||
beat(.5) && bin($(2), 34) && snd('snare').n([3,5].beat(1)).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"binrhythm for fast cool binary rhythms!",
|
||||
`
|
||||
"binrhythm for fast cool binary rhythms!",
|
||||
`
|
||||
let a = 0;
|
||||
a = beat(4) ? irand(1,20) : a;
|
||||
binrhythm(.5, 6) && snd(['kick', 'snare'].beat(0.5)).n(11).out()
|
||||
@ -221,34 +221,34 @@ binrhythm([.5, .25].beat(1), 30) && snd('wt_granular').n(a)
|
||||
.adsr(0, r(.1, .4), 0, 0).freq([50, 60, 72].beat(4))
|
||||
.room(1).size(1).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Submarine jungle music",
|
||||
`
|
||||
"Submarine jungle music",
|
||||
`
|
||||
bpm(145);
|
||||
beat(.5) && bin($(1), 911) && snd('ST69').n([2,3,4].beat())
|
||||
.delay(0.125).delayt(0.25).end(0.25).speed(1/3)
|
||||
.room(1).size(1).out()
|
||||
beat(.5) && sound('amencutup').n(irand(2,7)).shape(0.3).out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
false
|
||||
)}
|
||||
|
||||
If you don't find it spicy enough, you can add some more probabilities to your rhythms by taking advantage of the probability functions. See the functions documentation page to learn more about them.
|
||||
|
||||
${makeExample(
|
||||
"Probablistic drums in one line!",
|
||||
`
|
||||
"Probablistic drums in one line!",
|
||||
`
|
||||
prob(60)::beat(.5) && euclid($(1), 5, 8) && snd('kick').out()
|
||||
prob(60)::beat(.5) && euclid($(2), 3, 8) && snd('mash')
|
||||
.n([1,2,3].beat(1))
|
||||
.pan(usine(1/4)).out()
|
||||
prob(80)::beat(.5) && sound(['hh', 'hat'].pick()).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { type Editor } from "../main";
|
||||
import { makeExampleFactory } from "../Documentation";
|
||||
import { type Editor } from "../../main";
|
||||
import { makeExampleFactory } from "../../Documentation";
|
||||
import pulses from "./pulses.svg";
|
||||
|
||||
export const linear_time = (app: Editor): string => {
|
||||
@ -22,12 +22,12 @@ export const linear_time = (app: Editor): string => {
|
||||
There is a tiny widget at the bottom right of the screen showing you the current BPM and the status of the transport. You can turn it on or off in the settings menu.
|
||||
|
||||
${makeExample(
|
||||
"Printing the transport",
|
||||
`
|
||||
"Printing the transport",
|
||||
`
|
||||
log(\`\$\{cbar()}\, \$\{cbeat()\}, \$\{cpulse()\}\`)
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
### BPM and PPQN
|
||||
|
||||
@ -64,8 +64,8 @@ These values are **extremely useful** to craft more complex syntax or to write m
|
||||
You can use time primitives as conditionals. The following example will play a pattern A for 2 bars and a pattern B for 2 bars:
|
||||
|
||||
${makeExample(
|
||||
"Manual mode: using time primitives!",
|
||||
`
|
||||
"Manual mode: using time primitives!",
|
||||
`
|
||||
// Manual time condition
|
||||
if((cbar() % 4) > 1) {
|
||||
beat(2) && sound('kick').out()
|
||||
@ -83,8 +83,8 @@ if((cbar() % 4) > 1) {
|
||||
// This is always playing no matter what happens
|
||||
beat([.5, .5, 1, .25].beat(0.5)) :: sound('shaker').out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
## Time Warping
|
||||
|
||||
@ -94,8 +94,8 @@ Time generally flows from the past to the future. However, you can manipulate it
|
||||
|
||||
|
||||
${makeExample(
|
||||
"Time is now super elastic!",
|
||||
`
|
||||
"Time is now super elastic!",
|
||||
`
|
||||
// Obscure Shenanigans - Bubobubobubo
|
||||
beat([1/4,1/8,1/16].beat(8)):: sound('sine')
|
||||
.freq([100,50].beat(16) + 50 * ($(1)%10))
|
||||
@ -108,14 +108,14 @@ flip(3) :: beat([.25,.5].beat(.5)) :: sound('dr')
|
||||
// Jumping back and forth in time
|
||||
beat(.25) :: warp([12, 48, 24, 1, 120, 30].pick())
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
- <ic>beat_warp(beat: number)</ic>: this function jumps to the _n_ beat of the clock. The first beat is <ic>1</ic>.
|
||||
|
||||
${makeExample(
|
||||
"Jumping back and forth with beats",
|
||||
`
|
||||
"Jumping back and forth with beats",
|
||||
`
|
||||
// Resonance bliss - Bubobubobubo
|
||||
beat(.25)::snd('arpy')
|
||||
.note(30 + [0,3,7,10].beat())
|
||||
@ -130,40 +130,40 @@ beat(.5) :: snd('arpy').note(
|
||||
// Comment me to stop warping!
|
||||
beat(1) :: beat_warp([2,4,5,10,11].pick())
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
## Transport-based rhythm generators
|
||||
|
||||
- <ic>onbeat(...n: number[])</ic>: The <ic>onbeat</ic> function allows you to lock on to a specific beat from the clock to execute code. It can accept multiple arguments. It's usage is very straightforward and not hard to understand. You can pass either integers or floating point numbers. By default, topos is using a <ic>4/4</ic> bar meaning that you can target any of these beats (or in-between) with this function.
|
||||
|
||||
${makeExample(
|
||||
"Some simple yet detailed rhythms",
|
||||
`
|
||||
"Some simple yet detailed rhythms",
|
||||
`
|
||||
onbeat(1,2,3,4)::snd('kick').out() // Bassdrum on each beat
|
||||
onbeat(2,4)::snd('snare').n([8,4].beat(4)).out() // Snare on acccentuated beats
|
||||
onbeat(1.5,2.5,3.5, 3.75)::snd('hat').gain(r(0.9,1.1)).out() // Cool high-hats
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Let's do something more complex",
|
||||
`
|
||||
"Let's do something more complex",
|
||||
`
|
||||
onbeat(0.5, 2, 3, 3.75)::snd('kick').n(2).out()
|
||||
onbeat(2, [1.5, 3, 4].pick(), 4)::snd('snare').n(8).out()
|
||||
beat([.25, 1/8].beat(1.5))::snd('hat').n(2)
|
||||
.gain(rand(0.4, 0.7)).end(0.05)
|
||||
.pan(usine()).out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
false
|
||||
)}
|
||||
|
||||
- <ic>oncount(beats: number[], meter: number)</ic>: This function is similar to <ic>onbeat</ic> but it allows you to specify a custom number of beats as the last argument.
|
||||
|
||||
${makeExample(
|
||||
"Using oncount to create more variation in the rhythm",
|
||||
`
|
||||
"Using oncount to create more variation in the rhythm",
|
||||
`
|
||||
z1('1/16 (0 2 3 4)+(0 2 4 6)').scale('pentatonic').sound('sawtooth')
|
||||
.cutoff([400,500,1000,2000].beat(1))
|
||||
.lpadsr(2, 0, .2, 0, 0)
|
||||
@ -171,20 +171,20 @@ z1('1/16 (0 2 3 4)+(0 2 4 6)').scale('pentatonic').sound('sawtooth')
|
||||
onbeat(1,1.5,2,3,4) :: sound('bd').gain(2.0).out()
|
||||
oncount([1,3,5.5,7,7.5,8],8) :: sound('hh').gain(irand(1.0,4.0)).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Using oncount to create rhythms with a custom meter",
|
||||
`
|
||||
"Using oncount to create rhythms with a custom meter",
|
||||
`
|
||||
bpm(200)
|
||||
oncount([1, 5, 9, 13],16) :: sound('808bd').n(4).shape(0.5).gain(1.0).out()
|
||||
oncount([5, 6, 13],16) :: sound('shaker').room(0.25).gain(0.9).out()
|
||||
oncount([2, 3, 3.5, 6, 7, 10, 15],16) :: sound('hh').n(8).gain(0.8).out()
|
||||
oncount([1, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16],16) :: sound('hh').out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
@ -1,5 +1,5 @@
|
||||
import { makeExampleFactory } from "../Documentation";
|
||||
import { type Editor } from "../main";
|
||||
import { makeExampleFactory } from "../../Documentation";
|
||||
import { type Editor } from "../../main";
|
||||
import times from "./times.svg";
|
||||
|
||||
export const time = (application: Editor): string => {
|
||||
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
@ -159,8 +159,9 @@ export class Editor {
|
||||
let pre_loading = async () => {
|
||||
await loadSamples();
|
||||
};
|
||||
pre_loading();
|
||||
this.docs = documentation_factory(this);
|
||||
pre_loading().then(() => {
|
||||
this.docs = documentation_factory(this);
|
||||
});
|
||||
|
||||
// ================================================================================
|
||||
// Application event listeners
|
||||
@ -484,7 +485,7 @@ export class Editor {
|
||||
console.log("Hydra loaded successfully");
|
||||
this.initializeHydra();
|
||||
};
|
||||
script.onerror = function () {
|
||||
script.onerror = function() {
|
||||
console.error("Error loading Hydra script");
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
|
||||
3716
src/tidal-drum-machines.json
Normal file
@ -26,7 +26,9 @@ const webManifest = {
|
||||
const vitePWAconfiguration = {
|
||||
devOptions: {
|
||||
enabled: true,
|
||||
suppressWarnings: true,
|
||||
},
|
||||
|
||||
workbox: {
|
||||
sourcemap: false,
|
||||
cleanupOutdatedCaches: true,
|
||||
|
||||
@ -3319,10 +3319,10 @@ sucrase@^3.32.0:
|
||||
pirates "^4.0.1"
|
||||
ts-interface-checker "^0.1.9"
|
||||
|
||||
superdough@^0.9.10:
|
||||
version "0.9.10"
|
||||
resolved "https://registry.yarnpkg.com/superdough/-/superdough-0.9.10.tgz#9554964741c508b4c5d596fa8acbb2efea822250"
|
||||
integrity sha512-IGu0+fBXpSS4l4Q+4ATRhSFXnas7t2G6uc5Ry+keQ4G+nc6uK6twAP0YyBlSB4RUdGpZCIS1o0t8qJ7MaBg4gw==
|
||||
superdough@^0.9.11:
|
||||
version "0.9.11"
|
||||
resolved "https://registry.yarnpkg.com/superdough/-/superdough-0.9.11.tgz#3a3842a47d6340477f77d39077303f05e15274dd"
|
||||
integrity sha512-s0SNSg/EJHwp2sUnE2A7pTZ0G2luiSEq9NVKJvodjJw11Tn0fOp9XcnegNXINYz3U6mAsUYRoeaj4NmuTL13fA==
|
||||
dependencies:
|
||||
nanostores "^0.8.1"
|
||||
|
||||
|
||||