Compare commits
38 Commits
dependabot
...
fixes
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e60504de5 | |||
| fc0c7cc34c | |||
| bb6a52bd4b | |||
| 01235727c7 | |||
| b61ea25836 | |||
| 2b6e092d37 | |||
| 7f48b94ffa | |||
| 0d2f7046c9 | |||
| 1824a345fc | |||
| 605db460e5 | |||
| 0b09f16624 | |||
| 2304015975 | |||
| 9dfac1141f | |||
| 8c0555b02f | |||
| 4c7cfb44ce | |||
| d3ac9f19a2 | |||
| 75481e19f0 | |||
| 137840778a | |||
| 13cf95b71e | |||
| cee061a100 | |||
| 7100d5a470 | |||
| 75daa2cf6a | |||
| 73c0df333a | |||
| 2870cb124a | |||
| fcae478461 | |||
| e06119ba8c | |||
| 5548d30da8 | |||
| 4b6275d2e0 | |||
| 4801f78deb | |||
| 57f0c9dfe6 | |||
| b2a8e18b0e | |||
| 358195bf97 | |||
| 0a6d779867 | |||
| a21d6c9a88 | |||
| 7cef78bc3b | |||
| d395c9487a | |||
| b222fc25c9 | |||
| d2dee8f371 |
12
ToposServer/package-lock.json
generated
@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "topos-server",
|
"name": "topos-server",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "topos-server",
|
"name": "topos-server",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"osc": "^2.4.4",
|
"osc": "^2.4.4",
|
||||||
"ws": "^8.17.1"
|
"ws": "^8.14.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@serialport/binding-mock": {
|
"node_modules/@serialport/binding-mock": {
|
||||||
@ -309,9 +309,9 @@
|
|||||||
"integrity": "sha512-P+6vtWyuDw+MB01X7UeF8TaHBvbCovf4HPEMF/SV7BdDc1SMTiBy13SRD71lQh4ExFTG1d/WNzDGDCyOKSMblw=="
|
"integrity": "sha512-P+6vtWyuDw+MB01X7UeF8TaHBvbCovf4HPEMF/SV7BdDc1SMTiBy13SRD71lQh4ExFTG1d/WNzDGDCyOKSMblw=="
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.17.1",
|
"version": "8.14.2",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz",
|
||||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
"integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -10,6 +10,6 @@
|
|||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"osc": "^2.4.4",
|
"osc": "^2.4.4",
|
||||||
"ws": "^8.17.1"
|
"ws": "^8.14.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
global.d.ts
vendored
@ -1 +1,3 @@
|
|||||||
/// <reference types="vite-plugin-pwa/client" />
|
/// <reference types="vite-plugin-pwa/client" />
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
37
index.html
@ -32,26 +32,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hydracanvas {
|
.hydracanvas {
|
||||||
position: fixed; /* ignore margins */
|
position: fixed;
|
||||||
top: 0px;
|
top: 0px; left: 0px;
|
||||||
left: 0px;
|
width: 100%; height: 100%;
|
||||||
width: 100%; /* fill screen */
|
|
||||||
height: 100%;
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
z-index: -5; /* place behind everything else */
|
z-index: -5;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreencanvas {
|
.fullscreencanvas {
|
||||||
position: fixed; /* ignore margins */
|
position: fixed;
|
||||||
top: 0px;
|
top: 0px; left: 0px;
|
||||||
left: 0px;
|
width: 100%; height: 100%;
|
||||||
width: 100%; /* fill screen */
|
|
||||||
height: 100%;
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
z-index: -1; /* place behind everything else */
|
z-index: -1;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,12 +104,12 @@
|
|||||||
<header class="py-0 block">
|
<header class="py-0 block">
|
||||||
<div id="topbar" class="mx-auto flex flex-wrap pl-2 py-1 flex-row items-center bg-background">
|
<div id="topbar" class="mx-auto flex flex-wrap pl-2 py-1 flex-row items-center bg-background">
|
||||||
<a class="flex title-font font-medium items-center mb-0">
|
<a class="flex title-font font-medium items-center mb-0">
|
||||||
<img id="topos-logo" src="topos_frog.svg" class="w-12 h-12 text-selection_foreground p-2 rounded-full bg-foreground" alt="Topos Frog Logo"/>
|
<img id="topos_logo" src="topos_frog.svg" class="w-12 h-12 text-selection_foreground p-2 rounded-full bg-foreground" alt="Topos Frog Logo"/>
|
||||||
<input id="universe-viewer" class="hidden transparent xl:block ml-4 text-2xl bg-background text-brightwhite placeholder-brightwhite" id="renamer" type="text" placeholder="Topos">
|
<input id="universe-viewer" class="hidden transparent xl:block ml-4 text-2xl bg-background text-brightwhite placeholder-brightwhite" id="renamer" type="text" placeholder="Topos">
|
||||||
</a>
|
</a>
|
||||||
<nav class="py-2 flex flex-wrap items-center text-base absolute right-0">
|
<nav class="py-2 flex flex-wrap items-center text-base absolute right-0">
|
||||||
<!-- Play Button -->
|
<!-- Play Button -->
|
||||||
<a title="Play button (Ctrl+P)" id="play-button-1" class="bar_button">
|
<a title="Play button (Ctrl+P)" id="play-button" class="bar_button">
|
||||||
<svg id="play-icon" class="w-7 h-7" fill="currentColor" viewBox="0 0 14 16">
|
<svg id="play-icon" class="w-7 h-7" fill="currentColor" viewBox="0 0 14 16">
|
||||||
<path d="M0 .984v14.032a1 1 0 0 0 1.506.845l12.006-7.016a.974.974 0 0 0 0-1.69L1.506.139A1 1 0 0 0 0 .984Z"/>
|
<path d="M0 .984v14.032a1 1 0 0 0 1.506.845l12.006-7.016a.974.974 0 0 0 0-1.69L1.506.139A1 1 0 0 0 0 .984Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
@ -124,7 +120,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Stop button -->
|
<!-- Stop button -->
|
||||||
<a title="Stop button (Ctrl+R)" id="stop-button-1" class="bar_button">
|
<a title="Stop button (Ctrl+R)" id="stop-button" class="bar_button">
|
||||||
<svg class="w-7 h-7 " aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
<svg class="w-7 h-7 " aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Z"/>
|
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Z"/>
|
||||||
<rect x="6.5" y="6.5" width="7" height="7" fill="selection_background" rx="1" ry="1"/>
|
<rect x="6.5" y="6.5" width="7" height="7" fill="selection_background" rx="1" ry="1"/>
|
||||||
@ -140,13 +136,6 @@
|
|||||||
<p class="hidden lg:block text-xl pl-2 inline-block">Eval</p>
|
<p class="hidden lg:block text-xl pl-2 inline-block">Eval</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a title="Clear button" id="clear-button-1" class="bar_button">
|
|
||||||
<svg class="w-7 h-7 " aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 20">
|
|
||||||
<path d="M17 4h-4V2a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v2H1a1 1 0 0 0 0 2h1v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V6h1a1 1 0 1 0 0-2ZM7 2h4v2H7V2Zm1 14a1 1 0 1 1-2 0V8a1 1 0 0 1 2 0v8Zm4 0a1 1 0 0 1-2 0V8a1 1 0 0 1 2 0v8Z"/>
|
|
||||||
</svg>
|
|
||||||
<p class="hidden lg:block text-xl pl-2 inline-block">Clear</p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a title="Share button" id="share-button" class="bar_button">
|
<a title="Share button" id="share-button" class="bar_button">
|
||||||
<svg class="w-7 h-7 " aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 19 19">
|
<svg class="w-7 h-7 " aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 19 19">
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.013 7.962a3.519 3.519 0 0 0-4.975 0l-3.554 3.554a3.518 3.518 0 0 0 4.975 4.975l.461-.46m-.461-4.515a3.518 3.518 0 0 0 4.975 0l3.553-3.554a3.518 3.518 0 0 0-4.974-4.975L10.3 3.7"/>
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.013 7.962a3.519 3.519 0 0 0-4.975 0l-3.554 3.554a3.518 3.518 0 0 0 4.975 4.975l.461-.46m-.461-4.515a3.518 3.518 0 0 0 4.975 0l3.553-3.554a3.518 3.518 0 0 0-4.974-4.975L10.3 3.7"/>
|
||||||
@ -161,6 +150,7 @@
|
|||||||
<p class="hidden lg:block text-xl pl-2 inline-block">Docs</p>
|
<p class="hidden lg:block text-xl pl-2 inline-block">Docs</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<div id="transport_viewer" class="pr-2 text-selection_background"></div>
|
||||||
</nav>
|
</nav>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
@ -340,7 +330,6 @@
|
|||||||
<select id="theme-selector" class="ml-4 border mb-2
|
<select id="theme-selector" class="ml-4 border mb-2
|
||||||
text-sm rounded-lg block p-2.5">
|
text-sm rounded-lg block p-2.5">
|
||||||
</select>
|
</select>
|
||||||
<div id="theme-previewer"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Editor mode selection -->
|
<!-- Editor mode selection -->
|
||||||
@ -576,7 +565,6 @@
|
|||||||
<div id="editor" class="relative flex flex-row h-screen overflow-y-hidden">
|
<div id="editor" class="relative flex flex-row h-screen overflow-y-hidden">
|
||||||
<canvas id="scope" class="fullscreencanvas"></canvas>
|
<canvas id="scope" class="fullscreencanvas"></canvas>
|
||||||
<canvas id="feedback" class="fullscreencanvas"></canvas>
|
<canvas id="feedback" class="fullscreencanvas"></canvas>
|
||||||
<canvas id="drawings" class="fullscreencanvas"></canvas>
|
|
||||||
<canvas id="hydra-bg" class="hydracanvas"></canvas>
|
<canvas id="hydra-bg" class="hydracanvas"></canvas>
|
||||||
</div>
|
</div>
|
||||||
<p id="error_line" class="hidden w-screen bg-background font-mono absolute bottom-0 pl-2 py-2">Hello kids</p>
|
<p id="error_line" class="hidden w-screen bg-background font-mono absolute bottom-0 pl-2 py-2">Hello kids</p>
|
||||||
@ -592,6 +580,5 @@
|
|||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
</body>
|
</body>
|
||||||
<p id="timeviewer" class="rounded-lg px-2 py-2 font-bold cursor-textpointer-events-none select-none text-sm absolute bottom-2 right-2 bg-foreground text-background"></p>
|
|
||||||
<p id="fillviewer" class="invisible rounded-lg px-2 py-2 font-bold cursor-textpointer-events-none select-none text-sm absolute right-2 bottom-12 bg-foreground text-background">/////// Fill ///////</p>
|
<p id="fillviewer" class="invisible rounded-lg px-2 py-2 font-bold cursor-textpointer-events-none select-none text-sm absolute right-2 bottom-12 bg-foreground text-background">/////// Fill ///////</p>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -32,7 +32,7 @@
|
|||||||
"fflate": "^0.8.0",
|
"fflate": "^0.8.0",
|
||||||
"highlight.js": "^11.9.0",
|
"highlight.js": "^11.9.0",
|
||||||
"jisg": "^0.9.7",
|
"jisg": "^0.9.7",
|
||||||
"lru-cache": "^10.0.1",
|
"lru-cache": "^10.2.0",
|
||||||
"marked": "^7.0.3",
|
"marked": "^7.0.3",
|
||||||
"osc": "^2.4.4",
|
"osc": "^2.4.4",
|
||||||
"postcss": "^8.4.27",
|
"postcss": "^8.4.27",
|
||||||
|
|||||||
2678
src/API.ts
630
src/API/API.ts
Normal file
@ -0,0 +1,630 @@
|
|||||||
|
import * as Transport from './Time/Transport';
|
||||||
|
import * as Mouse from './DOM/Mouse';
|
||||||
|
import * as Theme from './DOM/Theme';
|
||||||
|
import * as Canvas from './DOM/Canvas';
|
||||||
|
import * as Cache from './Cache';
|
||||||
|
import * as Script from './Script';
|
||||||
|
import * as Drunk from './Drunk';
|
||||||
|
import * as Warp from './Time/Warp';
|
||||||
|
import * as Mathematics from './Math';
|
||||||
|
import * as Ziffers from './Ziffers';
|
||||||
|
import * as Filters from './Time/Filters';
|
||||||
|
import * as LFO from './LFO';
|
||||||
|
import * as Probability from './Probabilities';
|
||||||
|
import * as OSC from './IO/OSC';
|
||||||
|
import * as Randomness from './Randomness';
|
||||||
|
import * as Counter from './Counter';
|
||||||
|
import * as Sound from './Sound';
|
||||||
|
import * as Console from './DOM/Console';
|
||||||
|
import { type SoundEvent } from '../Classes/SoundEvent';
|
||||||
|
import { type SkipEvent } from '../Classes/SkipEvent';
|
||||||
|
import { OscilloscopeConfig } from "../DOM/Visuals/Oscilloscope";
|
||||||
|
import { Player } from "../Classes/ZPlayer";
|
||||||
|
import { InputOptions } from "../Classes/ZPlayer";
|
||||||
|
import { type ShapeObject } from "../API/DOM/Canvas";
|
||||||
|
import { nearScales } from "zifferjs";
|
||||||
|
import { MidiConnection } from "../IO/MidiConnection";
|
||||||
|
import { DrunkWalk } from "../Utils/Drunk";
|
||||||
|
import { Editor } from "../main";
|
||||||
|
import { LRUCache } from "lru-cache";
|
||||||
|
import {
|
||||||
|
loadUniverse,
|
||||||
|
openUniverseModal,
|
||||||
|
} from "../Editor/FileManagement";
|
||||||
|
import {
|
||||||
|
samples,
|
||||||
|
initAudioOnFirstClick,
|
||||||
|
registerSynthSounds,
|
||||||
|
registerZZFXSounds,
|
||||||
|
soundMap,
|
||||||
|
// @ts-ignore
|
||||||
|
} from "superdough";
|
||||||
|
import { getScaleNotes } from "zifferjs";
|
||||||
|
import drums from "../tidal-drum-machines.json";
|
||||||
|
import { updatePlayPauseIcon } from '../DOM/UILogic';
|
||||||
|
|
||||||
|
export async function loadSamples() {
|
||||||
|
return Promise.all([
|
||||||
|
initAudioOnFirstClick(),
|
||||||
|
samples("github:tidalcycles/Dirt-Samples/master", undefined, {
|
||||||
|
tag: "Tidal",
|
||||||
|
}).then(() => registerSynthSounds()),
|
||||||
|
registerZZFXSounds(),
|
||||||
|
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-Juj/main", undefined, {
|
||||||
|
tag: "Juliette",
|
||||||
|
}),
|
||||||
|
samples("github:Bubobubobubobubo/Dough-Amen/main", undefined, {
|
||||||
|
tag: "Amen",
|
||||||
|
}),
|
||||||
|
samples("github:Bubobubobubobubo/Dough-Waveforms/main", undefined, {
|
||||||
|
tag: "Waveforms",
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UserAPI {
|
||||||
|
/**
|
||||||
|
* The UserAPI class is the interface between the user's code and the backend. It provides
|
||||||
|
* access to the AudioContext, to the MIDI Interface, to internal variables, mouse position,
|
||||||
|
* useful functions, etc... This class is exposed to the user's action and any function
|
||||||
|
* destined to the user should be placed here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public codeExamples: { [key: string]: string } = {};
|
||||||
|
public counters: { [key: string]: any } = {};
|
||||||
|
//@ts-ignore
|
||||||
|
public _drunk: DrunkWalk = new DrunkWalk(-100, 100, false);
|
||||||
|
public randomGen = Math.random;
|
||||||
|
public currentSeed: string | undefined = undefined;
|
||||||
|
public localSeeds = new Map<string, Function>();
|
||||||
|
public patternCache = new LRUCache({ max: 10000, ttl: 10000 * 60 * 5 });
|
||||||
|
public invalidPatterns: { [key: string]: boolean } = {};
|
||||||
|
public cueTimes: { [key: string]: number } = {};
|
||||||
|
private errorTimeoutID: number = 0;
|
||||||
|
private printTimeoutID: number = 0;
|
||||||
|
public MidiConnection: MidiConnection;
|
||||||
|
public scale_aid: string | number | undefined = undefined;
|
||||||
|
public hydra: any;
|
||||||
|
public onceEvaluator: boolean = true;
|
||||||
|
public forceEvaluator: boolean = false;
|
||||||
|
|
||||||
|
load: samples;
|
||||||
|
public global: { [key: string]: any };
|
||||||
|
time: () => number;
|
||||||
|
play: () => void;
|
||||||
|
pause: () => void;
|
||||||
|
stop: () => void;
|
||||||
|
silence: () => void;
|
||||||
|
mouseX: () => number;
|
||||||
|
mouseY: () => number;
|
||||||
|
noteX: () => number;
|
||||||
|
noteY: () => number;
|
||||||
|
tempo: (n?: number | undefined) => number;
|
||||||
|
ppqn: (n?: number | undefined) => number;
|
||||||
|
time_signature: (numerator: number, denominator: number) => void;
|
||||||
|
theme: (color_scheme: string) => void;
|
||||||
|
themeName: () => string;
|
||||||
|
randomTheme: () => void;
|
||||||
|
nextTheme: () => void;
|
||||||
|
getThemes: () => string[];
|
||||||
|
pulseLocation: () => number;
|
||||||
|
clear: () => boolean;
|
||||||
|
loadHydra: () => void;
|
||||||
|
w: () => number;
|
||||||
|
h: () => number;
|
||||||
|
hc: () => number;
|
||||||
|
wc: () => number;
|
||||||
|
background: (color: string | number, ...gb: number[]) => boolean;
|
||||||
|
linearGradient: (x1: number, y1: number, x2: number, y2: number, ...stops: (number | string)[]) => CanvasGradient;
|
||||||
|
radialGradient: (x1: number, y1: number, r1: number, x2: number, y2: number, r2: number, ...stops: (number | string)[]) => CanvasGradient;
|
||||||
|
conicGradient: (x: number, y: number, angle: number, ...stops: (number | string)[]) => CanvasGradient;
|
||||||
|
draw: (func: Function) => boolean;
|
||||||
|
balloid: (curves: number | ShapeObject, radius: number, curve: number, fillStyle: string, secondary: string, x: number, y: number) => boolean;
|
||||||
|
equilateral: (radius: number | ShapeObject, fillStyle: string, rotation: number, x: number, y: number) => boolean;
|
||||||
|
triangular: (width: number | ShapeObject, height: number, fillStyle: string, rotation: number, x: number, y: number) => boolean;
|
||||||
|
ball: (radius: number | ShapeObject, fillStyle: string, x: number, y: number) => boolean;
|
||||||
|
circle: (radius: number | ShapeObject, fillStyle: string, x: number, y: number) => boolean;
|
||||||
|
donut: (slices: number | ShapeObject, eaten: number, radius: number, hole: number, fillStyle: string, secondary: string, stroke: string, rotation: number, x: number, y: number) => boolean;
|
||||||
|
pie: (slices: number | ShapeObject, eaten: number, radius: number, fillStyle: string, secondary: string, stroke: string, rotation: number, x: number, y: number) => boolean;
|
||||||
|
star: (points: number | ShapeObject, radius: number, fillStyle: string, rotation: number, outerRadius: number, x: number, y: number) => boolean;
|
||||||
|
stroke: (width: number | ShapeObject, strokeStyle: string, rotation: number, x1: number, y1: number, x2: number, y2: number) => boolean;
|
||||||
|
box: (width: number | ShapeObject, height: number, fillStyle: string, rotation: number, x: number, y: number) => boolean;
|
||||||
|
smiley: (happiness: number | ShapeObject, radius: number, eyeSize: number, fillStyle: string, rotation: number, x: number, y: number) => boolean;
|
||||||
|
text: (text: string | ShapeObject, fontSize: number, rotation: number, font: string, x: number, y: number, fillStyle: string, filter: string) => boolean;
|
||||||
|
image: (url: string | ShapeObject, width: number, height: number, rotation: number, x: number, y: number, filter: string) => boolean;
|
||||||
|
randomChar: (length: number, min: number, max: number) => string;
|
||||||
|
randomFromRange: (min: number, max: number) => string;
|
||||||
|
emoji: (n: number) => string;
|
||||||
|
food: (n: number) => string;
|
||||||
|
animals: (n: number) => string;
|
||||||
|
expressions: (n: number) => string;
|
||||||
|
generateCacheKey: (...args: any[]) => string;
|
||||||
|
resetAllFromCache: () => void;
|
||||||
|
clearPatternCache: () => void;
|
||||||
|
removePatternFromCache: (id: string) => void;
|
||||||
|
script: (...args: number[]) => void;
|
||||||
|
s: (...args: number[]) => void;
|
||||||
|
delete_script: (script: number) => void;
|
||||||
|
cs: (script: number) => void;
|
||||||
|
copy_script: (from: number, to: number) => void;
|
||||||
|
cps: (from: number, to: number) => void;
|
||||||
|
copy_universe: (from: string, to: string) => void;
|
||||||
|
delete_universe: (universe: string) => void;
|
||||||
|
big_bang: () => void;
|
||||||
|
drunk: (n?: number | undefined) => number;
|
||||||
|
drunk_max: (max: number) => void;
|
||||||
|
drunk_min: (min: number) => void;
|
||||||
|
drunk_wrap: (wrap: boolean) => void;
|
||||||
|
warp: (n: number) => void;
|
||||||
|
beat_warp: (beat: number) => void;
|
||||||
|
min: (...values: number[]) => number;
|
||||||
|
max: (...values: number[]) => number;
|
||||||
|
mean: (...values: number[]) => number;
|
||||||
|
limit: (value: number, min: number, max: number) => number;
|
||||||
|
abs: (value: number) => number;
|
||||||
|
z: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
fullseq: (sequence: string, duration: number) => boolean | boolean[];
|
||||||
|
seq: (expr: string, duration?: number) => boolean;
|
||||||
|
beat: (n?: number | number[], nudge?: number) => boolean;
|
||||||
|
bar: (n?: number | number[], nudge?: number) => boolean;
|
||||||
|
pulse: (n?: number | number[], nudge?: number) => boolean;
|
||||||
|
tick: (tick: number | number[], offset?: number) => boolean;
|
||||||
|
dur: (n: number | number[]) => boolean;
|
||||||
|
flip: (chunk: number, ratio?: number) => boolean;
|
||||||
|
flipbar: (chunk?: number) => boolean;
|
||||||
|
onbar: (bars: number | number[], n?: number) => boolean;
|
||||||
|
onbeat: (...beat: number[]) => boolean;
|
||||||
|
oncount: (beats: number | number[], count: number) => boolean;
|
||||||
|
oneuclid: (pulses: number, length: number, rotate?: number) => boolean;
|
||||||
|
euclid: (iterator: number, pulses: number, length: number, rotate?: number) => boolean;
|
||||||
|
ec: any;
|
||||||
|
rhythm: (div: number, pulses: number, length: number, rotate?: number) => boolean;
|
||||||
|
ry: any;
|
||||||
|
nrhythm: (div: number, pulses: number, length: number, rotate?: number) => boolean;
|
||||||
|
nry: any;
|
||||||
|
bin: (iterator: number, n: number) => boolean;
|
||||||
|
binrhythm: (div: number, n: number) => boolean;
|
||||||
|
bry: any;
|
||||||
|
line: any;
|
||||||
|
sine: any;
|
||||||
|
usine: any;
|
||||||
|
saw: any;
|
||||||
|
usaw: any;
|
||||||
|
triangle: any;
|
||||||
|
utriangle: any;
|
||||||
|
square: any;
|
||||||
|
usquare: any;
|
||||||
|
noise: any;
|
||||||
|
unoise: any;
|
||||||
|
prob: (p: number) => boolean;
|
||||||
|
toss: () => boolean;
|
||||||
|
odds: (n: number, beats?: number) => boolean;
|
||||||
|
never: (beats?: number) => boolean;
|
||||||
|
almostNever: (beats?: number) => boolean;
|
||||||
|
rarely: (beats?: number) => boolean;
|
||||||
|
scarcely: (beats?: number) => boolean;
|
||||||
|
sometimes: (beats?: number) => boolean;
|
||||||
|
often: (beats?: number) => boolean;
|
||||||
|
frequently: (beats?: number) => boolean;
|
||||||
|
almostAlways: (beats?: number) => boolean;
|
||||||
|
always: (beats?: number) => boolean;
|
||||||
|
dice: (sides: number) => number;
|
||||||
|
osc: (address: string, port: number, ...args: any[]) => void;
|
||||||
|
getOSC: (address?: string | undefined) => any[];
|
||||||
|
gif: (options: any) => void;
|
||||||
|
scope: (config: OscilloscopeConfig) => void;
|
||||||
|
randI: any;
|
||||||
|
rand: any;
|
||||||
|
ir: any;
|
||||||
|
irand: any;
|
||||||
|
r: any;
|
||||||
|
seed: any;
|
||||||
|
localSeededRandom: any;
|
||||||
|
clearLocalSeed: any;
|
||||||
|
once: () => boolean;
|
||||||
|
counter: (name: string | number, limit?: number | undefined, step?: number | undefined) => number;
|
||||||
|
$: any;
|
||||||
|
count: any;
|
||||||
|
i: (n?: number | undefined) => any;
|
||||||
|
sound: (sound: string | string[] | null | undefined) => SoundEvent | SkipEvent;
|
||||||
|
snd: any;
|
||||||
|
log: (message: any) => void;
|
||||||
|
logOnce: (message: any) => void;
|
||||||
|
speak: (text: string, lang?: string, voiceIndex?: number, rate?: number, pitch?: number) => void;
|
||||||
|
cbar: () => number;
|
||||||
|
ctick: () => number;
|
||||||
|
cpulse: () => number;
|
||||||
|
cbeat: () => number;
|
||||||
|
ebeat: () => number;
|
||||||
|
epulse: () => number;
|
||||||
|
nominator: () => number;
|
||||||
|
meter: () => number;
|
||||||
|
denominator: () => number;
|
||||||
|
pulsesForBar: () => number;
|
||||||
|
z0!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z1!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z2!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z3!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z4!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z5!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z6!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z7!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z8!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z9!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z10!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z11!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z12!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z13!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z14!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z15!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z16!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
|
||||||
|
constructor(public app: Editor) {
|
||||||
|
this.MidiConnection = new MidiConnection(this, app.settings);
|
||||||
|
this.global = {};
|
||||||
|
this.g = this.global;
|
||||||
|
this.time = Transport.time(this);
|
||||||
|
this.play = Transport.play(this);
|
||||||
|
this.pause = Transport.pause(this);
|
||||||
|
this.stop = Transport.stop(this);
|
||||||
|
this.silence = Transport.silence(this);
|
||||||
|
this.tempo = Transport.tempo(this.app);
|
||||||
|
this.ppqn = Transport.ppqn(this.app);
|
||||||
|
this.time_signature = Transport.time_signature(this.app);
|
||||||
|
this.mouseX = Mouse.mouseX(this.app);
|
||||||
|
this.mouseY = Mouse.mouseY(this.app);
|
||||||
|
this.noteX = Mouse.noteX(this.app);
|
||||||
|
this.noteY = Mouse.noteY(this.app);
|
||||||
|
this.theme = Theme.theme(this.app);
|
||||||
|
this.themeName = Theme.themeName(this.app);
|
||||||
|
this.randomTheme = Theme.randomTheme(this.app);
|
||||||
|
this.nextTheme = Theme.nextTheme(this.app);
|
||||||
|
this.getThemes = Theme.getThemes();
|
||||||
|
this.pulseLocation = Canvas.pulseLocation(this.app);
|
||||||
|
this.loadHydra = Canvas.loadHydra(this.app);
|
||||||
|
this.clear = Canvas.clear(this.app);
|
||||||
|
this.w = Canvas.w(this.app);
|
||||||
|
this.h = Canvas.h(this.app);
|
||||||
|
this.hc = Canvas.hc(this.app);
|
||||||
|
this.wc = Canvas.wc(this.app);
|
||||||
|
this.background = Canvas.background(this.app);
|
||||||
|
this.linearGradient = Canvas.linearGradient(this.app);
|
||||||
|
this.radialGradient = Canvas.radialGradient(this.app);
|
||||||
|
this.conicGradient = Canvas.conicGradient(this.app);
|
||||||
|
this.draw = Canvas.draw(this.app);
|
||||||
|
this.balloid = Canvas.balloid(this.app);
|
||||||
|
this.equilateral = Canvas.equilateral(this.app);
|
||||||
|
this.triangular = Canvas.triangular(this.app);
|
||||||
|
this.ball = Canvas.ball(this.app);
|
||||||
|
this.circle = Canvas.circle(this.app);
|
||||||
|
this.donut = Canvas.donut(this.app);
|
||||||
|
this.pie = Canvas.pie(this.app);
|
||||||
|
this.star = Canvas.star(this.app);
|
||||||
|
this.stroke = Canvas.stroke(this.app);
|
||||||
|
this.box = Canvas.box(this.app);
|
||||||
|
this.smiley = Canvas.smiley(this.app);
|
||||||
|
this.text = Canvas.text(this.app);
|
||||||
|
this.image = Canvas.image(this.app);
|
||||||
|
this.randomChar = Canvas.randomChar();
|
||||||
|
this.randomFromRange = Canvas.randomFromRange();
|
||||||
|
this.emoji = Canvas.emoji();
|
||||||
|
this.food = Canvas.food();
|
||||||
|
this.animals = Canvas.animals();
|
||||||
|
this.expressions = Canvas.expressions();
|
||||||
|
this.generateCacheKey = Cache.generateCacheKey();
|
||||||
|
this.resetAllFromCache = Cache.resetAllFromCache(this);
|
||||||
|
this.clearPatternCache = Cache.clearPatternCache(this);
|
||||||
|
this.removePatternFromCache = Cache.removePatternFromCache(this);
|
||||||
|
this.script = Script.script(this.app);
|
||||||
|
this.s = this.script;
|
||||||
|
this.delete_script = Script.delete_script(this.app);
|
||||||
|
this.cs = this.delete_script;
|
||||||
|
this.copy_script = Script.copy_script(this.app);
|
||||||
|
this.cps = this.copy_script;
|
||||||
|
this.copy_universe = Script.copy_universe(this.app);
|
||||||
|
this.delete_universe = Script.delete_universe(this.app);
|
||||||
|
this.big_bang = Script.big_bang(this.app);
|
||||||
|
this.drunk = Drunk.drunk(this);
|
||||||
|
this.drunk_max = Drunk.drunk_max(this);
|
||||||
|
this.drunk_min = Drunk.drunk_min(this);
|
||||||
|
this.drunk_wrap = Drunk.drunk_wrap(this);
|
||||||
|
this.warp = Warp.warp(this.app);
|
||||||
|
this.beat_warp = Warp.beat_warp(this.app);
|
||||||
|
this.min = Mathematics.min();
|
||||||
|
this.max = Mathematics.max();
|
||||||
|
this.mean = Mathematics.mean();
|
||||||
|
this.limit = Mathematics.limit();
|
||||||
|
this.abs = Mathematics.abs();
|
||||||
|
this.z = Ziffers.z(this);
|
||||||
|
Object.assign(this, Ziffers.generateZFunctions(this));
|
||||||
|
this.fullseq = Filters.fullseq();
|
||||||
|
this.seq = Filters.seq(this.app);
|
||||||
|
this.beat = Filters.beat(this.app);
|
||||||
|
this.bar = Filters.bar(this.app);
|
||||||
|
this.pulse = Filters.pulse(this.app);
|
||||||
|
this.tick = Filters.tick(this.app);
|
||||||
|
this.dur = Filters.dur(this.app);
|
||||||
|
this.flip = Filters.flip(this.app);
|
||||||
|
this.flipbar = Filters.flipbar(this.app);
|
||||||
|
this.onbar = Filters.onbar(this.app);
|
||||||
|
this.onbeat = Filters.onbeat(this);
|
||||||
|
this.oncount = Filters.oncount(this.app);
|
||||||
|
this.oneuclid = Filters.oneuclid(this.app);
|
||||||
|
this.euclid = Filters.euclid();
|
||||||
|
this.ec = this.euclid;
|
||||||
|
this.rhythm = Filters.rhythm(this.app);
|
||||||
|
this.ry = this.rhythm;
|
||||||
|
this.nrhythm = Filters.nrhythm(this.app);
|
||||||
|
this.nry = this.nrhythm;
|
||||||
|
this.bin = Filters.bin();
|
||||||
|
this.binrhythm = Filters.binrhythm(this.app);
|
||||||
|
this.bry = this.binrhythm;
|
||||||
|
this.line = LFO.line();
|
||||||
|
this.sine = LFO.sine(this.app);
|
||||||
|
this.usine = LFO.usine(this.app);
|
||||||
|
this.saw = LFO.saw(this.app);
|
||||||
|
this.usaw = LFO.usaw(this.app);
|
||||||
|
this.triangle = LFO.triangle(this.app);
|
||||||
|
this.utriangle = LFO.utriangle(this.app);
|
||||||
|
this.square = LFO.square(this.app);
|
||||||
|
this.usquare = LFO.usquare(this.app);
|
||||||
|
this.noise = LFO.noise(this);
|
||||||
|
this.unoise = LFO.unoise(this);
|
||||||
|
this.prob = Probability.prob(this);
|
||||||
|
this.toss = Probability.toss(this);
|
||||||
|
this.odds = Probability.odds(this);
|
||||||
|
this.never = Probability.never();
|
||||||
|
this.almostNever = Probability.almostNever(this);
|
||||||
|
this.rarely = Probability.rarely(this);
|
||||||
|
this.scarcely = Probability.scarcely(this);
|
||||||
|
this.sometimes = Probability.sometimes(this);
|
||||||
|
this.often = Probability.often(this);
|
||||||
|
this.frequently = Probability.frequently(this);
|
||||||
|
this.almostAlways = Probability.almostAlways(this);
|
||||||
|
this.always = Probability.always();
|
||||||
|
this.dice = Probability.dice(this);
|
||||||
|
this.osc = OSC.osc(this.app);
|
||||||
|
this.getOSC = OSC.getOSC();
|
||||||
|
this.gif = Canvas.gif(this.app);
|
||||||
|
this.scope = Canvas.scope(this.app);
|
||||||
|
this.randI = Randomness.randI(this);
|
||||||
|
this.ir = this.randI;
|
||||||
|
this.irand = this.randI;
|
||||||
|
this.rand = Randomness.rand(this);
|
||||||
|
this.r = this.rand;
|
||||||
|
this.seed = Randomness.seed(this);
|
||||||
|
this.localSeededRandom = Randomness.localSeededRandom(this);
|
||||||
|
this.clearLocalSeed = Randomness.clearLocalSeed(this);
|
||||||
|
this.once = Counter.once(this);
|
||||||
|
this.counter = Counter.counter(this);
|
||||||
|
this.$ = this.counter;
|
||||||
|
this.count = this.counter;
|
||||||
|
this.i = Counter.i(this.app);
|
||||||
|
this.sound = Sound.sound(this.app);
|
||||||
|
this.snd = this.sound;
|
||||||
|
this.speak = Sound.speak();
|
||||||
|
this.log = Console.log(this);
|
||||||
|
this.logOnce = Console.logOnce(this);
|
||||||
|
this.cbar = Transport.cbar(this.app);
|
||||||
|
this.ctick = Transport.ctick(this.app);
|
||||||
|
this.cpulse = Transport.cpulse(this.app);
|
||||||
|
this.cbeat = Transport.cbeat(this.app);
|
||||||
|
this.ebeat = Transport.ebeat(this.app);
|
||||||
|
this.epulse = Transport.epulse(this.app);
|
||||||
|
this.nominator = Transport.nominator(this.app);
|
||||||
|
this.meter = Transport.meter(this.app);
|
||||||
|
this.denominator = Transport.denominator(this.app);
|
||||||
|
this.pulsesForBar = Transport.pulsesForBar(this.app);
|
||||||
|
}
|
||||||
|
|
||||||
|
public g: any;
|
||||||
|
|
||||||
|
_loadUniverseFromInterface = (universe: string) => {
|
||||||
|
this.app.selected_universe = universe.trim();
|
||||||
|
this.app.settings.selected_universe = universe.trim();
|
||||||
|
loadUniverse(this.app, universe as string);
|
||||||
|
openUniverseModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
_deleteUniverseFromInterface = (universe: string) => {
|
||||||
|
delete this.app.universes[universe];
|
||||||
|
if (this.app.settings.selected_universe === universe) {
|
||||||
|
this.app.settings.selected_universe = "Welcome";
|
||||||
|
this.app.selected_universe = "Welcome";
|
||||||
|
}
|
||||||
|
this.app.settings.saveApplicationToLocalStorage(
|
||||||
|
this.app.universes,
|
||||||
|
this.app.settings,
|
||||||
|
);
|
||||||
|
this.app.updateKnownUniversesView();
|
||||||
|
};
|
||||||
|
|
||||||
|
_playDocExample = (code?: string) => {
|
||||||
|
/**
|
||||||
|
* Play an example from the documentation. The example is going
|
||||||
|
* to be stored in the example buffer belonging to the universe.
|
||||||
|
* This buffer is going to be cleaned everytime the user press
|
||||||
|
* pause or leaves the documentation window.
|
||||||
|
*
|
||||||
|
* @param code - The code example to play (identifier)
|
||||||
|
*/
|
||||||
|
let current_universe = this.app.universes[this.app.selected_universe]!;
|
||||||
|
this.app.exampleIsPlaying = true;
|
||||||
|
if (!current_universe.example) {
|
||||||
|
current_universe.example = {
|
||||||
|
candidate: "",
|
||||||
|
committed: "",
|
||||||
|
evaluations: 0,
|
||||||
|
};
|
||||||
|
current_universe.example.candidate! = code
|
||||||
|
? code
|
||||||
|
: (this.app.selectedExample as string);
|
||||||
|
} else {
|
||||||
|
current_universe.example.candidate! = code
|
||||||
|
? code
|
||||||
|
: (this.app.selectedExample as string);
|
||||||
|
}
|
||||||
|
this.patternCache.clear();
|
||||||
|
if (this.app.isPlaying) {
|
||||||
|
} else {
|
||||||
|
this.app.clock.resume();
|
||||||
|
updatePlayPauseIcon(this.app, "play");
|
||||||
|
this.app.api.MidiConnection.sendStartMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// this.app.clock.start();
|
||||||
|
};
|
||||||
|
|
||||||
|
_stopDocExample = () => {
|
||||||
|
let current_universe = this.app.universes[this.app.selected_universe];
|
||||||
|
if (current_universe?.example !== undefined) {
|
||||||
|
this.app.exampleIsPlaying = false;
|
||||||
|
current_universe.example.candidate! = "";
|
||||||
|
current_universe.example.committed! = "";
|
||||||
|
}
|
||||||
|
this.clearPatternCache();
|
||||||
|
this.stop();
|
||||||
|
};
|
||||||
|
|
||||||
|
_playDocExampleOnce = (code?: string) => {
|
||||||
|
let current_universe = this.app.universes[this.app.selected_universe];
|
||||||
|
if (current_universe?.example !== undefined) {
|
||||||
|
current_universe.example.candidate! = "";
|
||||||
|
current_universe.example.committed! = "";
|
||||||
|
}
|
||||||
|
this.clearPatternCache();
|
||||||
|
this.stop();
|
||||||
|
this.play();
|
||||||
|
this.app.exampleIsPlaying = true;
|
||||||
|
evaluateOnce(this.app, code as string);
|
||||||
|
};
|
||||||
|
|
||||||
|
_all_samples = (): object => {
|
||||||
|
return soundMap.get();
|
||||||
|
};
|
||||||
|
|
||||||
|
_reportError = (error: any): void => {
|
||||||
|
const extractLineAndColumn = (error: Error) => {
|
||||||
|
const stackLines = error.stack?.split("\n");
|
||||||
|
if (stackLines) {
|
||||||
|
for (const line of stackLines) {
|
||||||
|
if (line.includes("<anonymous>")) {
|
||||||
|
const match = line.match(/<anonymous>:(\d+):(\d+)/);
|
||||||
|
if (match as RegExpMatchArray)
|
||||||
|
return {
|
||||||
|
// @ts-ignore
|
||||||
|
line: parseInt(match[1], 10),
|
||||||
|
// @ts-ignore
|
||||||
|
column: parseInt(match[2]!, 10),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { line: null, column: null };
|
||||||
|
};
|
||||||
|
|
||||||
|
const { line, column } = extractLineAndColumn(error);
|
||||||
|
const errorMessage =
|
||||||
|
line && column
|
||||||
|
? `${error.message} (Line: ${line - 2}, Column: ${column})`
|
||||||
|
: error.message;
|
||||||
|
|
||||||
|
clearTimeout(this.errorTimeoutID);
|
||||||
|
clearTimeout(this.printTimeoutID);
|
||||||
|
this.app.interface.error_line.innerHTML = errorMessage;
|
||||||
|
this.app.interface.error_line.style.color = "red";
|
||||||
|
this.app.interface.error_line.classList.remove("hidden");
|
||||||
|
// @ts-ignore
|
||||||
|
this.errorTimeoutID = setTimeout(
|
||||||
|
() => this.app.interface.error_line.classList.add("hidden"),
|
||||||
|
2000,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
_logMessage = (message: any, error: boolean = false): void => {
|
||||||
|
console.log(message);
|
||||||
|
clearTimeout(this.printTimeoutID);
|
||||||
|
clearTimeout(this.errorTimeoutID);
|
||||||
|
this.app.interface.error_line.innerHTML = message as string;
|
||||||
|
this.app.interface.error_line.style.color = error ? "red" : "white";
|
||||||
|
this.app.interface.error_line.classList.remove("hidden");
|
||||||
|
// @ts-ignore
|
||||||
|
this.printTimeoutID = setTimeout(
|
||||||
|
() => this.app.interface.error_line.classList.add("hidden"),
|
||||||
|
4000,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// =============================================================
|
||||||
|
// Quantification functions
|
||||||
|
// =============================================================
|
||||||
|
|
||||||
|
public quantize = (value: number, quantization: number[]): number => {
|
||||||
|
/**
|
||||||
|
* Returns the closest value in an array to a given value.
|
||||||
|
*
|
||||||
|
* @param value - The value to quantize
|
||||||
|
* @param quantization - The array of values to quantize to
|
||||||
|
* @returns The closest value in the array to the given value
|
||||||
|
*/
|
||||||
|
if (quantization.length === 0) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
let closest: number | undefined = quantization[0];
|
||||||
|
quantization.forEach((q) => {
|
||||||
|
if (Math.abs(q - value) < Math.abs(closest! - value)) {
|
||||||
|
closest = q;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return closest!;
|
||||||
|
};
|
||||||
|
quant = this.quantize;
|
||||||
|
|
||||||
|
public clamp = (value: number, min: number, max: number): number => {
|
||||||
|
/**
|
||||||
|
* Returns a value clamped between min and max.
|
||||||
|
*
|
||||||
|
* @param value - The value to clamp
|
||||||
|
* @param min - The minimum value of the clamped value
|
||||||
|
* @param max - The maximum value of the clamped value
|
||||||
|
* @returns A value clamped between min and max
|
||||||
|
*/
|
||||||
|
return Math.min(Math.max(value, min), max);
|
||||||
|
};
|
||||||
|
cmp = this.clamp;
|
||||||
|
|
||||||
|
// =============================================================
|
||||||
|
// Time markers
|
||||||
|
// =============================================================
|
||||||
|
|
||||||
|
// =============================================================
|
||||||
|
// Fill
|
||||||
|
// =============================================================
|
||||||
|
|
||||||
|
public fill = (): boolean => this.app.fill;
|
||||||
|
|
||||||
|
scale = getScaleNotes;
|
||||||
|
nearScales = nearScales;
|
||||||
|
|
||||||
|
public cue = (functionName: string | Function): void => {
|
||||||
|
functionName = typeof functionName === "function" ? functionName.name : functionName;
|
||||||
|
this.cueTimes[functionName] = this.app.clock.grain;
|
||||||
|
};
|
||||||
|
|
||||||
|
onmousemove = (e: MouseEvent) => {
|
||||||
|
this.app._mouseX = e.pageX;
|
||||||
|
this.app._mouseY = e.pageY;
|
||||||
|
};
|
||||||
|
}
|
||||||
61
src/API/Cache.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { isGenerator, isGeneratorFunction, maybeToNumber } from "../Utils/Generic";
|
||||||
|
import { type Player } from "../Classes/ZPlayer";
|
||||||
|
import { type UserAPI } from "./API";
|
||||||
|
|
||||||
|
|
||||||
|
export const generateCacheKey = () => (...args: any[]): string => {
|
||||||
|
return args.map((arg) => JSON.stringify(arg)).join(",");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resetAllFromCache = (api: UserAPI) => (): void => {
|
||||||
|
api.patternCache.forEach((player) => (player as Player).reset());
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clearPatternCache = (api: UserAPI) => (): void => {
|
||||||
|
api.patternCache.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removePatternFromCache = (api: UserAPI) => (id: string): void => {
|
||||||
|
api.patternCache.delete(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const cache = (api: UserAPI) => (key: string, value: any) => {
|
||||||
|
if (value !== undefined) {
|
||||||
|
if (isGenerator(value)) {
|
||||||
|
if (api.patternCache.has(key)) {
|
||||||
|
const cachedValue = (api.patternCache.get(key) as Generator<any>).next().value;
|
||||||
|
if (cachedValue !== 0 && !cachedValue) {
|
||||||
|
const generator = value as unknown as Generator<any>;
|
||||||
|
api.patternCache.set(key, generator);
|
||||||
|
return maybeToNumber(generator.next().value);
|
||||||
|
}
|
||||||
|
return maybeToNumber(cachedValue);
|
||||||
|
} else {
|
||||||
|
const generator = value as unknown as Generator<any>;
|
||||||
|
api.patternCache.set(key, generator);
|
||||||
|
return maybeToNumber(generator.next().value);
|
||||||
|
}
|
||||||
|
} else if (isGeneratorFunction(value)) {
|
||||||
|
if (api.patternCache.has(key)) {
|
||||||
|
const cachedValue = (api.patternCache.get(key) as Generator<any>).next().value;
|
||||||
|
if (cachedValue || cachedValue === 0 || cachedValue === 0n) {
|
||||||
|
return maybeToNumber(cachedValue);
|
||||||
|
} else {
|
||||||
|
const generator = value();
|
||||||
|
api.patternCache.set(key, generator);
|
||||||
|
return maybeToNumber(generator.next().value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const generator = value();
|
||||||
|
api.patternCache.set(key, generator);
|
||||||
|
return maybeToNumber(generator.next().value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
api.patternCache.set(key, value);
|
||||||
|
return maybeToNumber(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return maybeToNumber(api.patternCache.get(key));
|
||||||
|
}
|
||||||
|
};
|
||||||
43
src/API/Counter.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { type UserAPI } from "./API";
|
||||||
|
import { type Editor } from "../main";
|
||||||
|
|
||||||
|
export const once = (api: UserAPI) => (): boolean => {
|
||||||
|
const firstTime = api.onceEvaluator;
|
||||||
|
api.onceEvaluator = false;
|
||||||
|
return firstTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const counter = (api: UserAPI) => (name: string | number, limit?: number, step?: number): number => {
|
||||||
|
if (!(name in api.counters)) {
|
||||||
|
api.counters[name] = {
|
||||||
|
value: 0,
|
||||||
|
step: step ?? 1,
|
||||||
|
limit,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if (api.counters[name].limit !== limit) {
|
||||||
|
api.counters[name].value = 0;
|
||||||
|
api.counters[name].limit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (api.counters[name].step !== step) {
|
||||||
|
api.counters[name].step = step ?? api.counters[name].step;
|
||||||
|
}
|
||||||
|
|
||||||
|
api.counters[name].value += api.counters[name].step;
|
||||||
|
|
||||||
|
if (api.counters[name].limit !== undefined && api.counters[name].value > api.counters[name].limit) {
|
||||||
|
api.counters[name].value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.counters[name].value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const i = (app: Editor) => (n?: number) => {
|
||||||
|
if (n !== undefined) {
|
||||||
|
app.universes[app.selected_universe]!.global.evaluations = n;
|
||||||
|
return app.universes[app.selected_universe];
|
||||||
|
}
|
||||||
|
return app.universes[app.selected_universe]!.global.evaluations as number;
|
||||||
|
};
|
||||||
452
src/API/DOM/Canvas.ts
Normal file
@ -0,0 +1,452 @@
|
|||||||
|
import { OscilloscopeConfig } from "../../DOM/Visuals/Oscilloscope";
|
||||||
|
import { createConicGradient, createLinearGradient, createRadialGradient, drawBackground, drawBox, drawBall, drawBalloid, drawDonut, drawEquilateral, drawImage, drawPie, drawSmiley, drawStar, drawStroke, drawText, drawTriangular } from "../../DOM/Visuals/CanvasVisuals";
|
||||||
|
import { Editor } from "../../main";
|
||||||
|
|
||||||
|
export type ShapeObject = {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
x1: number;
|
||||||
|
y1: number;
|
||||||
|
x2: number;
|
||||||
|
y2: number;
|
||||||
|
radius: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
fillStyle: string;
|
||||||
|
secondary: string;
|
||||||
|
strokeStyle: string;
|
||||||
|
rotation: number;
|
||||||
|
points: number;
|
||||||
|
outerRadius: number;
|
||||||
|
eyeSize: number;
|
||||||
|
happiness: number;
|
||||||
|
slices: number;
|
||||||
|
gap: number;
|
||||||
|
font: string;
|
||||||
|
fontSize: number;
|
||||||
|
text: string;
|
||||||
|
filter: string;
|
||||||
|
url: string;
|
||||||
|
curve: number;
|
||||||
|
curves: number;
|
||||||
|
stroke: string;
|
||||||
|
eaten: number;
|
||||||
|
hole: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const loadHydra = (app: Editor) => (): void => {
|
||||||
|
app.api.log("Hydra is now loaded!")
|
||||||
|
app.ensureHydraLoaded()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const w = (app: Editor) => (): number => {
|
||||||
|
const canvas: HTMLCanvasElement = app.interface["feedback"] as HTMLCanvasElement;
|
||||||
|
return canvas.clientWidth;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pulseLocation = (app: Editor) => (): number => {
|
||||||
|
return ((app.api.epulse() / app.api.pulsesForBar()) * w(app)()) % w(app)();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clear = (app: Editor) => (): boolean => {
|
||||||
|
const canvas: HTMLCanvasElement = app.interface["feedback"] as HTMLCanvasElement;
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const h = (app: Editor) => (): number => {
|
||||||
|
const canvas: HTMLCanvasElement = app.interface["feedback"] as HTMLCanvasElement;
|
||||||
|
return canvas.clientHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hc = (app: Editor) => (): number => {
|
||||||
|
return h(app)() / 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const wc = (app: Editor) => (): number => {
|
||||||
|
return w(app)() / 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const background = (app: Editor) => (color: string | number, ...gb: number[]): boolean => {
|
||||||
|
drawBackground(app.interface["feedback"] as HTMLCanvasElement, color, ...gb);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
export const bg = background;
|
||||||
|
|
||||||
|
export const linearGradient = (app: Editor) => (x1: number, y1: number, x2: number, y2: number, ...stops: (number | string)[]): CanvasGradient => {
|
||||||
|
return createLinearGradient(app.interface["feedback"] as HTMLCanvasElement, x1, y1, x2, y2, ...stops);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const radialGradient = (app: Editor) => (x1: number, y1: number, r1: number, x2: number, y2: number, r2: number, ...stops: (number | string)[]) => {
|
||||||
|
return createRadialGradient(app.interface["feedback"] as HTMLCanvasElement, x1, y1, r1, x2, y2, r2, ...stops);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const conicGradient = (app: Editor) => (x: number, y: number, angle: number, ...stops: (number | string)[]) => {
|
||||||
|
return createConicGradient(app.interface["feedback"] as HTMLCanvasElement, x, y, angle, ...stops);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const draw = (app: Editor) => (func: Function): boolean => {
|
||||||
|
if (typeof func === "string") {
|
||||||
|
drawText(app.interface["feedback"] as HTMLCanvasElement, func, 24, 0, "Arial", wc(app)(), hc(app)(), "white", "none");
|
||||||
|
} else {
|
||||||
|
const canvas: HTMLCanvasElement = app.interface["feedback"] as HTMLCanvasElement;
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
func(ctx);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Additional drawing and utility functions in canvas.ts
|
||||||
|
export const balloid = (app: Editor) => (
|
||||||
|
curves: number | ShapeObject = 6,
|
||||||
|
radius: number = hc(app)() / 2,
|
||||||
|
curve: number = 1.5,
|
||||||
|
fillStyle: string = "white",
|
||||||
|
secondary: string = "black",
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
): boolean => {
|
||||||
|
if (typeof curves === "object") {
|
||||||
|
fillStyle = curves.fillStyle || "white";
|
||||||
|
x = curves.x || wc(app)();
|
||||||
|
y = curves.y || hc(app)();
|
||||||
|
curve = curves.curve || 1.5;
|
||||||
|
radius = curves.radius || hc(app)() / 2;
|
||||||
|
curves = curves.curves || 6;
|
||||||
|
}
|
||||||
|
drawBalloid(app.interface["feedback"] as HTMLCanvasElement, curves, radius, curve, fillStyle, secondary, x, y);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const equilateral = (app: Editor) => (
|
||||||
|
radius: number | ShapeObject = hc(app)() / 3,
|
||||||
|
fillStyle: string = "white",
|
||||||
|
rotation: number = 0,
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
): boolean => {
|
||||||
|
if (typeof radius === "object") {
|
||||||
|
fillStyle = radius.fillStyle || "white";
|
||||||
|
x = radius.x || wc(app)();
|
||||||
|
y = radius.y || hc(app)();
|
||||||
|
rotation = radius.rotation || 0;
|
||||||
|
radius = radius.radius || hc(app)() / 3;
|
||||||
|
}
|
||||||
|
drawEquilateral(app.interface["feedback"] as HTMLCanvasElement, radius, fillStyle, rotation, x, y);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const triangular = (app: Editor) => (
|
||||||
|
width: number | ShapeObject = hc(app)() / 3,
|
||||||
|
height: number = hc(app)() / 3,
|
||||||
|
fillStyle: string = "white",
|
||||||
|
rotation: number = 0,
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
): boolean => {
|
||||||
|
if (typeof width === "object") {
|
||||||
|
fillStyle = width.fillStyle || "white";
|
||||||
|
x = width.x || wc(app)();
|
||||||
|
y = width.y || hc(app)();
|
||||||
|
rotation = width.rotation || 0;
|
||||||
|
height = width.height || hc(app)() / 3;
|
||||||
|
width = width.width || hc(app)() / 3;
|
||||||
|
}
|
||||||
|
drawTriangular(app.interface['feedback'] as HTMLCanvasElement, width, height, fillStyle, rotation, x, y);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
export const pointy = triangular;
|
||||||
|
|
||||||
|
export const ball = (app: Editor) => (
|
||||||
|
radius: number | ShapeObject = hc(app)() / 3,
|
||||||
|
fillStyle: string = "white",
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
): boolean => {
|
||||||
|
if (typeof radius === "object") {
|
||||||
|
fillStyle = radius.fillStyle || "white";
|
||||||
|
x = radius.x || wc(app)();
|
||||||
|
y = radius.y || hc(app)();
|
||||||
|
radius = radius.radius || hc(app)() / 3;
|
||||||
|
}
|
||||||
|
drawBall(app.interface['feedback'] as HTMLCanvasElement, radius, fillStyle, x, y);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
export const circle = ball;
|
||||||
|
|
||||||
|
export const donut = (app: Editor) => (
|
||||||
|
slices: number | ShapeObject = 3,
|
||||||
|
eaten: number = 0,
|
||||||
|
radius: number = hc(app)() / 3,
|
||||||
|
hole: number = hc(app)() / 12,
|
||||||
|
fillStyle: string = "white",
|
||||||
|
secondary: string = "black",
|
||||||
|
stroke: string = "black",
|
||||||
|
rotation: number = 0,
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
): boolean => {
|
||||||
|
if (typeof slices === "object") {
|
||||||
|
fillStyle = slices.fillStyle || "white";
|
||||||
|
x = slices.x || wc(app)();
|
||||||
|
y = slices.y || hc(app)();
|
||||||
|
rotation = slices.rotation || 0;
|
||||||
|
radius = slices.radius || hc(app)() / 3;
|
||||||
|
eaten = slices.eaten || 0;
|
||||||
|
hole = slices.hole || hc(app)() / 12;
|
||||||
|
secondary = slices.secondary || "black";
|
||||||
|
stroke = slices.stroke || "black";
|
||||||
|
slices = slices.slices || 3;
|
||||||
|
}
|
||||||
|
drawDonut(app.interface['feedback'] as HTMLCanvasElement, slices, eaten, radius, hole, fillStyle, secondary, stroke, rotation, x, y);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pie = (app: Editor) => (
|
||||||
|
slices: number | ShapeObject = 3,
|
||||||
|
eaten: number = 0,
|
||||||
|
radius: number = hc(app)() / 3,
|
||||||
|
fillStyle: string = "white",
|
||||||
|
secondary: string = "black",
|
||||||
|
stroke: string = "black",
|
||||||
|
rotation: number = 0,
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
): boolean => {
|
||||||
|
if (typeof slices === "object") {
|
||||||
|
fillStyle = slices.fillStyle || "white";
|
||||||
|
x = slices.x || wc(app)();
|
||||||
|
y = slices.y || hc(app)();
|
||||||
|
rotation = slices.rotation || 0;
|
||||||
|
radius = slices.radius || hc(app)() / 3;
|
||||||
|
secondary = slices.secondary || "black";
|
||||||
|
stroke = slices.stroke || "black";
|
||||||
|
eaten = slices.eaten || 0;
|
||||||
|
slices = slices.slices || 3;
|
||||||
|
}
|
||||||
|
drawPie(app.interface['feedback'] as HTMLCanvasElement, slices, eaten, radius, fillStyle, secondary, stroke, rotation, x, y);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const star = (app: Editor) => (
|
||||||
|
points: number | ShapeObject = 5,
|
||||||
|
radius: number = hc(app)() / 3,
|
||||||
|
fillStyle: string = "white",
|
||||||
|
rotation: number = 0,
|
||||||
|
outerRadius: number = radius / 100,
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
): boolean => {
|
||||||
|
if (typeof points === "object") {
|
||||||
|
radius = points.radius || hc(app)() / 3;
|
||||||
|
fillStyle = points.fillStyle || "white";
|
||||||
|
x = points.x || wc(app)();
|
||||||
|
y = points.y || hc(app)();
|
||||||
|
rotation = points.rotation || 0;
|
||||||
|
outerRadius = points.outerRadius || radius / 100;
|
||||||
|
points = points.points || 5;
|
||||||
|
}
|
||||||
|
drawStar(app.interface['feedback'] as HTMLCanvasElement, points, radius, fillStyle, rotation, outerRadius, x, y);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stroke = (app: Editor) => (
|
||||||
|
width: number | ShapeObject = 1,
|
||||||
|
strokeStyle: string = "white",
|
||||||
|
rotation: number = 0,
|
||||||
|
x1: number = wc(app)() - wc(app)() / 10,
|
||||||
|
y1: number = hc(app)(),
|
||||||
|
x2: number = wc(app)() + wc(app)() / 5,
|
||||||
|
y2: number = hc(app)(),
|
||||||
|
): boolean => {
|
||||||
|
if (typeof width === "object") {
|
||||||
|
strokeStyle = width.strokeStyle || "white";
|
||||||
|
x1 = width.x1 || wc(app)() - wc(app)() / 10;
|
||||||
|
y1 = width.y1 || hc(app)();
|
||||||
|
x2 = width.x2 || wc(app)() + wc(app)() / 5;
|
||||||
|
y2 = width.y2 || hc(app)();
|
||||||
|
rotation = width.rotation || 0;
|
||||||
|
width = width.width || 1;
|
||||||
|
}
|
||||||
|
drawStroke(app.interface['feedback'] as HTMLCanvasElement, width, strokeStyle, rotation, x1, y1, x2, y2);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const box = (app: Editor) => (
|
||||||
|
width: number | ShapeObject = wc(app)() / 4,
|
||||||
|
height: number = wc(app)() / 4,
|
||||||
|
fillStyle: string = "white",
|
||||||
|
rotation: number = 0,
|
||||||
|
x: number = wc(app)() - wc(app)() / 8,
|
||||||
|
y: number = hc(app)() - hc(app)() / 8,
|
||||||
|
): boolean => {
|
||||||
|
if (typeof width === "object") {
|
||||||
|
fillStyle = width.fillStyle || "white";
|
||||||
|
x = width.x || wc(app)() - wc(app)() / 4;
|
||||||
|
y = width.y || hc(app)() - hc(app)() / 2;
|
||||||
|
rotation = width.rotation || 0;
|
||||||
|
height = width.height || wc(app)() / 4;
|
||||||
|
width = width.width || wc(app)() / 4;
|
||||||
|
}
|
||||||
|
drawBox(app.interface['feedback'] as HTMLCanvasElement, width, height, fillStyle, rotation, x, y);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const smiley = (app: Editor) => (
|
||||||
|
happiness: number | ShapeObject = 0,
|
||||||
|
radius: number = hc(app)() / 3,
|
||||||
|
eyeSize: number = 3.0,
|
||||||
|
fillStyle: string = "yellow",
|
||||||
|
rotation: number = 0,
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
): boolean => {
|
||||||
|
if (typeof happiness === "object") {
|
||||||
|
fillStyle = happiness.fillStyle || "yellow";
|
||||||
|
x = happiness.x || wc(app)();
|
||||||
|
y = happiness.y || hc(app)();
|
||||||
|
rotation = happiness.rotation || 0;
|
||||||
|
eyeSize = happiness.eyeSize || 3.0;
|
||||||
|
radius = happiness.radius || hc(app)() / 3;
|
||||||
|
happiness = happiness.happiness || 0;
|
||||||
|
}
|
||||||
|
drawSmiley(app.interface['feedback'] as HTMLCanvasElement, happiness, radius, eyeSize, fillStyle, rotation, x, y);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const text = (app: Editor) => (
|
||||||
|
text: string | ShapeObject,
|
||||||
|
fontSize: number = 24,
|
||||||
|
rotation: number = 0,
|
||||||
|
font: string = "Arial",
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
fillStyle: string = "white",
|
||||||
|
filter: string = "none",
|
||||||
|
): boolean => {
|
||||||
|
if (typeof text === "object") {
|
||||||
|
fillStyle = text.fillStyle || "white";
|
||||||
|
x = text.x || wc(app)();
|
||||||
|
y = text.y || hc(app)();
|
||||||
|
rotation = text.rotation || 0;
|
||||||
|
font = text.font || "Arial";
|
||||||
|
fontSize = text.fontSize || 24;
|
||||||
|
filter = text.filter || "none";
|
||||||
|
text = text.text || "";
|
||||||
|
}
|
||||||
|
drawText(app.interface['feedback'] as HTMLCanvasElement, text, fontSize, rotation, font, x, y, fillStyle, filter);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const image = (app: Editor) => (
|
||||||
|
url: string | ShapeObject,
|
||||||
|
width: number = wc(app)() / 2,
|
||||||
|
height: number = hc(app)() / 2,
|
||||||
|
rotation: number = 0,
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
filter: string = "none",
|
||||||
|
): boolean => {
|
||||||
|
if (typeof url === "object") {
|
||||||
|
if (!url.url) return true;
|
||||||
|
x = url.x || wc(app)();
|
||||||
|
y = url.y || hc(app)();
|
||||||
|
rotation = url.rotation || 0;
|
||||||
|
width = url.width || 100;
|
||||||
|
height = url.height || 100;
|
||||||
|
filter = url.filter || "none";
|
||||||
|
url = url.url || "";
|
||||||
|
}
|
||||||
|
drawImage(app.interface['feedback'] as HTMLCanvasElement, url, width, height, rotation, x, y, filter);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const randomChar = () => (length: number = 1, min: number = 0, max: number = 65536): string => {
|
||||||
|
return Array.from(
|
||||||
|
{ length }, () => String.fromCodePoint(Math.floor(Math.random() * (max - min) + min))
|
||||||
|
).join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const randomFromRange = () => (min: number, max: number): string => {
|
||||||
|
const codePoint = Math.floor(Math.random() * (max - min) + min);
|
||||||
|
return String.fromCodePoint(codePoint);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const emoji = () => (n: number = 1): string => {
|
||||||
|
return randomChar()(n, 0x1f600, 0x1f64f);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const food = () => (n: number = 1): string => {
|
||||||
|
return randomChar()(n, 0x1f32d, 0x1f37f);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const animals = () => (n: number = 1): string => {
|
||||||
|
return randomChar()(n, 0x1f400, 0x1f4d3);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const expressions = () => (n: number = 1): string => {
|
||||||
|
return randomChar()(n, 0x1f910, 0x1f92f);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const gif = (app: Editor) => (options: any): void => {
|
||||||
|
const {
|
||||||
|
url,
|
||||||
|
posX = 0,
|
||||||
|
posY = 0,
|
||||||
|
opacity = 1,
|
||||||
|
size = "auto",
|
||||||
|
center = false,
|
||||||
|
rotation = 0,
|
||||||
|
filter = 'none',
|
||||||
|
duration = 10
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
let real_duration = duration * app.clock.time_position.tick_duration * app.clock.ppqn;
|
||||||
|
let fadeOutDuration = real_duration * 0.1;
|
||||||
|
let visibilityDuration = real_duration - fadeOutDuration;
|
||||||
|
const gifElement = document.createElement("img");
|
||||||
|
gifElement.src = url;
|
||||||
|
gifElement.style.position = "fixed";
|
||||||
|
gifElement.style.left = center ? "50%" : `${posX}px`;
|
||||||
|
gifElement.style.top = center ? "50%" : `${posY}px`;
|
||||||
|
gifElement.style.opacity = `${opacity}`;
|
||||||
|
gifElement.style.zIndex = "1000"; // Ensure it's on top, fixed zIndex
|
||||||
|
if (size !== "auto") {
|
||||||
|
gifElement.style.width = size;
|
||||||
|
gifElement.style.height = size;
|
||||||
|
}
|
||||||
|
const transformRules = [`rotate(${rotation}deg)`];
|
||||||
|
if (center) {
|
||||||
|
transformRules.unshift("translate(-50%, -50%)");
|
||||||
|
}
|
||||||
|
gifElement.style.transform = transformRules.join(" ");
|
||||||
|
gifElement.style.filter = filter;
|
||||||
|
gifElement.style.transition = `opacity ${fadeOutDuration}s ease`;
|
||||||
|
document.body.appendChild(gifElement);
|
||||||
|
|
||||||
|
// Start the fade-out at the end of the visibility duration
|
||||||
|
setTimeout(() => {
|
||||||
|
gifElement.style.opacity = "0";
|
||||||
|
}, visibilityDuration * 1000);
|
||||||
|
|
||||||
|
// Remove the GIF from the DOM after the fade-out duration
|
||||||
|
setTimeout(() => {
|
||||||
|
if (document.body.contains(gifElement)) {
|
||||||
|
document.body.removeChild(gifElement);
|
||||||
|
}
|
||||||
|
}, real_duration * 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const scope = (app: Editor) => (config: OscilloscopeConfig): void => {
|
||||||
|
/**
|
||||||
|
* Configures the oscilloscope.
|
||||||
|
* @param config - The configuration object for the oscilloscope.
|
||||||
|
*/
|
||||||
|
app.osc = {
|
||||||
|
...app.osc,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
};
|
||||||
22
src/API/DOM/Console.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { type UserAPI } from "../API";
|
||||||
|
|
||||||
|
export const log = (api: UserAPI) => (message: any) => {
|
||||||
|
/**
|
||||||
|
* Logs a message to the console and app-specific logger.
|
||||||
|
* @param message - The message to log.
|
||||||
|
*/
|
||||||
|
console.log(message);
|
||||||
|
api._logMessage(message, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logOnce = (api: UserAPI) => (message: any) => {
|
||||||
|
/**
|
||||||
|
* Logs a message to the console and app-specific logger, but only once.
|
||||||
|
* @param message - The message to log.
|
||||||
|
*/
|
||||||
|
if (api.onceEvaluator) {
|
||||||
|
console.log(message);
|
||||||
|
api._logMessage(message, false);
|
||||||
|
api.onceEvaluator = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
29
src/API/DOM/Mouse.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Editor } from "../../main";
|
||||||
|
|
||||||
|
export const mouseX = (app: Editor) => (): number => {
|
||||||
|
/**
|
||||||
|
* @returns The current x position of the mouse
|
||||||
|
*/
|
||||||
|
return app._mouseX;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mouseY = (app: Editor) => (): number => {
|
||||||
|
/**
|
||||||
|
* @returns The current y position of the mouse
|
||||||
|
*/
|
||||||
|
return app._mouseY;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const noteX = (app: Editor) => (): number => {
|
||||||
|
/**
|
||||||
|
* @returns The current x position scaled to 0-127 using screen width
|
||||||
|
*/
|
||||||
|
return Math.floor((app._mouseX / document.body.clientWidth) * 127);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const noteY = (app: Editor) => (): number => {
|
||||||
|
/**
|
||||||
|
* @returns The current y position scaled to 0-127 using screen height
|
||||||
|
*/
|
||||||
|
return Math.floor((app._mouseY / document.body.clientHeight) * 127);
|
||||||
|
};
|
||||||
32
src/API/DOM/Theme.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { type Editor } from '../../main';
|
||||||
|
import colorschemes from "../../Editor/colors.json";
|
||||||
|
|
||||||
|
export const theme = (app: Editor) => (color_scheme: string): void => {
|
||||||
|
app.readTheme(color_scheme);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const themeName = (app: Editor) => (): string => {
|
||||||
|
return app.currentThemeName;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const randomTheme = (app: Editor) => (): void => {
|
||||||
|
let theme_names = getThemes()();
|
||||||
|
let selected_theme = theme_names[Math.floor(Math.random() * theme_names.length)];
|
||||||
|
if (selected_theme) {
|
||||||
|
app.readTheme(selected_theme);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const nextTheme = (app: Editor) => (): void => {
|
||||||
|
let theme_names = getThemes()();
|
||||||
|
let current_theme = themeName(app)();
|
||||||
|
let current_theme_idx = theme_names.indexOf(current_theme);
|
||||||
|
let next_theme_idx = (current_theme_idx + 1) % theme_names.length;
|
||||||
|
let next_theme = theme_names[next_theme_idx];
|
||||||
|
app.readTheme(next_theme!);
|
||||||
|
app.api.log(next_theme);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getThemes = () => (): string[] => {
|
||||||
|
return Object.keys(colorschemes);
|
||||||
|
};
|
||||||
39
src/API/Drunk.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { type UserAPI } from './API';
|
||||||
|
|
||||||
|
export const drunk = (api: UserAPI) => (n?: number): number => {
|
||||||
|
/**
|
||||||
|
* This function sets or returns the current drunk mechanism's value.
|
||||||
|
* @param n - [optional] The value to set the drunk mechanism to
|
||||||
|
* @returns The current value of the drunk mechanism
|
||||||
|
*/
|
||||||
|
if (n !== undefined) {
|
||||||
|
api._drunk.position = n;
|
||||||
|
return api._drunk.getPosition();
|
||||||
|
}
|
||||||
|
api._drunk.step();
|
||||||
|
return api._drunk.getPosition();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drunk_max = (api: UserAPI) => (max: number): void => {
|
||||||
|
/**
|
||||||
|
* Sets the maximum value of the drunk mechanism.
|
||||||
|
* @param max - The maximum value of the drunk mechanism
|
||||||
|
*/
|
||||||
|
api._drunk.max = max;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drunk_min = (api: UserAPI) => (min: number): void => {
|
||||||
|
/**
|
||||||
|
* Sets the minimum value of the drunk mechanism.
|
||||||
|
* @param min - The minimum value of the drunk mechanism
|
||||||
|
*/
|
||||||
|
api._drunk.min = min;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drunk_wrap = (api: UserAPI) => (wrap: boolean): void => {
|
||||||
|
/**
|
||||||
|
* Sets whether the drunk mechanism should wrap around
|
||||||
|
* @param wrap - Whether the drunk mechanism should wrap around
|
||||||
|
*/
|
||||||
|
api._drunk.toggleWrap(wrap);
|
||||||
|
};
|
||||||
198
src/API/IO/MIDI.ts
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
import { getAllScaleNotes } from 'zifferjs';
|
||||||
|
import {
|
||||||
|
MidiCCEvent,
|
||||||
|
MidiNoteEvent,
|
||||||
|
} from "../../IO/MidiConnection";
|
||||||
|
import { MidiEvent, MidiParams } from "../../Classes/MidiEvent";
|
||||||
|
import { UserAPI } from '../API';
|
||||||
|
import { Editor } from '../../main';
|
||||||
|
|
||||||
|
interface ControlChange {
|
||||||
|
channel: number;
|
||||||
|
control: number;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const midi_outputs = (api: UserAPI) => (): void => {
|
||||||
|
api._logMessage(api.MidiConnection.listMidiOutputs(), false);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const midi_output = (api: UserAPI) => (outputName: string): void => {
|
||||||
|
if (!outputName) {
|
||||||
|
console.log(api.MidiConnection.getCurrentMidiPort());
|
||||||
|
} else {
|
||||||
|
api.MidiConnection.switchMidiOutput(outputName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const midi = (app: Editor) => (
|
||||||
|
value: number | number[] = 60,
|
||||||
|
velocity?: number | number[],
|
||||||
|
channel?: number | number[],
|
||||||
|
port?: number | string | number[] | string[],
|
||||||
|
): MidiEvent => {
|
||||||
|
const event = { note: value, velocity, channel, port } as MidiParams;
|
||||||
|
return new MidiEvent(event, app);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sysex = (api: UserAPI) => (data: Array<number>): void => {
|
||||||
|
api.MidiConnection.sendSysExMessage(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pitch_bend = (api: UserAPI) => (value: number, channel: number): void => {
|
||||||
|
api.MidiConnection.sendPitchBend(value, channel);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const program_change = (api: UserAPI) => (program: number, channel: number): void => {
|
||||||
|
api.MidiConnection.sendProgramChange(program, channel);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const midi_clock = (api: UserAPI) => (): void => {
|
||||||
|
api.MidiConnection.sendMidiClock();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const control_change = (api: UserAPI) => ({
|
||||||
|
control = 20,
|
||||||
|
value = 0,
|
||||||
|
channel = 0,
|
||||||
|
}: ControlChange): void => {
|
||||||
|
api.MidiConnection.sendMidiControlChange(control, value, channel);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cc = control_change;
|
||||||
|
|
||||||
|
export const midi_panic = (api: UserAPI) => (): void => {
|
||||||
|
api.MidiConnection.panic();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const active_note_events = (api: UserAPI) => (
|
||||||
|
channel?: number,
|
||||||
|
): MidiNoteEvent[] | undefined => {
|
||||||
|
let events;
|
||||||
|
if (channel) {
|
||||||
|
events = api.MidiConnection.activeNotesFromChannel(channel);
|
||||||
|
} else {
|
||||||
|
events = api.MidiConnection.activeNotes;
|
||||||
|
}
|
||||||
|
if (events.length > 0) return events;
|
||||||
|
else return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const transmission = (api: UserAPI) => (): boolean => {
|
||||||
|
return api.MidiConnection.activeNotes.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const active_notes = (api: UserAPI) => (channel?: number): number[] | undefined => {
|
||||||
|
const events = active_note_events(api)(channel);
|
||||||
|
if (events && events.length > 0) return events.map((e) => e.note);
|
||||||
|
else return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const kill_active_notes = (api: UserAPI) => (): void => {
|
||||||
|
api.MidiConnection.activeNotes = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sticky_notes = (api: UserAPI) => (channel?: number): number[] | undefined => {
|
||||||
|
let notes;
|
||||||
|
if (channel) notes = api.MidiConnection.stickyNotesFromChannel(channel);
|
||||||
|
else notes = api.MidiConnection.stickyNotes;
|
||||||
|
if (notes.length > 0) return notes.map((e: any) => e.note);
|
||||||
|
else return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const kill_sticky_notes = (api: UserAPI) => (): void => {
|
||||||
|
api.MidiConnection.stickyNotes = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buffer = (api: UserAPI) => (channel?: number): boolean => {
|
||||||
|
if (channel)
|
||||||
|
return (
|
||||||
|
api.MidiConnection.findNoteFromBufferInChannel(channel) !== undefined
|
||||||
|
);
|
||||||
|
else return api.MidiConnection.noteInputBuffer.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buffer_event = (api: UserAPI) => (channel?: number): MidiNoteEvent | undefined => {
|
||||||
|
if (channel)
|
||||||
|
return api.MidiConnection.findNoteFromBufferInChannel(channel);
|
||||||
|
else return api.MidiConnection.noteInputBuffer.shift();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buffer_note = (api: UserAPI) => (channel?: number): number | undefined => {
|
||||||
|
const note = buffer_event(api)(channel);
|
||||||
|
return note ? note.note : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const last_note_event = (api: UserAPI) => (channel?: number): MidiNoteEvent | undefined => {
|
||||||
|
if (channel) return api.MidiConnection.lastNoteInChannel[channel];
|
||||||
|
else return api.MidiConnection.lastNote;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const last_note = (api: UserAPI) => (channel?: number): number => {
|
||||||
|
const note = last_note_event(api)(channel);
|
||||||
|
return note ? note.note : 60;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ccIn = (api: UserAPI) => (control: number, channel?: number): number => {
|
||||||
|
if (channel) {
|
||||||
|
if (api.MidiConnection.lastCCInChannel[channel]) {
|
||||||
|
return api.MidiConnection.lastCCInChannel[channel]?.[control] ?? 0;
|
||||||
|
} else return 0;
|
||||||
|
} else return api.MidiConnection.lastCC[control] ?? 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const has_cc = (api: UserAPI) => (channel?: number): boolean => {
|
||||||
|
if (channel)
|
||||||
|
return (
|
||||||
|
api.MidiConnection.findCCFromBufferInChannel(channel) !== undefined
|
||||||
|
);
|
||||||
|
else return api.MidiConnection.ccInputBuffer.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buffer_cc = (api: UserAPI) => (channel?: number): MidiCCEvent | undefined => {
|
||||||
|
if (channel) return api.MidiConnection.findCCFromBufferInChannel(channel);
|
||||||
|
else return api.MidiConnection.ccInputBuffer.shift();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const show_scale = (api: UserAPI) => (
|
||||||
|
root: number | string,
|
||||||
|
scale: number | string,
|
||||||
|
channel: number = 0,
|
||||||
|
port: number | string = api.MidiConnection.currentOutputIndex || 0,
|
||||||
|
soundOff: boolean = false,
|
||||||
|
): void => {
|
||||||
|
if (!api.scale_aid || scale !== api.scale_aid) {
|
||||||
|
hide_scale(api)(channel, port);
|
||||||
|
const scaleNotes = getAllScaleNotes(scale, root);
|
||||||
|
scaleNotes.forEach((note) => {
|
||||||
|
api.MidiConnection.sendMidiOn(note, channel, 1, port);
|
||||||
|
if (soundOff) api.MidiConnection.sendAllSoundOff(channel, port);
|
||||||
|
});
|
||||||
|
api.scale_aid = scale;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hide_scale = (api: UserAPI) => (
|
||||||
|
channel: number = 0,
|
||||||
|
port: number | string = api.MidiConnection.currentOutputIndex || 0,
|
||||||
|
): void => {
|
||||||
|
const allNotes = Array.from(Array(128).keys());
|
||||||
|
allNotes.forEach((note) => {
|
||||||
|
api.MidiConnection.sendMidiOff(note, channel, port);
|
||||||
|
});
|
||||||
|
api.scale_aid = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const midi_notes_off = (api: UserAPI) => (
|
||||||
|
channel: number = 0,
|
||||||
|
port: number | string = api.MidiConnection.currentOutputIndex || 0,
|
||||||
|
): void => {
|
||||||
|
api.MidiConnection.sendAllNotesOff(channel, port);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const midi_sound_off = (api: UserAPI) => (
|
||||||
|
channel: number = 0,
|
||||||
|
port: number | string = api.MidiConnection.currentOutputIndex || 0,
|
||||||
|
): void => {
|
||||||
|
api.MidiConnection.sendAllSoundOff(channel, port);
|
||||||
|
};
|
||||||
28
src/API/IO/OSC.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { sendToServer, type OSCMessage } from "../../IO/OSC";
|
||||||
|
import { oscMessages } from "../../IO/OSC";
|
||||||
|
import { type Editor } from "../../main";
|
||||||
|
|
||||||
|
export const osc = (app: Editor) => (address: string, port: number, ...args: any[]): void => {
|
||||||
|
/**
|
||||||
|
* Sends an OSC message to the server.
|
||||||
|
*/
|
||||||
|
sendToServer({
|
||||||
|
address: address,
|
||||||
|
port: port,
|
||||||
|
args: args,
|
||||||
|
timetag: Math.round(Date.now() - app.clock.getTimeDeviation()),
|
||||||
|
} as OSCMessage);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getOSC = () => (address?: string): any[] => {
|
||||||
|
/**
|
||||||
|
* Retrieves incoming OSC messages. Filters by address if provided.
|
||||||
|
*/
|
||||||
|
if (address) {
|
||||||
|
let messages = oscMessages.filter((msg: { address: string; }) => msg.address === address);
|
||||||
|
messages = messages.map((msg: { data: any; }) => msg.data);
|
||||||
|
return messages;
|
||||||
|
} else {
|
||||||
|
return oscMessages;
|
||||||
|
}
|
||||||
|
};
|
||||||
68
src/API/LFO.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { Editor } from "../main";
|
||||||
|
import { UserAPI } from "./API";
|
||||||
|
|
||||||
|
export const line = () => (start: number, end: number, step: number = 1): number[] => {
|
||||||
|
const countPlaces = (num: number) => {
|
||||||
|
var text = num.toString();
|
||||||
|
var index = text.indexOf(".");
|
||||||
|
return index == -1 ? 0 : (text.length - index - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const result: number[] = [];
|
||||||
|
|
||||||
|
if ((end > start && step > 0) || (end < start && step < 0)) {
|
||||||
|
for (let value = start; value <= end; value += step) {
|
||||||
|
result.push(value);
|
||||||
|
}
|
||||||
|
} else if ((end > start && step < 0) || (end < start && step > 0)) {
|
||||||
|
for (let value = start; value >= end; value -= step) {
|
||||||
|
result.push(parseFloat(value.toFixed(countPlaces(step))));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("Invalid range or step provided.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sine = (app: Editor) => (freq: number = 1, phase: number = 0): number => {
|
||||||
|
return Math.sin(2 * Math.PI * freq * (app.clock.ctx.currentTime - phase));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usine = (app: Editor) => (freq: number = 1, phase: number = 0): number => {
|
||||||
|
return ((sine(app)(freq, phase) + 1) / 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const saw = (app: Editor) => (freq: number = 1, phase: number = 0): number => {
|
||||||
|
return (((app.clock.ctx.currentTime * freq + phase) % 1) * 2 - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usaw = (app: Editor) => (freq: number = 1, phase: number = 0): number => {
|
||||||
|
return ((saw(app)(freq, phase) + 1) / 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const triangle = (app: Editor) => (freq: number = 1, phase: number = 0): number => {
|
||||||
|
return (Math.abs(saw(app)(freq, phase)) * 2 - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const utriangle = (app: Editor) => (freq: number = 1, phase: number = 0): number => {
|
||||||
|
return ((triangle(app)(freq, phase) + 1) / 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const square = (app: Editor) => (freq: number = 1, duty: number = 0.5): number => {
|
||||||
|
const period = 1 / freq;
|
||||||
|
const t = (app.clock.ctx.currentTime % period);
|
||||||
|
return (t / period < duty ? 1 : -1);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usquare = (app: Editor) => (freq: number = 1, duty: number = 0.5): number => {
|
||||||
|
return ((square(app)(freq, duty) + 1) / 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const noise = (api: UserAPI) => (): number => {
|
||||||
|
return (api.randomGen() * 2 - 1); // Assuming randomGen() is defined in the app context
|
||||||
|
};
|
||||||
|
|
||||||
|
export const unoise = (api: UserAPI) => (): number => {
|
||||||
|
return ((noise(api)() + 1) / 2);
|
||||||
|
};
|
||||||
36
src/API/Math.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// mathFunctions.ts
|
||||||
|
export const min = () => (...values: number[]): number => {
|
||||||
|
/**
|
||||||
|
* Returns the minimum value of a list of numbers.
|
||||||
|
*/
|
||||||
|
return Math.min(...values);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const max = () => (...values: number[]): number => {
|
||||||
|
/**
|
||||||
|
* Returns the maximum value of a list of numbers.
|
||||||
|
*/
|
||||||
|
return Math.max(...values);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mean = () => (...values: number[]): number => {
|
||||||
|
/**
|
||||||
|
* Returns the mean of a list of numbers.
|
||||||
|
*/
|
||||||
|
const sum = values.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
|
||||||
|
return values.length > 0 ? sum / values.length : 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const limit = () => (value: number, min: number, max: number): number => {
|
||||||
|
/**
|
||||||
|
* Limits a value between a minimum and a maximum.
|
||||||
|
*/
|
||||||
|
return Math.min(Math.max(value, min), max);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const abs = () => (value: number): number => {
|
||||||
|
/**
|
||||||
|
* Returns the absolute value of a number.
|
||||||
|
*/
|
||||||
|
return Math.abs(value);
|
||||||
|
};
|
||||||
53
src/API/Probabilities.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { type UserAPI } from "./API";
|
||||||
|
|
||||||
|
export const prob = (api: UserAPI) => (p: number): boolean => {
|
||||||
|
return api.randomGen() * 100 < p;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toss = (api: UserAPI) => (): boolean => {
|
||||||
|
return api.randomGen() > 0.5;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const odds = (api: UserAPI) => (n: number, beats: number = 1): boolean => {
|
||||||
|
return api.randomGen() < (n * api.ppqn()) / (api.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const never = () => (): boolean => {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const almostNever = (api: UserAPI) => (beats: number = 1): boolean => {
|
||||||
|
return api.randomGen() < (0.025 * api.ppqn()) / (api.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rarely = (api: UserAPI) => (beats: number = 1): boolean => {
|
||||||
|
return api.randomGen() < (0.1 * api.ppqn()) / (api.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const scarcely = (api: UserAPI) => (beats: number = 1): boolean => {
|
||||||
|
return api.randomGen() < (0.25 * api.ppqn()) / (api.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sometimes = (api: UserAPI) => (beats: number = 1): boolean => {
|
||||||
|
return api.randomGen() < (0.5 * api.ppqn()) / (api.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const often = (api: UserAPI) => (beats: number = 1): boolean => {
|
||||||
|
return api.randomGen() < (0.75 * api.ppqn()) / (api.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const frequently = (api: UserAPI) => (beats: number = 1): boolean => {
|
||||||
|
return api.randomGen() < (0.9 * api.ppqn()) / (api.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const almostAlways = (api: UserAPI) => (beats: number = 1): boolean => {
|
||||||
|
return api.randomGen() < (0.985 * api.ppqn()) / (api.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const always = () => (): boolean => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dice = (api: UserAPI) => (sides: number): number => {
|
||||||
|
return Math.floor(api.randomGen() * sides) + 1;
|
||||||
|
};
|
||||||
35
src/API/Randomness.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { seededRandom } from "zifferjs";
|
||||||
|
import { UserAPI } from "./API";
|
||||||
|
|
||||||
|
export const randI = (api: UserAPI) => (min: number, max: number): number => {
|
||||||
|
return Math.floor(api.randomGen() * (max - min + 1)) + min;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rand = (api: UserAPI) => (min: number, max: number): number => {
|
||||||
|
return api.randomGen() * (max - min) + min;
|
||||||
|
};
|
||||||
|
export const r = rand
|
||||||
|
|
||||||
|
export const seed = (api: UserAPI) => (seed: string | number): void => {
|
||||||
|
if (typeof seed === "number") seed = seed.toString();
|
||||||
|
if (api.currentSeed !== seed) {
|
||||||
|
api.currentSeed = seed;
|
||||||
|
api.randomGen = seededRandom(seed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const localSeededRandom = (api: UserAPI) => (seed: string | number): Function => {
|
||||||
|
if (typeof seed === "number") seed = seed.toString();
|
||||||
|
if (api.localSeeds.has(seed)) return api.localSeeds.get(seed) as Function;
|
||||||
|
const newSeededRandom = seededRandom(seed);
|
||||||
|
api.localSeeds.set(seed, newSeededRandom);
|
||||||
|
return newSeededRandom;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clearLocalSeed = (api: UserAPI) => (seed: string | number | undefined = undefined): void => {
|
||||||
|
if (seed) {
|
||||||
|
api.localSeeds.delete(seed.toString());
|
||||||
|
} else {
|
||||||
|
api.localSeeds.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
64
src/API/Script.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { tryEvaluate } from "../Evaluator";
|
||||||
|
import { blinkScript } from "../DOM/Visuals/Blinkers";
|
||||||
|
import { template_universes } from "../Editor/FileManagement";
|
||||||
|
import { Editor } from "../main";
|
||||||
|
|
||||||
|
export const script = (app: Editor) => (...args: number[]): void => {
|
||||||
|
args.forEach((arg) => {
|
||||||
|
if (arg >= 1 && arg <= 9) {
|
||||||
|
blinkScript(app, "local", arg);
|
||||||
|
tryEvaluate(
|
||||||
|
app,
|
||||||
|
app.universes[app.selected_universe]!.locals[arg]!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const s = script;
|
||||||
|
|
||||||
|
export const delete_script = (app: Editor) => (script: number): void => {
|
||||||
|
app.universes[app.selected_universe]!.locals[script] = {
|
||||||
|
candidate: "",
|
||||||
|
committed: "",
|
||||||
|
evaluations: 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const copy_script = (app: Editor) => (from: number, to: number): void => {
|
||||||
|
//@ts-ignore
|
||||||
|
app.universes[app.selected_universe].locals[to] = {
|
||||||
|
...app.universes[app.selected_universe]!.locals[from],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const copy_universe = (app: Editor) => (from: string, to: string): void => {
|
||||||
|
//@ts-ignore
|
||||||
|
app.universes[to] = { ...app.universes[from], };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const delete_universe = (app: Editor) => (universe: string): void => {
|
||||||
|
if (app.selected_universe === universe) {
|
||||||
|
app.selected_universe = "Default";
|
||||||
|
}
|
||||||
|
delete app.universes[universe];
|
||||||
|
app.settings.saveApplicationToLocalStorage(
|
||||||
|
app.universes,
|
||||||
|
app.settings,
|
||||||
|
);
|
||||||
|
app.updateKnownUniversesView();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const big_bang = (app: Editor) => (): void => {
|
||||||
|
if (confirm("Are you sure you want to delete all universes?")) {
|
||||||
|
app.universes = {
|
||||||
|
...template_universes, // Assuming template_universes is defined elsewhere
|
||||||
|
};
|
||||||
|
app.settings.saveApplicationToLocalStorage(
|
||||||
|
app.universes,
|
||||||
|
app.settings,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
app.selected_universe = "Default";
|
||||||
|
app.updateKnownUniversesView();
|
||||||
|
};
|
||||||
44
src/API/Sound.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { SoundEvent } from "../Classes/SoundEvent";
|
||||||
|
import { SkipEvent } from "../Classes/SkipEvent";
|
||||||
|
import { Editor } from "../main";
|
||||||
|
|
||||||
|
export const sound = (app: Editor) => (sound: string | string[] | null | undefined) => {
|
||||||
|
/**
|
||||||
|
* Creates a sound event if a sound is specified, otherwise returns a skip event.
|
||||||
|
* @param sound - The sound identifier or array of identifiers to play.
|
||||||
|
* @returns SoundEvent if sound is defined, otherwise SkipEvent.
|
||||||
|
*/
|
||||||
|
if (sound) return new SoundEvent(sound, app);
|
||||||
|
else return new SkipEvent();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const snd = sound;
|
||||||
|
|
||||||
|
export const speak = () => (text: string, lang: string = "en-US", voiceIndex: number = 0, rate: number = 1, pitch: number = 1): void => {
|
||||||
|
/**
|
||||||
|
* Speaks the given text using the browser's speech synthesis API.
|
||||||
|
* @param text - The text to speak.
|
||||||
|
* @param lang - The language code (e.g., "en-US").
|
||||||
|
* @param voiceIndex - The index of the voice to use from the speechSynthesis voice list.
|
||||||
|
* @param rate - The rate at which to speak the text.
|
||||||
|
* @param pitch - The pitch at which to speak the text.
|
||||||
|
*/
|
||||||
|
const msg = new SpeechSynthesisUtterance(text);
|
||||||
|
msg.lang = lang;
|
||||||
|
msg.rate = rate;
|
||||||
|
msg.pitch = pitch;
|
||||||
|
|
||||||
|
// Set the voice using a provided index
|
||||||
|
const voices = window.speechSynthesis.getVoices();
|
||||||
|
msg.voice = voices[voiceIndex] || null;
|
||||||
|
|
||||||
|
window.speechSynthesis.speak(msg);
|
||||||
|
|
||||||
|
msg.onend = () => {
|
||||||
|
console.log("Finished speaking:", text);
|
||||||
|
};
|
||||||
|
|
||||||
|
msg.onerror = (event) => {
|
||||||
|
console.error("Speech synthesis error:", event);
|
||||||
|
};
|
||||||
|
};
|
||||||
215
src/API/Time/Filters.ts
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
import { type Editor } from "../../main";
|
||||||
|
import { UserAPI } from "../API";
|
||||||
|
|
||||||
|
const _euclidean_cycle = (
|
||||||
|
pulses: number,
|
||||||
|
length: number,
|
||||||
|
rotate: number = 0,
|
||||||
|
): boolean[] => {
|
||||||
|
if (pulses == length) return Array.from({ length }, () => true);
|
||||||
|
function startsDescent(list: number[], i: number): boolean {
|
||||||
|
const length = list.length;
|
||||||
|
const nextIndex = (i + 1) % length;
|
||||||
|
return list[i]! > list[nextIndex]!? true : false;
|
||||||
|
}
|
||||||
|
if (pulses >= length) return [true];
|
||||||
|
const resList = Array.from(
|
||||||
|
{ length },
|
||||||
|
(_, i) => (((pulses * (i - 1)) % length) + length) % length,
|
||||||
|
);
|
||||||
|
let cycle = resList.map((_, i) => startsDescent(resList, i));
|
||||||
|
if (rotate != 0) {
|
||||||
|
cycle = cycle.slice(rotate).concat(cycle.slice(0, rotate));
|
||||||
|
}
|
||||||
|
return cycle;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fullseq = () => (sequence: string, duration: number): boolean | Array<boolean> => {
|
||||||
|
if (sequence.split("").every((c) => c === "x" || c === "o")) {
|
||||||
|
return [...sequence].map((c) => c === "x").beat(duration);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const seq = (app: any) => (expr: string, duration: number = 0.5): boolean => {
|
||||||
|
let len = expr.length * duration;
|
||||||
|
let output: number[] = [];
|
||||||
|
|
||||||
|
for (let i = 1; i <= len + 1; i += duration) {
|
||||||
|
output.push(Math.floor(i * 10) / 10);
|
||||||
|
}
|
||||||
|
output.pop();
|
||||||
|
|
||||||
|
output = output.filter((_, idx) => {
|
||||||
|
const exprIdx = idx % expr.length;
|
||||||
|
return expr[exprIdx] === "x";
|
||||||
|
});
|
||||||
|
|
||||||
|
return oncount(app)(output, len);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const beat = (app: Editor) => (n: number | number[] = 1, nudge: number = 0): boolean => {
|
||||||
|
const nArray = Array.isArray(n) ? n : [n];
|
||||||
|
const results: boolean[] = nArray.map(
|
||||||
|
(value) =>
|
||||||
|
(app.clock.time_position.grain - Math.round(nudge * app.clock.time_position.ppqn)) %
|
||||||
|
Math.round(value * app.clock.time_position.ppqn) === 0,
|
||||||
|
);
|
||||||
|
return results.some((value) => value === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// export const beat = (app: Editor) => (n: number | number[] = 1, nudge: number = 0): boolean => {
|
||||||
|
// const nArray = !Array.isArray(n) ? [n] : n;
|
||||||
|
// return nArray.some(
|
||||||
|
// (value) =>
|
||||||
|
// !((app.clock.time_position.grain - nudge * app.clock.time_position.ppqn) % Math.floor(value * app.clock.time_position.ppqn))
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
export const bar = (app: Editor) => (n: number | number[] = 1, nudge: number = 0): boolean => {
|
||||||
|
const nArray = Array.isArray(n) ? n : [n];
|
||||||
|
const barLength = app.clock.time_position.num * app.clock.ppqn;
|
||||||
|
const nudgeInPulses = Math.floor(nudge * barLength);
|
||||||
|
const results: boolean[] = nArray.map(
|
||||||
|
(value) =>
|
||||||
|
(app.clock.grain - nudgeInPulses) %
|
||||||
|
Math.floor(value * barLength) === 0,
|
||||||
|
);
|
||||||
|
return results.some((value) => value === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pulse = (app: Editor) => (n: number | number[] = 1, nudge: number = 0): boolean => {
|
||||||
|
const nArray = Array.isArray(n) ? n : [n];
|
||||||
|
const results: boolean[] = nArray.map(
|
||||||
|
(value) => (app.clock.grain - nudge) % value === 0,
|
||||||
|
);
|
||||||
|
return results.some((value) => value === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tick = (app: Editor) => (tick: number | number[], offset: number = 0): boolean => {
|
||||||
|
const nArray = Array.isArray(tick) ? tick : [tick];
|
||||||
|
const results: boolean[] = nArray.map(
|
||||||
|
(value) => app.clock.time_position.tick === value + offset,
|
||||||
|
);
|
||||||
|
return results.some((value) => value === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dur = (app: Editor) => (n: number | number[]): boolean => {
|
||||||
|
let nums: number[] = Array.isArray(n) ? n : [n];
|
||||||
|
return beat(app)(nums.dur(...nums));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const flip = (app: Editor) => (chunk: number, ratio: number = 50): boolean => {
|
||||||
|
let realChunk = chunk * 2;
|
||||||
|
const time_pos = app.clock.grain;
|
||||||
|
const full_chunk = Math.floor(realChunk * app.clock.ppqn);
|
||||||
|
const threshold = Math.floor((ratio / 100) * full_chunk);
|
||||||
|
const pos_within_chunk = time_pos % full_chunk;
|
||||||
|
return pos_within_chunk < threshold;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const flipbar = (app: Editor) => (chunk: number = 1): boolean => {
|
||||||
|
let realFlip = chunk;
|
||||||
|
const time_pos = app.clock.time_position.bar;
|
||||||
|
const current_chunk = Math.floor(time_pos / realFlip);
|
||||||
|
return current_chunk % 2 === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onbar = (app: Editor) => (
|
||||||
|
bars: number[] | number,
|
||||||
|
n: number = app.clock.time_position.num,
|
||||||
|
): boolean => {
|
||||||
|
let current_bar = (app.clock.time_position.bar % n) + 1;
|
||||||
|
return typeof bars === "number"
|
||||||
|
? bars === current_bar
|
||||||
|
: bars.some((b) => b === current_bar);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onbeat = (api: UserAPI) => (...beat: number[]): boolean => {
|
||||||
|
let final_pulses: boolean[] = [];
|
||||||
|
beat.forEach((b) => {
|
||||||
|
let beatNumber = b % api.nominator() || api.nominator();
|
||||||
|
let integral_part = Math.floor(beatNumber);
|
||||||
|
integral_part = integral_part === 0 ? api.nominator() : integral_part;
|
||||||
|
let decimal_part = Math.floor((beatNumber - integral_part) * api.app.clock.ppqn + 1);
|
||||||
|
if (decimal_part <= 0)
|
||||||
|
decimal_part += api.app.clock.ppqn * api.nominator();
|
||||||
|
final_pulses.push(
|
||||||
|
integral_part === api.cbeat() && api.cpulse() === decimal_part,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return final_pulses.some((p) => p === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const oncount = (app: Editor) => (beats: number[] | number, count: number): boolean => {
|
||||||
|
if (typeof beats === "number") beats = [beats];
|
||||||
|
const origin = app.clock.grain;
|
||||||
|
let final_pulses: boolean[] = [];
|
||||||
|
beats.forEach((b) => {
|
||||||
|
b = b < 1 ? 0 : b - 1;
|
||||||
|
const beatInTicks = Math.ceil(b * app.clock.ppqn);
|
||||||
|
const meterPosition = origin % (app.clock.ppqn * count);
|
||||||
|
final_pulses.push(meterPosition === beatInTicks);
|
||||||
|
});
|
||||||
|
return final_pulses.some((p) => p === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const oneuclid = (app: Editor) => (pulses: number, length: number, rotate: number = 0): boolean => {
|
||||||
|
const cycle = _euclidean_cycle(pulses, length, rotate);
|
||||||
|
const beats = cycle.reduce((acc: number[], x: boolean, i: number) => {
|
||||||
|
if (x) acc.push(i + 1);
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
return oncount(app)(beats, length);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const euclid = () => (iterator: number, pulses: number, length: number, rotate: number = 0): boolean => {
|
||||||
|
/**
|
||||||
|
* Returns a Euclidean cycle of size length, with n pulses, rotated or not.
|
||||||
|
*/
|
||||||
|
const cycle = _euclidean_cycle(pulses, length, rotate);
|
||||||
|
return cycle && cycle[iterator % length] === true;
|
||||||
|
};
|
||||||
|
export const ec = euclid;
|
||||||
|
|
||||||
|
export const rhythm = (app: Editor) => (div: number, pulses: number, length: number, rotate: number = 0): boolean => {
|
||||||
|
/**
|
||||||
|
* Returns a rhythm based on Euclidean cycle.
|
||||||
|
*/
|
||||||
|
return (
|
||||||
|
beat(app)(div) && _euclidean_cycle(pulses, length, rotate).beat(div)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const ry = rhythm;
|
||||||
|
|
||||||
|
export const nrhythm = (app: Editor) => (div: number, pulses: number, length: number, rotate: number = 0): boolean => {
|
||||||
|
/**
|
||||||
|
* Returns a negated rhythm based on Euclidean cycle.
|
||||||
|
*/
|
||||||
|
let rhythm = _euclidean_cycle(pulses, length, rotate).map((n: any) => !n);
|
||||||
|
return (
|
||||||
|
beat(app)(div) && rhythm.beat(div)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const nry = nrhythm;
|
||||||
|
|
||||||
|
export const bin = () => (iterator: number, n: number): boolean => {
|
||||||
|
/**
|
||||||
|
* Returns a binary cycle of size n.
|
||||||
|
*/
|
||||||
|
let convert: string = n.toString(2);
|
||||||
|
let tobin: boolean[] = convert.split("").map((x: string) => x === "1");
|
||||||
|
return tobin[iterator % tobin.length] || false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const binrhythm = (app: Editor) => (div: number, n: number): boolean => {
|
||||||
|
/**
|
||||||
|
* Returns a binary rhythm based on division and binary cycle.
|
||||||
|
*/
|
||||||
|
let convert: string = n.toString(2);
|
||||||
|
let tobin: boolean[] = convert.split("").map((x: string) => x === "1");
|
||||||
|
return beat(app)(div) && tobin.beat(div);
|
||||||
|
};
|
||||||
|
export const bry = binrhythm;
|
||||||
103
src/API/Time/Transport.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import { type UserAPI } from "../API";
|
||||||
|
import { type Editor } from "../../main";
|
||||||
|
|
||||||
|
export const time = (api: UserAPI) => (): number => {
|
||||||
|
return api.app.audioContext.currentTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const play = (api: UserAPI) => (): void => {
|
||||||
|
api.app.setButtonHighlighting("play", true);
|
||||||
|
api.MidiConnection.sendStartMessage();
|
||||||
|
api.app.clock.start();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pause = (api: UserAPI) => (): void => {
|
||||||
|
api.app.setButtonHighlighting("pause", true);
|
||||||
|
api.app.clock.pause();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stop = (api: UserAPI) => (): void => {
|
||||||
|
api.app.setButtonHighlighting("stop", true);
|
||||||
|
api.app.clock.stop();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const silence = (api: UserAPI) => (): void => {
|
||||||
|
return stop(api)();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tempo = (app: Editor) => (n?: number): number => {
|
||||||
|
/**
|
||||||
|
* Sets or returns the current bpm.
|
||||||
|
*/
|
||||||
|
if (n === undefined) return app.clock.bpm;
|
||||||
|
|
||||||
|
if (n >= 1 && n <= 500) {
|
||||||
|
app.clock.bpm = n;
|
||||||
|
} else {
|
||||||
|
console.error("BPM out of acceptable range (1-500).");
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ppqn = (app: Editor) => (n?: number): number => {
|
||||||
|
/**
|
||||||
|
* Sets or returns the number of pulses per quarter note.
|
||||||
|
*/
|
||||||
|
if (n === undefined) return app.clock.ppqn;
|
||||||
|
|
||||||
|
if (n >= 1) {
|
||||||
|
app.clock.ppqn = n;
|
||||||
|
} else {
|
||||||
|
console.error("Pulses per quarter note must be at least 1.");
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const time_signature = (app: Editor) => (numerator: number, denominator: number): void => {
|
||||||
|
/**
|
||||||
|
* Sets the time signature.
|
||||||
|
*/
|
||||||
|
if (numerator < 1 || denominator < 1) {
|
||||||
|
console.error("Time signature values must be at least 1.");
|
||||||
|
} else {
|
||||||
|
app.clock.setSignature(numerator, denominator);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cbar = (app: Editor) => (): number => {
|
||||||
|
return app.clock.time_position.bar + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ctick = (app: Editor) => (): number => {
|
||||||
|
return app.clock.grain + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cpulse = (app: Editor) => (): number => {
|
||||||
|
return app.clock.time_position.tick + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cbeat = (app: Editor) => (): number => {
|
||||||
|
return app.clock.time_position.beat + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ebeat = (app: Editor) => (): number => {
|
||||||
|
return app.clock.beats_since_origin + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const epulse = (app: Editor) => (): number => {
|
||||||
|
return app.clock.grain + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const nominator = (app: Editor) => (): number => {
|
||||||
|
return app.clock.time_position.num;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const meter = (app: Editor) => (): number => {
|
||||||
|
return app.clock.time_position.den;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const denominator = meter;
|
||||||
|
|
||||||
|
export const pulsesForBar = (app: Editor) => (): number => {
|
||||||
|
return (app.clock.bpm * app.clock.ppqn * nominator(app)()) / 60;
|
||||||
|
};
|
||||||
18
src/API/Time/Warp.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Editor } from "../../main";
|
||||||
|
|
||||||
|
export const warp = (app: Editor) => (n: number): void => {
|
||||||
|
/**
|
||||||
|
* Time-warp the clock by using the tick you wish to jump to.
|
||||||
|
*/
|
||||||
|
app.clock.time_position.tick = n;
|
||||||
|
app.clock.time_position = app.clock.convertTicksToTimeposition(n);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const beat_warp = (app: Editor) => (beat: number): void => {
|
||||||
|
/**
|
||||||
|
* Time-warp the clock by using the tick you wish to jump to.
|
||||||
|
*/
|
||||||
|
const ticks = beat * app.clock.ppqn;
|
||||||
|
app.clock.time_position.tick = ticks;
|
||||||
|
app.clock.time_position = app.clock.convertTicksToTimeposition(ticks);
|
||||||
|
};
|
||||||
72
src/API/Ziffers.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { InputOptions, Player } from "../Classes/ZPlayer";
|
||||||
|
import { UserAPI } from "./API";
|
||||||
|
import { generateCacheKey, removePatternFromCache } from "./Cache"
|
||||||
|
|
||||||
|
export const z = (api: UserAPI) => (input: string | Generator<number>, options: InputOptions = {}, id: number | string = ""): Player => {
|
||||||
|
const zid = "z" + id.toString();
|
||||||
|
const key = id === "" ? generateCacheKey()(input, options) : zid;
|
||||||
|
|
||||||
|
const validSyntax = typeof input === "string" && !api.invalidPatterns[input]
|
||||||
|
|
||||||
|
let player;
|
||||||
|
let replace = false;
|
||||||
|
|
||||||
|
if (api.patternCache.has(key)) {
|
||||||
|
player = api.patternCache.get(key) as Player;
|
||||||
|
|
||||||
|
if (typeof input === "string" &&
|
||||||
|
player.input !== input &&
|
||||||
|
(player.atTheBeginning() || api.forceEvaluator)) {
|
||||||
|
replace = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((typeof input !== "string" || validSyntax) && (!player || replace)) {
|
||||||
|
if (typeof input === "string" && player && api.forceEvaluator) {
|
||||||
|
if (!player.updatePattern(input, options)) {
|
||||||
|
api.logOnce(`Invalid syntax: ${input}`);
|
||||||
|
};
|
||||||
|
api.forceEvaluator = false;
|
||||||
|
} else {
|
||||||
|
const newPlayer = player ? new Player(input, options, api.app, zid, player.nextEndTime()) : new Player(input, options, api.app, zid);
|
||||||
|
if (newPlayer.isValid()) {
|
||||||
|
player = newPlayer;
|
||||||
|
api.patternCache.set(key, player);
|
||||||
|
} else if (typeof input === "string") {
|
||||||
|
api.invalidPatterns[input] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player) {
|
||||||
|
if (player.atTheBeginning()) {
|
||||||
|
if (typeof input === "string" && !validSyntax) api.log(`Invalid syntax: ${input}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.ziffers.generator && player.ziffers.generatorDone) {
|
||||||
|
removePatternFromCache(api)(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof id === "number") player.zid = zid;
|
||||||
|
|
||||||
|
player.updateLastCallTime();
|
||||||
|
|
||||||
|
if (id !== "" && zid !== "z0") {
|
||||||
|
// Sync named patterns to z0 by default
|
||||||
|
player.sync("z0", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return player;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Invalid syntax: ${input}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generating numbered functions dynamically
|
||||||
|
export const generateZFunctions = (api: UserAPI) => {
|
||||||
|
const zFunctions: { [key: string]: (input: string, opts: InputOptions) => Player } = {};
|
||||||
|
for (let i = 0; i <= 16; i++) {
|
||||||
|
zFunctions[`z${i}`] = (input: string, opts: InputOptions = {}) => z(api)(input, opts, i);
|
||||||
|
}
|
||||||
|
return zFunctions;
|
||||||
|
};
|
||||||
259
src/Clock.ts
@ -1,259 +0,0 @@
|
|||||||
// @ts-ignore
|
|
||||||
import { TransportNode } from "./TransportNode";
|
|
||||||
import TransportProcessor from "./TransportProcessor?worker&url";
|
|
||||||
import { Editor } from "./main";
|
|
||||||
|
|
||||||
export interface TimePosition {
|
|
||||||
/**
|
|
||||||
* A position in time.
|
|
||||||
*
|
|
||||||
* @param bar - The bar number
|
|
||||||
* @param beat - The beat number
|
|
||||||
* @param pulse - The pulse number
|
|
||||||
*/
|
|
||||||
bar: number;
|
|
||||||
beat: number;
|
|
||||||
pulse: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Clock {
|
|
||||||
/**
|
|
||||||
* The Clock Class is responsible for keeping track of the current time.
|
|
||||||
* It is also responsible for starting and stopping the Clock TransportNode.
|
|
||||||
*
|
|
||||||
* @param app - The main application instance
|
|
||||||
* @param ctx - The current AudioContext used by app
|
|
||||||
* @param transportNode - The TransportNode helper
|
|
||||||
* @param bpm - The current beats per minute value
|
|
||||||
* @param time_signature - The time signature
|
|
||||||
* @param time_position - The current time position
|
|
||||||
* @param ppqn - The pulses per quarter note
|
|
||||||
* @param tick - The current tick since origin
|
|
||||||
* @param running - Is the clock running?
|
|
||||||
* @param lastPauseTime - The last time the clock was paused
|
|
||||||
* @param lastPlayPressTime - The last time the clock was started
|
|
||||||
* @param totalPauseTime - The total time the clock has been paused / stopped
|
|
||||||
*/
|
|
||||||
|
|
||||||
ctx: AudioContext;
|
|
||||||
logicalTime: number;
|
|
||||||
transportNode: TransportNode | null;
|
|
||||||
private _bpm: number;
|
|
||||||
time_signature: number[];
|
|
||||||
time_position: TimePosition;
|
|
||||||
private _ppqn: number;
|
|
||||||
tick: number;
|
|
||||||
running: boolean;
|
|
||||||
lastPauseTime: number;
|
|
||||||
lastPlayPressTime: number;
|
|
||||||
totalPauseTime: number;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
public app: Editor,
|
|
||||||
ctx: AudioContext,
|
|
||||||
) {
|
|
||||||
this.time_position = { bar: 0, beat: 0, pulse: 0 };
|
|
||||||
this.time_signature = [4, 4];
|
|
||||||
this.logicalTime = 0;
|
|
||||||
this.tick = 0;
|
|
||||||
this._bpm = 120;
|
|
||||||
this._ppqn = 48;
|
|
||||||
this.transportNode = null;
|
|
||||||
this.ctx = ctx;
|
|
||||||
this.running = true;
|
|
||||||
this.lastPauseTime = 0;
|
|
||||||
this.lastPlayPressTime = 0;
|
|
||||||
this.totalPauseTime = 0;
|
|
||||||
ctx.audioWorklet
|
|
||||||
.addModule(TransportProcessor)
|
|
||||||
.then((e) => {
|
|
||||||
this.transportNode = new TransportNode(ctx, {}, this.app);
|
|
||||||
this.transportNode.connect(ctx.destination);
|
|
||||||
return e;
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.log("Error loading TransportProcessor.js:", e);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
convertTicksToTimeposition(ticks: number): TimePosition {
|
|
||||||
/**
|
|
||||||
* Converts ticks to a TimePosition object.
|
|
||||||
* @param ticks The number of ticks to convert.
|
|
||||||
* @returns The TimePosition object representing the converted ticks.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const beatsPerBar = this.app.clock.time_signature[0];
|
|
||||||
const ppqnPosition = ticks % this.app.clock.ppqn;
|
|
||||||
const beatNumber = Math.floor(ticks / this.app.clock.ppqn);
|
|
||||||
const barNumber = Math.floor(beatNumber / beatsPerBar);
|
|
||||||
const beatWithinBar = Math.floor(beatNumber % beatsPerBar);
|
|
||||||
return { bar: barNumber, beat: beatWithinBar, pulse: ppqnPosition };
|
|
||||||
}
|
|
||||||
|
|
||||||
get ticks_before_new_bar(): number {
|
|
||||||
/**
|
|
||||||
* This function returns the number of ticks separating the current moment
|
|
||||||
* from the beginning of the next bar.
|
|
||||||
*
|
|
||||||
* @returns number of ticks until next bar
|
|
||||||
*/
|
|
||||||
const ticskMissingFromBeat = this.ppqn - this.time_position.pulse;
|
|
||||||
const beatsMissingFromBar = this.beats_per_bar - this.time_position.beat;
|
|
||||||
return beatsMissingFromBar * this.ppqn + ticskMissingFromBeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
get next_beat_in_ticks(): number {
|
|
||||||
/**
|
|
||||||
* This function returns the number of ticks separating the current moment
|
|
||||||
* from the beginning of the next beat.
|
|
||||||
*
|
|
||||||
* @returns number of ticks until next beat
|
|
||||||
*/
|
|
||||||
return this.app.clock.pulses_since_origin + this.time_position.pulse;
|
|
||||||
}
|
|
||||||
|
|
||||||
get beats_per_bar(): number {
|
|
||||||
/**
|
|
||||||
* Returns the number of beats per bar.
|
|
||||||
*/
|
|
||||||
return this.time_signature[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
get beats_since_origin(): number {
|
|
||||||
/**
|
|
||||||
* Returns the number of beats since the origin.
|
|
||||||
*
|
|
||||||
* @returns number of beats since origin
|
|
||||||
*/
|
|
||||||
return Math.floor(this.tick / this.ppqn);
|
|
||||||
}
|
|
||||||
|
|
||||||
get pulses_since_origin(): number {
|
|
||||||
/**
|
|
||||||
* Returns the number of pulses since the origin.
|
|
||||||
*
|
|
||||||
* @returns number of pulses since origin
|
|
||||||
*/
|
|
||||||
return this.tick;
|
|
||||||
}
|
|
||||||
|
|
||||||
get pulse_duration(): number {
|
|
||||||
/**
|
|
||||||
* Returns the duration of a pulse in seconds.
|
|
||||||
*/
|
|
||||||
return 60 / this.bpm / this.ppqn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public pulse_duration_at_bpm(bpm: number = this.bpm): number {
|
|
||||||
/**
|
|
||||||
* Returns the duration of a pulse in seconds at a specific bpm.
|
|
||||||
*/
|
|
||||||
return 60 / bpm / this.ppqn;
|
|
||||||
}
|
|
||||||
|
|
||||||
get bpm(): number {
|
|
||||||
return this._bpm;
|
|
||||||
}
|
|
||||||
|
|
||||||
set nudge(nudge: number) {
|
|
||||||
this.transportNode?.setNudge(nudge);
|
|
||||||
}
|
|
||||||
|
|
||||||
set bpm(bpm: number) {
|
|
||||||
if (bpm > 0 && this._bpm !== bpm) {
|
|
||||||
this.transportNode?.setBPM(bpm);
|
|
||||||
this._bpm = bpm;
|
|
||||||
this.logicalTime = this.realTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get ppqn(): number {
|
|
||||||
return this._ppqn;
|
|
||||||
}
|
|
||||||
|
|
||||||
get realTime(): number {
|
|
||||||
return this.app.audioContext.currentTime - this.totalPauseTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
get deviation(): number {
|
|
||||||
return Math.abs(this.logicalTime - this.realTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
set ppqn(ppqn: number) {
|
|
||||||
if (ppqn > 0 && this._ppqn !== ppqn) {
|
|
||||||
this._ppqn = ppqn;
|
|
||||||
this.transportNode?.setPPQN(ppqn);
|
|
||||||
this.logicalTime = this.realTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public incrementTick(bpm: number) {
|
|
||||||
this.tick++;
|
|
||||||
this.logicalTime += this.pulse_duration_at_bpm(bpm);
|
|
||||||
}
|
|
||||||
|
|
||||||
public nextTickFrom(time: number, nudge: number): number {
|
|
||||||
/**
|
|
||||||
* Compute the time remaining before the next clock tick.
|
|
||||||
* @param time - audio context currentTime
|
|
||||||
* @param nudge - nudge in the future (in seconds)
|
|
||||||
* @returns remainingTime
|
|
||||||
*/
|
|
||||||
const pulseDuration = this.pulse_duration;
|
|
||||||
const nudgedTime = time + nudge;
|
|
||||||
const nextTickTime = Math.ceil(nudgedTime / pulseDuration) * pulseDuration;
|
|
||||||
const remainingTime = nextTickTime - nudgedTime;
|
|
||||||
|
|
||||||
return remainingTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public convertPulseToSecond(n: number): number {
|
|
||||||
/**
|
|
||||||
* Converts a pulse to a second.
|
|
||||||
*/
|
|
||||||
return n * this.pulse_duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public start(): void {
|
|
||||||
/**
|
|
||||||
* Starts the TransportNode (starts the clock).
|
|
||||||
*
|
|
||||||
* @remark also sends a MIDI message if a port is declared
|
|
||||||
*/
|
|
||||||
this.app.audioContext.resume();
|
|
||||||
this.running = true;
|
|
||||||
this.app.api.MidiConnection.sendStartMessage();
|
|
||||||
this.lastPlayPressTime = this.app.audioContext.currentTime;
|
|
||||||
this.totalPauseTime += this.lastPlayPressTime - this.lastPauseTime;
|
|
||||||
this.transportNode?.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public pause(): void {
|
|
||||||
/**
|
|
||||||
* Pauses the TransportNode (pauses the clock).
|
|
||||||
*
|
|
||||||
* @remark also sends a MIDI message if a port is declared
|
|
||||||
*/
|
|
||||||
this.running = false;
|
|
||||||
this.transportNode?.pause();
|
|
||||||
this.app.api.MidiConnection.sendStopMessage();
|
|
||||||
this.lastPauseTime = this.app.audioContext.currentTime;
|
|
||||||
this.logicalTime = this.realTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public stop(): void {
|
|
||||||
/**
|
|
||||||
* Stops the TransportNode (stops the clock).
|
|
||||||
*
|
|
||||||
* @remark also sends a MIDI message if a port is declared
|
|
||||||
*/
|
|
||||||
this.running = false;
|
|
||||||
this.tick = 0;
|
|
||||||
this.lastPauseTime = this.app.audioContext.currentTime;
|
|
||||||
this.logicalTime = this.realTime;
|
|
||||||
this.time_position = { bar: 0, beat: 0, pulse: 0 };
|
|
||||||
this.app.api.MidiConnection.sendStopMessage();
|
|
||||||
this.transportNode?.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,19 +1,7 @@
|
|||||||
import { type Editor } from "./main";
|
import { type Editor } from "../main";
|
||||||
|
|
||||||
export type ElementMap = {
|
|
||||||
[key: string]:
|
|
||||||
| HTMLElement
|
|
||||||
| HTMLButtonElement
|
|
||||||
| HTMLDivElement
|
|
||||||
| HTMLInputElement
|
|
||||||
| HTMLSelectElement
|
|
||||||
| HTMLCanvasElement
|
|
||||||
| HTMLFormElement
|
|
||||||
| HTMLInputElement;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const singleElements = {
|
export const singleElements = {
|
||||||
topos_logo: "topos-logo",
|
logo: "topos_logo",
|
||||||
fill_viewer: "fillviewer",
|
fill_viewer: "fillviewer",
|
||||||
load_universe_button: "load-universe-button",
|
load_universe_button: "load-universe-button",
|
||||||
download_universe_button: "download-universes",
|
download_universe_button: "download-universes",
|
||||||
@ -41,12 +29,12 @@ export const singleElements = {
|
|||||||
line_numbers_checkbox: "show-line-numbers",
|
line_numbers_checkbox: "show-line-numbers",
|
||||||
time_position_checkbox: "show-time-position",
|
time_position_checkbox: "show-time-position",
|
||||||
tips_checkbox: "show-tips",
|
tips_checkbox: "show-tips",
|
||||||
|
transport_viewer: "transport_viewer",
|
||||||
completion_checkbox: "show-completions",
|
completion_checkbox: "show-completions",
|
||||||
midi_clock_checkbox: "send-midi-clock",
|
midi_clock_checkbox: "send-midi-clock",
|
||||||
midi_channels_scripts: "midi-channels-scripts",
|
midi_channels_scripts: "midi-channels-scripts",
|
||||||
midi_clock_ppqn: "midi-clock-ppqn-input",
|
midi_clock_ppqn: "midi-clock-ppqn-input",
|
||||||
theme_selector: "theme-selector",
|
theme_selector: "theme-selector",
|
||||||
theme_previewer: "theme-previewer",
|
|
||||||
load_demo_songs: "load-demo-songs",
|
load_demo_songs: "load-demo-songs",
|
||||||
normal_mode_button: "normal-mode",
|
normal_mode_button: "normal-mode",
|
||||||
vim_mode_button: "vim-mode",
|
vim_mode_button: "vim-mode",
|
||||||
@ -56,14 +44,26 @@ export const singleElements = {
|
|||||||
error_line: "error_line",
|
error_line: "error_line",
|
||||||
hydra_canvas: "hydra-bg",
|
hydra_canvas: "hydra-bg",
|
||||||
feedback: "feedback",
|
feedback: "feedback",
|
||||||
drawings: "drawings",
|
|
||||||
scope: "scope",
|
scope: "scope",
|
||||||
};
|
play_button: "play-button",
|
||||||
|
play_label: "play-label",
|
||||||
|
stop_button: "stop-button",
|
||||||
|
play_icon: "play-icon",
|
||||||
|
pause_icon: "pause-icon",
|
||||||
|
} as const;
|
||||||
|
|
||||||
export const buttonGroups = {
|
export type SingleElementsKeys = keyof typeof singleElements;
|
||||||
play_buttons: ["play-button-1"],
|
|
||||||
stop_buttons: ["stop-button-1"],
|
export type ElementMap = {
|
||||||
clear_buttons: ["clear-button-1"],
|
[K in SingleElementsKeys]:
|
||||||
|
| HTMLElement
|
||||||
|
| HTMLButtonElement
|
||||||
|
| HTMLDivElement
|
||||||
|
| HTMLInputElement
|
||||||
|
| HTMLSelectElement
|
||||||
|
| HTMLCanvasElement
|
||||||
|
| HTMLFormElement
|
||||||
|
| HTMLInputElement;
|
||||||
};
|
};
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
@ -1,8 +1,9 @@
|
|||||||
import { type Editor } from "./main";
|
import { type Editor } from "../main";
|
||||||
import { vim } from "@replit/codemirror-vim";
|
import { vim } from "@replit/codemirror-vim";
|
||||||
import { tryEvaluate } from "./Evaluator";
|
import { tryEvaluate } from "../Evaluator";
|
||||||
import { hideDocumentation, showDocumentation } from "./Documentation";
|
import { hideDocumentation, showDocumentation } from "../Docs/Documentation";
|
||||||
import { openSettingsModal, openUniverseModal } from "./FileManagement";
|
import { openSettingsModal, openUniverseModal } from "../Editor/FileManagement";
|
||||||
|
import { resetTransportView, updatePlayButton } from "./UILogic";
|
||||||
|
|
||||||
export const registerFillKeys = (app: Editor) => {
|
export const registerFillKeys = (app: Editor) => {
|
||||||
document.addEventListener("keydown", (event) => {
|
document.addEventListener("keydown", (event) => {
|
||||||
@ -53,21 +54,21 @@ export const registerOnKeyDown = (app: Editor) => {
|
|||||||
|
|
||||||
if (event.ctrlKey && event.key === "s") {
|
if (event.ctrlKey && event.key === "s") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
app.setButtonHighlighting("stop", true);
|
app.flashBackground("#404040", 200);
|
||||||
app.clock.stop();
|
requestAnimationFrame (() => {
|
||||||
|
updatePlayButton(app);
|
||||||
|
resetTransportView(app);
|
||||||
|
});
|
||||||
|
app.clock.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.ctrlKey && event.key === "p") {
|
if (event.ctrlKey && event.key === "p") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (app.isPlaying) {
|
app.flashBackground("#404040", 200);
|
||||||
app.isPlaying = false;
|
requestAnimationFrame(() => {
|
||||||
app.setButtonHighlighting("pause", true);
|
updatePlayButton(app);
|
||||||
app.clock.pause();
|
});
|
||||||
} else {
|
app.clock.resume()
|
||||||
app.isPlaying = true;
|
|
||||||
app.setButtonHighlighting("play", true);
|
|
||||||
app.clock.start();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl + Shift + V: Vim Mode
|
// Ctrl + Shift + V: Vim Mode
|
||||||
@ -1,14 +1,14 @@
|
|||||||
import { EditorView } from "codemirror";
|
import { EditorView } from "codemirror";
|
||||||
import { vim } from "@replit/codemirror-vim";
|
import { vim } from "@replit/codemirror-vim";
|
||||||
import { type Editor } from "./main";
|
import { type Editor } from "../main";
|
||||||
import colors from "./colors.json";
|
import colors from "../Editor/colors.json";
|
||||||
import {
|
import {
|
||||||
documentation_factory,
|
documentation_factory,
|
||||||
documentation_pages,
|
documentation_pages,
|
||||||
hideDocumentation,
|
hideDocumentation,
|
||||||
showDocumentation,
|
showDocumentation,
|
||||||
updateDocumentationContent,
|
updateDocumentationContent,
|
||||||
} from "./Documentation";
|
} from "../Docs/Documentation";
|
||||||
import {
|
import {
|
||||||
type Universe,
|
type Universe,
|
||||||
template_universe,
|
template_universe,
|
||||||
@ -18,14 +18,14 @@ import {
|
|||||||
share,
|
share,
|
||||||
closeUniverseModal,
|
closeUniverseModal,
|
||||||
openUniverseModal,
|
openUniverseModal,
|
||||||
} from "./FileManagement";
|
} from "../Editor/FileManagement";
|
||||||
import { loadSamples } from "./API";
|
import { loadSamples } from "../API/API";
|
||||||
import { tryEvaluate } from "./Evaluator";
|
import { tryEvaluate } from "../Evaluator";
|
||||||
import { inlineHoveringTips } from "./documentation/inlineHelp";
|
import { inlineHoveringTips } from "../Docs/inlineHelp";
|
||||||
import { lineNumbers } from "@codemirror/view";
|
import { lineNumbers } from "@codemirror/view";
|
||||||
import { jsCompletions } from "./EditorSetup";
|
import { jsCompletions } from "../Editor/EditorSetup";
|
||||||
import { saveState } from "./WindowBehavior";
|
import { saveState } from "./WindowBehavior";
|
||||||
import { registerSamplesFromDB, samplesDBConfig, uploadSamplesToDB } from "./IO/SampleLoading";
|
import { registerSamplesFromDB, samplesDBConfig, uploadSamplesToDB } from "../IO/SampleLoading";
|
||||||
|
|
||||||
export const installInterfaceLogic = (app: Editor) => {
|
export const installInterfaceLogic = (app: Editor) => {
|
||||||
// Initialize style
|
// Initialize style
|
||||||
@ -50,56 +50,45 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
const tabs = document.querySelectorAll('[id^="tab-"]');
|
const tabs = document.querySelectorAll('[id^="tab-"]');
|
||||||
// Iterate over the tabs with an index
|
// Iterate over the tabs with an index
|
||||||
for (let i = 0; i < tabs.length; i++) {
|
for (let i = 0; i < tabs.length; i++) {
|
||||||
tabs[i].addEventListener("click", (event) => {
|
tabs[i]!.addEventListener("click", (event) => {
|
||||||
// Updating the CSS accordingly
|
// Updating the CSS accordingly
|
||||||
tabs[i].classList.add("bg-foreground");
|
tabs[i]!.classList.add("bg-foreground");
|
||||||
tabs[i].classList.add("text-selection_foreground");
|
tabs[i]!.classList.add("text-selection_foreground");
|
||||||
for (let j = 0; j < tabs.length; j++) {
|
for (let j = 0; j < tabs.length; j++) {
|
||||||
if (j != i) tabs[j].classList.remove("bg-foreground");
|
if (j != i) tabs[j]!.classList.remove("bg-foreground");
|
||||||
if (j != i) tabs[j].classList.remove("text-selection_foreground");
|
if (j != i) tabs[j]!.classList.remove("text-selection_foreground");
|
||||||
}
|
}
|
||||||
app.currentFile().candidate = app.view.state.doc.toString();
|
app.currentFile().candidate = app.view.state.doc.toString();
|
||||||
|
|
||||||
let tab = event.target as HTMLElement;
|
let tab = event.target as HTMLElement;
|
||||||
let tab_id = tab.id.split("-")[1];
|
let tab_id = tab.id.split("-")[1];
|
||||||
app.local_index = parseInt(tab_id);
|
app.local_index = parseInt(tab_id!);
|
||||||
app.updateEditorView();
|
app.updateEditorView();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
app.interface.topos_logo.addEventListener("click", () => {
|
app.interface['logo'].addEventListener("click", () => {
|
||||||
hideDocumentation();
|
hideDocumentation();
|
||||||
app.updateKnownUniversesView();
|
app.updateKnownUniversesView();
|
||||||
openUniverseModal();
|
openUniverseModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.buttonElements.play_buttons.forEach((button) => {
|
app.interface['play_button'].addEventListener("click", () => {
|
||||||
button.addEventListener("click", () => {
|
|
||||||
if (app.isPlaying) {
|
if (app.isPlaying) {
|
||||||
app.setButtonHighlighting("pause", true);
|
|
||||||
app.isPlaying = !app.isPlaying;
|
|
||||||
app.clock.pause();
|
app.clock.pause();
|
||||||
app.api.MidiConnection.sendStopMessage();
|
|
||||||
} else {
|
} else {
|
||||||
app.setButtonHighlighting("play", true);
|
app.clock.resume()
|
||||||
app.isPlaying = !app.isPlaying;
|
|
||||||
app.clock.start();
|
|
||||||
app.api.MidiConnection.sendStartMessage();
|
|
||||||
}
|
}
|
||||||
});
|
updatePlayButton(app);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.buttonElements.clear_buttons.forEach((button) => {
|
app.interface['stop_button'].addEventListener("click", () => {
|
||||||
button.addEventListener("click", () => {
|
app.isPlaying = false;
|
||||||
app.setButtonHighlighting("clear", true);
|
app.clock.stop();
|
||||||
if (confirm("Do you want to reset the current universe?")) {
|
updatePlayButton(app);
|
||||||
app.universes[app.selected_universe] =
|
|
||||||
structuredClone(template_universe);
|
|
||||||
app.updateEditorView();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
app.interface.documentation_button.addEventListener("click", () => {
|
app.interface.documentation_button.addEventListener("click", () => {
|
||||||
showDocumentation(app);
|
showDocumentation(app);
|
||||||
});
|
});
|
||||||
@ -140,20 +129,13 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.interface.audio_nudge_range.addEventListener("input", () => {
|
|
||||||
// TODO: rebuild this
|
|
||||||
// app.clock.nudge = parseInt(
|
|
||||||
// (app.interface.audio_nudge_range as HTMLInputElement).value,
|
|
||||||
// );
|
|
||||||
});
|
|
||||||
|
|
||||||
app.interface.dough_nudge_range.addEventListener("input", () => {
|
app.interface.dough_nudge_range.addEventListener("input", () => {
|
||||||
app.dough_nudge = parseInt(
|
app.dough_nudge = parseInt(
|
||||||
(app.interface.dough_nudge_range as HTMLInputElement).value,
|
(app.interface.dough_nudge_range as HTMLInputElement).value,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.interface.upload_samples_button.addEventListener("input", async (event) => {
|
app.interface.upload_samples_button.addEventListener("input", async (event: Event) => {
|
||||||
let fileInput = event.target as HTMLInputElement;
|
let fileInput = event.target as HTMLInputElement;
|
||||||
if (!fileInput.files?.length) {
|
if (!fileInput.files?.length) {
|
||||||
return;
|
return;
|
||||||
@ -239,14 +221,6 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
app.flashBackground("#404040", 200);
|
app.flashBackground("#404040", 200);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.buttonElements.stop_buttons.forEach((button) => {
|
|
||||||
button.addEventListener("click", () => {
|
|
||||||
app.setButtonHighlighting("stop", true);
|
|
||||||
app.isPlaying = false;
|
|
||||||
app.clock.stop();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.interface.local_button.addEventListener("click", () =>
|
app.interface.local_button.addEventListener("click", () =>
|
||||||
app.changeModeFromInterface("local"),
|
app.changeModeFromInterface("local"),
|
||||||
);
|
);
|
||||||
@ -310,7 +284,6 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
for (const [key, _] of Object.entries(selected_theme)) {
|
for (const [key, _] of Object.entries(selected_theme)) {
|
||||||
theme_preview += `<p class="inline text-${key} bg-${key}">█</div>`;
|
theme_preview += `<p class="inline text-${key} bg-${key}">█</div>`;
|
||||||
}
|
}
|
||||||
app.interface.theme_previewer.innerHTML = theme_preview;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.interface.settings_button.addEventListener("click", () => {
|
app.interface.settings_button.addEventListener("click", () => {
|
||||||
@ -336,12 +309,6 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
let selected_theme = colors[app.settings.theme as string];
|
let selected_theme = colors[app.settings.theme as string];
|
||||||
let theme_preview = "<div class='ml-6'>";
|
|
||||||
for (const [key, _] of Object.entries(selected_theme)) {
|
|
||||||
theme_preview += `<p class="inline text-${key} bg-${key}">█</p>`;
|
|
||||||
}
|
|
||||||
theme_preview += "</div>";
|
|
||||||
app.interface.theme_previewer.innerHTML = theme_preview;
|
|
||||||
// Populate the font family selector
|
// Populate the font family selector
|
||||||
const doughNudgeRange = app.interface.dough_nudge_range as HTMLInputElement;
|
const doughNudgeRange = app.interface.dough_nudge_range as HTMLInputElement;
|
||||||
doughNudgeRange.value = app.dough_nudge.toString();
|
doughNudgeRange.value = app.dough_nudge.toString();
|
||||||
@ -442,18 +409,6 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.interface.time_position_checkbox.addEventListener("change", () => {
|
|
||||||
let timeviewer = document.getElementById("timeviewer") as HTMLElement;
|
|
||||||
let checked = (app.interface.time_position_checkbox as HTMLInputElement)
|
|
||||||
.checked
|
|
||||||
? true
|
|
||||||
: false;
|
|
||||||
app.settings.time_position = checked;
|
|
||||||
checked
|
|
||||||
? timeviewer.classList.remove("hidden")
|
|
||||||
: timeviewer.classList.add("hidden");
|
|
||||||
});
|
|
||||||
|
|
||||||
app.interface.tips_checkbox.addEventListener("change", () => {
|
app.interface.tips_checkbox.addEventListener("change", () => {
|
||||||
let checked = (app.interface.tips_checkbox as HTMLInputElement).checked
|
let checked = (app.interface.tips_checkbox as HTMLInputElement).checked
|
||||||
? true
|
? true
|
||||||
@ -509,7 +464,7 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
// app.settings.load_demo_songs = checked;
|
// app.settings.load_demo_songs = checked;
|
||||||
// });
|
// });
|
||||||
|
|
||||||
app.interface.universe_creator.addEventListener("submit", (event) => {
|
app.interface.universe_creator.addEventListener("submit", (event: Event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
let data = new FormData(app.interface.universe_creator as HTMLFormElement);
|
let data = new FormData(app.interface.universe_creator as HTMLFormElement);
|
||||||
@ -528,7 +483,7 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tryEvaluate(app, app.universes[app.selected_universe.toString()].init);
|
tryEvaluate(app, app.universes[app.selected_universe.toString()]!.init);
|
||||||
|
|
||||||
documentation_pages.forEach((e) => {
|
documentation_pages.forEach((e) => {
|
||||||
let name = `docs_` + e;
|
let name = `docs_` + e;
|
||||||
@ -556,3 +511,43 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updatePlayButton = (app: Editor) => {
|
||||||
|
switch (app.clock.state) {
|
||||||
|
case 'stopped':
|
||||||
|
app.interface.play_label.innerText = "Play";
|
||||||
|
updatePlayPauseIcon(app, "play");
|
||||||
|
break;
|
||||||
|
case 'paused':
|
||||||
|
app.interface.play_label.innerText = "Resume";
|
||||||
|
updatePlayPauseIcon(app, "play");
|
||||||
|
break;
|
||||||
|
case 'running':
|
||||||
|
app.interface.play_label.innerText = "Pause";
|
||||||
|
updatePlayPauseIcon(app, "pause");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updatePlayPauseIcon = (app: Editor, state: "play" | "pause"): void => {
|
||||||
|
const { play_icon, pause_icon } = app.interface;
|
||||||
|
|
||||||
|
const isPlayIconHidden = play_icon.classList.contains("hidden");
|
||||||
|
const isPauseIconHidden = pause_icon.classList.contains("hidden");
|
||||||
|
|
||||||
|
if (state === "play" && isPlayIconHidden) {
|
||||||
|
play_icon.classList.remove("hidden");
|
||||||
|
pause_icon.classList.add("hidden");
|
||||||
|
} else if (state === "pause" && isPauseIconHidden) {
|
||||||
|
play_icon.classList.add("hidden");
|
||||||
|
pause_icon.classList.remove("hidden");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const resetTransportView = (app: Editor) => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
app.interface.transport_viewer.innerHTML = `<span class="text-xl text-neutral">00:00:00</span>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,4 +1,9 @@
|
|||||||
import { type Editor } from "../main";
|
import { type Editor } from "../../main";
|
||||||
|
|
||||||
|
const HORIZONTALOFFSETPERCENT = 0.025;
|
||||||
|
const VERTICALOFFSETPERCENT = 0.025;
|
||||||
|
const RADIUSPERCENT = 0.010;
|
||||||
|
const SHIFTPERCENT = 0.025;
|
||||||
|
|
||||||
export const drawCircle = (
|
export const drawCircle = (
|
||||||
/**
|
/**
|
||||||
@ -17,6 +22,7 @@ export const drawCircle = (
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const canvas: HTMLCanvasElement = app.interface.feedback;
|
const canvas: HTMLCanvasElement = app.interface.feedback;
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
|
console.log(`Canvas size: ${canvas.width}x${canvas.height}`);
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
@ -38,38 +44,36 @@ export const blinkScript = (
|
|||||||
) => {
|
) => {
|
||||||
if (no !== undefined && no < 1 && no > 9) return;
|
if (no !== undefined && no < 1 && no > 9) return;
|
||||||
const blinkDuration =
|
const blinkDuration =
|
||||||
(app.clock.bpm / 60 / app.clock.time_signature[1]) * 200;
|
(app.clock.bpm / 60 / app.clock.time_position.num) * 200;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const ctx = app.interface.feedback.getContext("2d"); // Assuming a canvas context
|
const ctx = app.interface.feedback.getContext("2d");
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws a circle at a given shift.
|
|
||||||
* @param shift - The pixel distance from the origin.
|
|
||||||
*/
|
|
||||||
const _drawBlinker = (shift: number) => {
|
const _drawBlinker = (shift: number) => {
|
||||||
const horizontalOffset = 50;
|
const horizontalOffsetPercent = HORIZONTALOFFSETPERCENT;
|
||||||
|
const verticalOffsetPercent = VERTICALOFFSETPERCENT;
|
||||||
|
const radiusPercent = RADIUSPERCENT;
|
||||||
drawCircle(
|
drawCircle(
|
||||||
app,
|
app,
|
||||||
horizontalOffset + shift,
|
(app.interface.feedback as HTMLCanvasElement).width * horizontalOffsetPercent + shift,
|
||||||
app.interface.feedback.clientHeight - 15,
|
(app.interface.feedback as HTMLCanvasElement).height * (1 - verticalOffsetPercent),
|
||||||
8,
|
(app.interface.feedback as HTMLCanvasElement).width * radiusPercent,
|
||||||
"#fdba74",
|
"#fdba74",
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const _clearBlinker = (shift: number) => {
|
const _clearBlinker = (shift: number) => {
|
||||||
/**
|
const horizontalOffsetPercent = HORIZONTALOFFSETPERCENT;
|
||||||
* Clears the circle at a given shift.
|
const verticalOffsetPercent = VERTICALOFFSETPERCENT;
|
||||||
* @param shift - The pixel distance from the origin.
|
const radiusPercent = RADIUSPERCENT;
|
||||||
*/
|
const x = (app.interface.feedback as HTMLCanvasElement).width * horizontalOffsetPercent + shift;
|
||||||
const x = 50 + shift;
|
const y = (app.interface.feedback as HTMLCanvasElement).height * (1 - verticalOffsetPercent);
|
||||||
const y = app.interface.feedback.clientHeight - 15;
|
const radius = (app.interface.feedback as HTMLCanvasElement).width * radiusPercent;
|
||||||
const radius = 8;
|
|
||||||
ctx.clearRect(x - radius, y - radius, radius * 2, radius * 2);
|
ctx.clearRect(x - radius, y - radius, radius * 2, radius * 2);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (script === "local" && no !== undefined) {
|
if (script === "local" && no !== undefined) {
|
||||||
const shiftAmount = no * 25;
|
const shiftPercent = SHIFTPERCENT;
|
||||||
|
const shiftAmount = no * (app.interface.feedback as HTMLCanvasElement).width * shiftPercent;
|
||||||
|
|
||||||
// Clear existing timeout if any
|
// Clear existing timeout if any
|
||||||
if (app.blinkTimeouts[shiftAmount]) {
|
if (app.blinkTimeouts[shiftAmount]) {
|
||||||
@ -1,35 +1,3 @@
|
|||||||
export type ShapeObject = {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
x1: number;
|
|
||||||
y1: number;
|
|
||||||
x2: number;
|
|
||||||
y2: number;
|
|
||||||
radius: number;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
fillStyle: string;
|
|
||||||
secondary: string;
|
|
||||||
strokeStyle: string;
|
|
||||||
rotation: number;
|
|
||||||
points: number;
|
|
||||||
outerRadius: number;
|
|
||||||
eyeSize: number;
|
|
||||||
happiness: number;
|
|
||||||
slices: number;
|
|
||||||
gap: number;
|
|
||||||
font: string;
|
|
||||||
fontSize: number;
|
|
||||||
text: string;
|
|
||||||
filter: string;
|
|
||||||
url: string;
|
|
||||||
curve: number;
|
|
||||||
curves: number;
|
|
||||||
stroke: string;
|
|
||||||
eaten: number;
|
|
||||||
hole: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const drawBackground = (
|
export const drawBackground = (
|
||||||
canvas: HTMLCanvasElement,
|
canvas: HTMLCanvasElement,
|
||||||
color: string | number,
|
color: string | number,
|
||||||
@ -60,7 +28,7 @@ export const createLinearGradient = (
|
|||||||
let color = stops[i + 1];
|
let color = stops[i + 1];
|
||||||
if (typeof color === "number")
|
if (typeof color === "number")
|
||||||
color = `rgb(${color},${stops[i + 2]},${stops[i + 3]})`;
|
color = `rgb(${color},${stops[i + 2]},${stops[i + 3]})`;
|
||||||
gradient.addColorStop(stops[i] as number, color);
|
gradient.addColorStop(stops[i] as number, color as string);
|
||||||
}
|
}
|
||||||
return gradient;
|
return gradient;
|
||||||
};
|
};
|
||||||
@ -91,7 +59,7 @@ export const createRadialGradient = (
|
|||||||
let color = stops[i + 1];
|
let color = stops[i + 1];
|
||||||
if (typeof color === "number")
|
if (typeof color === "number")
|
||||||
color = `rgb(${color},${stops[i + 2]},${stops[i + 3]})`;
|
color = `rgb(${color},${stops[i + 2]},${stops[i + 3]})`;
|
||||||
gradient.addColorStop(stops[i] as number, color);
|
gradient.addColorStop(stops[i] as number, color as string);
|
||||||
}
|
}
|
||||||
return gradient;
|
return gradient;
|
||||||
};
|
};
|
||||||
@ -116,7 +84,7 @@ export const createConicGradient = (
|
|||||||
let color = stops[i + 1];
|
let color = stops[i + 1];
|
||||||
if (typeof color === "number")
|
if (typeof color === "number")
|
||||||
color = `rgb(${color},${stops[i + 2]},${stops[i + 3]})`;
|
color = `rgb(${color},${stops[i + 2]},${stops[i + 3]})`;
|
||||||
gradient.addColorStop(stops[i] as number, color);
|
gradient.addColorStop(stops[i] as number, color as string);
|
||||||
}
|
}
|
||||||
return gradient;
|
return gradient;
|
||||||
};
|
};
|
||||||
@ -221,10 +189,11 @@ export const drawBalloid = (
|
|||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.fillStyle = secondary;
|
ctx.fillStyle = secondary;
|
||||||
// Form the shape from points with straight lines and fill it
|
// Form the shape from points with straight lines and fill it
|
||||||
ctx.moveTo(points[0][0], points[0][1]);
|
if (points[0]) {
|
||||||
for (let point of points) ctx.lineTo(point[0], point[1]);
|
ctx.moveTo(points[0][0] as number, points[0][1] as number);
|
||||||
|
for (let point of points) ctx.lineTo(point[0] as number, point[1] as number);
|
||||||
|
}
|
||||||
// Close and fill
|
// Close and fill
|
||||||
|
|
||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { getAnalyser } from "superdough";
|
import { getAnalyser } from "superdough";
|
||||||
import { Editor } from "../main";
|
import { Editor } from "../../main";
|
||||||
|
|
||||||
export interface OscilloscopeConfig {
|
export interface OscilloscopeConfig {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@ -71,7 +71,7 @@ export const runOscilloscope = (
|
|||||||
|
|
||||||
for (let i = 0; i < numBars; i++) {
|
for (let i = 0; i < numBars; i++) {
|
||||||
barHeight = Math.floor(
|
barHeight = Math.floor(
|
||||||
freqDataArray[Math.floor((i * freqDataArray.length) / numBars)] *
|
freqDataArray[Math.floor((i * freqDataArray.length) / numBars)]! *
|
||||||
((height / 256) * app.osc.size),
|
((height / 256) * app.osc.size),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -139,7 +139,7 @@ export const runOscilloscope = (
|
|||||||
|
|
||||||
canvasCtx.fillStyle = "rgba(0, 0, 0, 0)";
|
canvasCtx.fillStyle = "rgba(0, 0, 0, 0)";
|
||||||
canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
|
canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
|
||||||
if (app.clock.time_position.pulse % app.osc.refresh == 0) {
|
if (app.clock.time_position.tick % app.osc.refresh == 0) {
|
||||||
canvasCtx.clearRect(
|
canvasCtx.clearRect(
|
||||||
-OFFSET_WIDTH,
|
-OFFSET_WIDTH,
|
||||||
-OFFSET_HEIGHT,
|
-OFFSET_HEIGHT,
|
||||||
@ -150,14 +150,14 @@ export const runOscilloscope = (
|
|||||||
canvasCtx.lineWidth = app.osc.thickness;
|
canvasCtx.lineWidth = app.osc.thickness;
|
||||||
|
|
||||||
if (app.osc.color === "random") {
|
if (app.osc.color === "random") {
|
||||||
if (app.clock.time_position.pulse % 16 === 0) {
|
if (app.clock.time_position.tick % 16 === 0) {
|
||||||
canvasCtx.strokeStyle = `hsl(${Math.random() * 360}, 100%, 50%)`;
|
canvasCtx.strokeStyle = `hsl(${Math.random() * 360}, 100%, 50%)`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
canvasCtx.strokeStyle = app.osc.color;
|
canvasCtx.strokeStyle = app.osc.color;
|
||||||
}
|
}
|
||||||
const remainingRefreshTime =
|
const remainingRefreshTime =
|
||||||
app.clock.time_position.pulse % app.osc.refresh;
|
app.clock.time_position.tick % app.osc.refresh;
|
||||||
const opacityRatio = 1 - remainingRefreshTime / app.osc.refresh;
|
const opacityRatio = 1 - remainingRefreshTime / app.osc.refresh;
|
||||||
canvasCtx.globalAlpha = opacityRatio;
|
canvasCtx.globalAlpha = opacityRatio;
|
||||||
canvasCtx.beginPath();
|
canvasCtx.beginPath();
|
||||||
@ -165,9 +165,9 @@ export const runOscilloscope = (
|
|||||||
let startIndex = 0;
|
let startIndex = 0;
|
||||||
for (let i = 1; i < dataArray.length; ++i) {
|
for (let i = 1; i < dataArray.length; ++i) {
|
||||||
let currentType = null;
|
let currentType = null;
|
||||||
if (dataArray[i] >= 0 && dataArray[i - 1] < 0) {
|
if (dataArray[i]! >= 0 && dataArray[i - 1]! < 0) {
|
||||||
currentType = "negToPos";
|
currentType = "negToPos";
|
||||||
} else if (dataArray[i] < 0 && dataArray[i - 1] >= 0) {
|
} else if (dataArray[i]! < 0 && dataArray[i - 1]! >= 0) {
|
||||||
currentType = "posToNeg";
|
currentType = "posToNeg";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,8 +187,8 @@ export const runOscilloscope = (
|
|||||||
drawFrequencyScope(WIDTH, HEIGHT, OFFSET_HEIGHT, OFFSET_WIDTH);
|
drawFrequencyScope(WIDTH, HEIGHT, OFFSET_HEIGHT, OFFSET_WIDTH);
|
||||||
} else if (app.osc.mode === "3D") {
|
} else if (app.osc.mode === "3D") {
|
||||||
for (let i = startIndex; i < dataArray.length; i += 2) {
|
for (let i = startIndex; i < dataArray.length; i += 2) {
|
||||||
const x = (dataArray[i] * WIDTH * app.osc.size) / 2 + WIDTH / 4;
|
const x = (dataArray[i]! * WIDTH * app.osc.size) / 2 + WIDTH / 4;
|
||||||
const y = (dataArray[i + 1] * HEIGHT * app.osc.size) / 2 + HEIGHT / 4;
|
const y = (dataArray[i + 1]! * HEIGHT * app.osc.size) / 2 + HEIGHT / 4;
|
||||||
i === startIndex ? canvasCtx.moveTo(x, y) : canvasCtx.lineTo(x, y);
|
i === startIndex ? canvasCtx.moveTo(x, y) : canvasCtx.lineTo(x, y);
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
@ -199,7 +199,7 @@ export const runOscilloscope = (
|
|||||||
const yOffset = HEIGHT / 4;
|
const yOffset = HEIGHT / 4;
|
||||||
let x = 0;
|
let x = 0;
|
||||||
for (let i = startIndex; i < dataArray.length; i++) {
|
for (let i = startIndex; i < dataArray.length; i++) {
|
||||||
const v = dataArray[i] * 0.5 * HEIGHT * app.osc.size;
|
const v = dataArray[i]! * 0.5 * HEIGHT * app.osc.size;
|
||||||
const y = v + yOffset;
|
const y = v + yOffset;
|
||||||
i === startIndex ? canvasCtx.moveTo(x, y) : canvasCtx.lineTo(x, y);
|
i === startIndex ? canvasCtx.moveTo(x, y) : canvasCtx.lineTo(x, y);
|
||||||
x += sliceWidth;
|
x += sliceWidth;
|
||||||
@ -210,7 +210,7 @@ export const runOscilloscope = (
|
|||||||
const xOffset = WIDTH / 4;
|
const xOffset = WIDTH / 4;
|
||||||
let y = 0;
|
let y = 0;
|
||||||
for (let i = startIndex; i < dataArray.length; i++) {
|
for (let i = startIndex; i < dataArray.length; i++) {
|
||||||
const v = dataArray[i] * 0.5 * WIDTH * app.osc.size;
|
const v = dataArray[i]! * 0.5 * WIDTH * app.osc.size;
|
||||||
const x = v + xOffset;
|
const x = v + xOffset;
|
||||||
i === startIndex ? canvasCtx.moveTo(x, y) : canvasCtx.lineTo(x, y);
|
i === startIndex ? canvasCtx.moveTo(x, y) : canvasCtx.lineTo(x, y);
|
||||||
y += sliceHeight;
|
y += sliceHeight;
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "./main";
|
import { type Editor } from "../main";
|
||||||
import { outputSocket, inputSocket } from "./IO/OSC";
|
import { outputSocket, inputSocket } from "../IO/OSC";
|
||||||
|
|
||||||
const handleResize = (canvas: HTMLCanvasElement) => {
|
const handleResize = (canvas: HTMLCanvasElement) => {
|
||||||
if (!canvas) return;
|
if (!canvas) return;
|
||||||
@ -38,32 +38,29 @@ export const installWindowBehaviors = (
|
|||||||
window: Window,
|
window: Window,
|
||||||
preventMultipleTabs: boolean = false,
|
preventMultipleTabs: boolean = false,
|
||||||
) => {
|
) => {
|
||||||
window.addEventListener("resize", () =>
|
|
||||||
handleResize(app.interface.scope as HTMLCanvasElement),
|
|
||||||
);
|
|
||||||
window.addEventListener("resize", () =>
|
window.addEventListener("resize", () =>
|
||||||
handleResize(app.interface.feedback as HTMLCanvasElement),
|
handleResize(app.interface.feedback as HTMLCanvasElement),
|
||||||
);
|
);
|
||||||
window.addEventListener("resize", () =>
|
window.addEventListener("resize", () =>
|
||||||
handleResize(app.interface.drawings as HTMLCanvasElement),
|
handleResize(app.interface.scope as HTMLCanvasElement),
|
||||||
);
|
);
|
||||||
window.addEventListener("beforeunload", (event) => {
|
// window.addEventListener("beforeunload", (event) => {
|
||||||
event.preventDefault();
|
// event.preventDefault();
|
||||||
saveBeforeExit(app);
|
// saveBeforeExit(app);
|
||||||
});
|
// });
|
||||||
window.addEventListener("visibilitychange", (event) => {
|
window.addEventListener("visibilitychange", (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
saveState(app);
|
saveState(app);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (preventMultipleTabs) {
|
if (preventMultipleTabs) {
|
||||||
localStorage.openpages = Date.now();
|
localStorage["openpages"] = Date.now();
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"storage",
|
"storage",
|
||||||
function (e) {
|
function(e) {
|
||||||
if (e.key == "openpages") {
|
if (e.key == "openpages") {
|
||||||
// Listen if anybody else is opening the same page!
|
// Listen if anybody else is opening the same page!
|
||||||
localStorage.page_available = Date.now();
|
localStorage["page_available"] = Date.now();
|
||||||
}
|
}
|
||||||
if (e.key == "page_available") {
|
if (e.key == "page_available") {
|
||||||
document.getElementById("all")!.classList.add("invisible");
|
document.getElementById("all")!.classList.add("invisible");
|
||||||
@ -1,53 +1,37 @@
|
|||||||
import { type Editor } from "./main";
|
import { Editor } from "../main";
|
||||||
// Basics
|
import { introduction, atelier, software_interface, shortcuts, code, mouse, interaction } from "./basics";
|
||||||
import { introduction } from "./documentation/basics/welcome";
|
import { amplitude, effects, sampler, synths, filters, audio_basics } from "./learning/audio_engine";
|
||||||
import { atelier } from "./documentation/basics/atelier";
|
import { lfos, functions, generators, variables, probabilities } from './patterns';
|
||||||
import { loading_samples } from "./documentation/learning/samples/loading_samples";
|
import { ziffers_basics, ziffers_scales, ziffers_rhythm, ziffers_algorithmic, ziffers_tonnetz, ziffers_syncing } from "./patterns/ziffers";
|
||||||
import { amplitude } from "./documentation/learning/audio_engine/amplitude";
|
import { loading_samples } from "./learning/samples/loading_samples";
|
||||||
import { effects } from "./documentation/learning/audio_engine/effects";
|
import { sample_banks } from "./learning/samples/sample_banks";
|
||||||
import { sampler } from "./documentation/learning/audio_engine/sampler";
|
import { sample_list } from "./learning/samples/sample_list";
|
||||||
import { sample_banks } from "./documentation/learning/samples/sample_banks";
|
import { oscilloscope } from "./more/oscilloscope";
|
||||||
import { audio_basics } from "./documentation/learning/audio_engine/audio_basics";
|
import { synchronisation } from "./more/synchronisation";
|
||||||
import { sample_list } from "./documentation/learning/samples/sample_list";
|
import { about } from "./more/about";
|
||||||
import { software_interface } from "./documentation/basics/interface";
|
import { bonus } from "./more/bonus";
|
||||||
import { shortcuts } from "./documentation/basics/keyboard";
|
import { visualization } from "./more/visualization";
|
||||||
import { code } from "./documentation/basics/code";
|
import { chaining } from "./patterns/chaining";
|
||||||
import { mouse } from "./documentation/basics/mouse";
|
import { time } from "./learning/time/time";
|
||||||
// More
|
import { linear_time } from "./learning/time/linear_time";
|
||||||
import { oscilloscope } from "./documentation/more/oscilloscope";
|
import { cyclical_time } from "./learning/time/cyclical_time";
|
||||||
import { synchronisation } from "./documentation/more/synchronisation";
|
import { long_forms } from "./learning/time/long_forms";
|
||||||
import { about } from "./documentation/more/about";
|
import { midi } from "./learning/midi";
|
||||||
import { bonus } from "./documentation/more/bonus";
|
import { osc } from "./learning/osc";
|
||||||
import { visualization } from "./documentation/more/visualization";
|
import { patterns } from "./patterns/patterns";
|
||||||
import { chaining } from "./documentation/patterns/chaining";
|
|
||||||
import { interaction } from "./documentation/basics/interaction";
|
|
||||||
import { time } from "./documentation/learning/time/time";
|
|
||||||
import { linear_time } from "./documentation/learning/time/linear_time";
|
|
||||||
import { cyclical_time } from "./documentation/learning/time/cyclical_time";
|
|
||||||
import { long_forms } from "./documentation/learning/time/long_forms";
|
|
||||||
import { midi } from "./documentation/learning/midi";
|
|
||||||
import { osc } from "./documentation/learning/osc";
|
|
||||||
import { patterns } from "./documentation/patterns/patterns";
|
|
||||||
import { functions } from "./documentation/patterns/functions";
|
|
||||||
import { generators } from "./documentation/patterns/generators";
|
|
||||||
import { variables } from "./documentation/patterns/variables";
|
|
||||||
import { probabilities } from "./documentation/patterns/probabilities";
|
|
||||||
import { lfos } from "./documentation/patterns/lfos";
|
|
||||||
import { ziffers_basics } from "./documentation/patterns/ziffers/ziffers_basics";
|
|
||||||
import { ziffers_scales } from "./documentation/patterns/ziffers/ziffers_scales";
|
|
||||||
import { ziffers_rhythm } from "./documentation/patterns/ziffers/ziffers_rhythm";
|
|
||||||
import { ziffers_algorithmic } from "./documentation/patterns/ziffers/ziffers_algorithmic";
|
|
||||||
import { ziffers_tonnetz } from "./documentation/patterns/ziffers/ziffers_tonnetz";
|
|
||||||
import { ziffers_syncing } from "./documentation/patterns/ziffers/ziffers_syncing";
|
|
||||||
import { synths } from "./documentation/learning/audio_engine/synths";
|
|
||||||
// Setting up the Markdown converter with syntax highlighting
|
// Setting up the Markdown converter with syntax highlighting
|
||||||
import showdown from "showdown";
|
import showdown from "showdown";
|
||||||
import showdownHighlight from "showdown-highlight";
|
import showdownHighlight from "showdown-highlight";
|
||||||
import "highlight.js/styles/atom-one-dark-reasonable.min.css";
|
import "highlight.js/styles/atom-one-dark-reasonable.min.css";
|
||||||
import { createDocumentationStyle } from "./DomElements";
|
import { createDocumentationStyle } from "../DOM/DomElements";
|
||||||
import { filters } from "./documentation/learning/audio_engine/filters";
|
|
||||||
showdown.setFlavor("github");
|
showdown.setFlavor("github");
|
||||||
|
|
||||||
|
type StyleBinding = {
|
||||||
|
type: string;
|
||||||
|
regex: RegExp;
|
||||||
|
replace: (match: string, p1: string) => string;
|
||||||
|
};
|
||||||
|
|
||||||
export const key_shortcut = (shortcut: string): string => {
|
export const key_shortcut = (shortcut: string): string => {
|
||||||
return `<kbd class="lg:px-2 lg:py-1.5 px-1 py-1 lg:text-sm text-xs font-semibold text-brightwhite bg-brightblack border border-black rounded-lg">${shortcut}</kbd>`;
|
return `<kbd class="lg:px-2 lg:py-1.5 px-1 py-1 lg:text-sm text-xs font-semibold text-brightwhite bg-brightblack border border-black rounded-lg">${shortcut}</kbd>`;
|
||||||
};
|
};
|
||||||
@ -70,8 +54,7 @@ export const makeExampleFactory = (application: Editor): Function => {
|
|||||||
<button class="py-1 text-base px-4 hover:bg-brightmagenta bg-magenta text-selection_foreground inline-block" onclick="navigator.clipboard.writeText(app.api.codeExamples['${codeId}'])">📎 Copy</button>
|
<button class="py-1 text-base px-4 hover:bg-brightmagenta bg-magenta text-selection_foreground inline-block" onclick="navigator.clipboard.writeText(app.api.codeExamples['${codeId}'])">📎 Copy</button>
|
||||||
</summary>
|
</summary>
|
||||||
<pre><code class="hljs language-javascript">${code.trim()}</code></pre>
|
<pre><code class="hljs language-javascript">${code.trim()}</code></pre>
|
||||||
</details>
|
</details> `;
|
||||||
`;
|
|
||||||
};
|
};
|
||||||
return make_example;
|
return make_example;
|
||||||
};
|
};
|
||||||
@ -170,34 +153,68 @@ export const documentation_factory = (application: Editor) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const showDocumentation = (app: Editor) => {
|
|
||||||
/**
|
|
||||||
* Shows or hides the documentation based on the current state of the app.
|
|
||||||
* @param app - The Editor instance.
|
|
||||||
*/
|
|
||||||
if (document.getElementById("app")?.classList.contains("hidden")) {
|
|
||||||
document.getElementById("app")?.classList.remove("hidden");
|
|
||||||
document.getElementById("documentation")?.classList.add("hidden");
|
|
||||||
app.exampleIsPlaying = false;
|
|
||||||
} else {
|
|
||||||
document.getElementById("app")?.classList.add("hidden");
|
|
||||||
document.getElementById("documentation")?.classList.remove("hidden");
|
|
||||||
// Load and convert Markdown content from the documentation file
|
|
||||||
let style = createDocumentationStyle(app);
|
|
||||||
|
|
||||||
function update_and_assign(callback: Function) {
|
|
||||||
let bindings = Object.keys(style).map((key) => ({
|
export const showDocumentation = (app: Editor): void => {
|
||||||
|
const toggleElementVisibility = (elementId: string, shouldHide: boolean): void => {
|
||||||
|
const element = document.getElementById(elementId);
|
||||||
|
if (element) {
|
||||||
|
element.classList.toggle("hidden", shouldHide);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyStyleBindings = (style: Record<string, string>, updateContent: (bindings: StyleBinding[]) => void): void => {
|
||||||
|
const bindings: StyleBinding[] = Object.keys(style).map((key) => ({
|
||||||
type: "output",
|
type: "output",
|
||||||
regex: new RegExp(`<${key}([^>]*)>`, "g"),
|
regex: new RegExp(`<${key}([^>]*)>`, "g"),
|
||||||
//@ts-ignore
|
replace: (_, p1) => `<${key} class="${style[key]}" ${p1}>`
|
||||||
replace: (match, p1) => `<${key} class="${style[key]}" ${p1}>`,
|
|
||||||
}));
|
}));
|
||||||
callback(bindings)
|
updateContent(bindings);
|
||||||
}
|
};
|
||||||
update_and_assign((e: Object) => updateDocumentationContent(app, e));
|
|
||||||
|
const appHidden = document.getElementById("app")?.classList.contains("hidden");
|
||||||
|
if (appHidden) {
|
||||||
|
toggleElementVisibility("app", false);
|
||||||
|
toggleElementVisibility("documentation", true);
|
||||||
|
app.exampleIsPlaying = false;
|
||||||
|
} else {
|
||||||
|
toggleElementVisibility("app", true);
|
||||||
|
toggleElementVisibility("documentation", false);
|
||||||
|
const style = createDocumentationStyle(app);
|
||||||
|
applyStyleBindings(style, (bindings: StyleBinding[]) => updateDocumentationContent(app, bindings));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the URL to the base URL
|
||||||
|
window.history.pushState({}, '', '/');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// export const showDocumentation = (app: Editor) => {
|
||||||
|
// /**
|
||||||
|
// * Shows or hides the documentation based on the current state of the app.
|
||||||
|
// * @param app - The Editor instance.
|
||||||
|
// */
|
||||||
|
// if (document.getElementById("app")?.classList.contains("hidden")) {
|
||||||
|
// document.getElementById("app")?.classList.remove("hidden");
|
||||||
|
// document.getElementById("documentation")?.classList.add("hidden");
|
||||||
|
// app.exampleIsPlaying = false;
|
||||||
|
// } else {
|
||||||
|
// document.getElementById("app")?.classList.add("hidden");
|
||||||
|
// document.getElementById("documentation")?.classList.remove("hidden");
|
||||||
|
// // Load and convert Markdown content from the documentation file
|
||||||
|
// let style = createDocumentationStyle(app);
|
||||||
|
// function update_and_assign(callback: Function) {
|
||||||
|
// let bindings = Object.keys(style).map((key) => ({
|
||||||
|
// type: "output",
|
||||||
|
// regex: new RegExp(`<${key}([^>]*)>`, "g"),
|
||||||
|
// //@ts-ignore
|
||||||
|
// replace: (match, p1) => `<${key} class="${style[key]}" ${p1}>`,
|
||||||
|
// }));
|
||||||
|
// callback(bindings)
|
||||||
|
// }
|
||||||
|
// update_and_assign((e: Object) => updateDocumentationContent(app, e));
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
export const hideDocumentation = () => {
|
export const hideDocumentation = () => {
|
||||||
/**
|
/**
|
||||||
* Hides the documentation section and shows the main application.
|
* Hides the documentation section and shows the main application.
|
||||||
@ -232,7 +249,7 @@ export const updateDocumentationContent = (app: Editor, bindings: any) => {
|
|||||||
|
|
||||||
function _update_and_assign(callback: Function) {
|
function _update_and_assign(callback: Function) {
|
||||||
const converted_markdown = converter.makeHtml(
|
const converted_markdown = converter.makeHtml(
|
||||||
app.docs[app.currentDocumentationPane],
|
app.docs[app.currentDocumentationPane]!,
|
||||||
);
|
);
|
||||||
callback(converted_markdown)
|
callback(converted_markdown)
|
||||||
}
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
export const atelier = (application: Editor): string => {
|
export const atelier = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory, key_shortcut } from "../../Documentation";
|
import { makeExampleFactory, key_shortcut } from "../Documentation";
|
||||||
|
|
||||||
export const code = (application: Editor): string => {
|
export const code = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -43,7 +43,7 @@ beat(1) :: snd('bd').out()
|
|||||||
|
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"More complex conditions using ?",
|
"More complex conditions using ?",
|
||||||
@ -53,7 +53,7 @@ beat(4) ? snd('kick').out() : beat(2) :: snd('snare').out()
|
|||||||
// (true) ? log('very true') : log('very false')
|
// (true) ? log('very true') : log('very false')
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -64,7 +64,7 @@ beat(4) ? snd('kick').out() : beat(2) :: snd('snare').out()
|
|||||||
!beat(2) :: beat(0.5) :: snd('clap').out()
|
!beat(2) :: beat(0.5) :: snd('clap').out()
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
# About crashes and bugs
|
# About crashes and bugs
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ ${makeExample(
|
|||||||
qjldfqsdklqsjdlkqjsdlqkjdlksjd
|
qjldfqsdklqsjdlkqjsdlqkjdlksjd
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
7
src/Docs/basics/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export { introduction } from './welcome';
|
||||||
|
export { atelier } from './atelier';
|
||||||
|
export { software_interface } from './interface';
|
||||||
|
export { shortcuts } from './keyboard';
|
||||||
|
export { code } from './code';
|
||||||
|
export { mouse } from './mouse';
|
||||||
|
export { interaction } from './interaction';
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export const interaction = (application: Editor): string => {
|
export const interaction = (application: Editor): string => {
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { key_shortcut, makeExampleFactory } from "../../Documentation";
|
import { key_shortcut, makeExampleFactory } from "../Documentation";
|
||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import topos_arch from "./topos_arch.svg";
|
import topos_arch from "./topos_arch.svg";
|
||||||
import many_universes from "./many_universes.svg";
|
import many_universes from "./many_universes.svg";
|
||||||
@ -44,7 +44,7 @@ beat(1) :: script(1) // Calling local script n°1
|
|||||||
flip(4) :: beat(.5) :: script(2) // Calling script n°2
|
flip(4) :: beat(.5) :: script(2) // Calling script n°2
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Script execution can become musical too!",
|
"Script execution can become musical too!",
|
||||||
@ -55,7 +55,7 @@ flip(4) :: beat([.5, .25].beat(16)) :: script(
|
|||||||
[5, 6, 7, 8].beat())
|
[5, 6, 7, 8].beat())
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
### Navigating the interface
|
### Navigating the interface
|
||||||
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { key_shortcut } from "../../Documentation";
|
import { key_shortcut } from "../Documentation";
|
||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
export const shortcuts = (app: Editor): string => {
|
export const shortcuts = (app: Editor): string => {
|
||||||
let makeExample = makeExampleFactory(app);
|
let makeExample = makeExampleFactory(app);
|
||||||
@ -74,7 +74,7 @@ ${makeExample(
|
|||||||
beat(fill() ? 1/4 : 1/2)::sound('cp').out()
|
beat(fill() ? 1/4 : 1/2)::sound('cp').out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
export const mouse = (app: Editor): string => {
|
export const mouse = (app: Editor): string => {
|
||||||
let makeExample = makeExampleFactory(app);
|
let makeExample = makeExampleFactory(app);
|
||||||
@ -28,7 +28,7 @@ beat(.25) :: sound('sine')
|
|||||||
.room(0.35).size(4).out()
|
.room(0.35).size(4).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ beat(.25) :: sound('sine')
|
|||||||
.note(noteX())
|
.note(noteX())
|
||||||
.room(0.35).size(4).out()`,
|
.room(0.35).size(4).out()`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Mouse and Arrays
|
## Mouse and Arrays
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ log([4,5,6,7].mouseY())
|
|||||||
|
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
@ -1,6 +1,6 @@
|
|||||||
import { makeExampleFactory, key_shortcut } from "../../Documentation";
|
import { makeExampleFactory, key_shortcut } from "../Documentation";
|
||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { examples } from "../../examples/excerpts";
|
import { examples } from "../excerpts";
|
||||||
|
|
||||||
export const introduction = (application: Editor): string => {
|
export const introduction = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -940,7 +940,7 @@ const completionDatabase: CompletionDatabase = {
|
|||||||
description: "Noise amount in the signal (0-1)",
|
description: "Noise amount in the signal (0-1)",
|
||||||
example: "sound('triangle').noise(.25).out()",
|
example: "sound('triangle').noise(.25).out()",
|
||||||
},
|
},
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
export const inlineHoveringTips = hoverTooltip(
|
export const inlineHoveringTips = hoverTooltip(
|
||||||
(view: any, pos: any, side: any) => {
|
(view: any, pos: any, side: any) => {
|
||||||
@ -962,8 +962,7 @@ export const inlineHoveringTips = hoverTooltip(
|
|||||||
) {
|
) {
|
||||||
return { dom: document.createElement("div") };
|
return { dom: document.createElement("div") };
|
||||||
}
|
}
|
||||||
let completion =
|
let completion = completionDatabase[text.slice(start - from, end - from)]!;
|
||||||
completionDatabase[text.slice(start - from, end - from)] || {};
|
|
||||||
let divContent = `
|
let divContent = `
|
||||||
<h1 class="text-brightwhite text-base pb-1">${completion.name} [<em class="text-white">${completion.category}</em>]</h1>
|
<h1 class="text-brightwhite text-base pb-1">${completion.name} [<em class="text-white">${completion.category}</em>]</h1>
|
||||||
<p class="text-base pl-4">${completion.description}</p>
|
<p class="text-base pl-4">${completion.description}</p>
|
||||||
@ -986,13 +985,13 @@ export const toposCompletions = (context: CompletionContext) => {
|
|||||||
from: word.from,
|
from: word.from,
|
||||||
options: Object.keys(completionDatabase).map((key) => ({
|
options: Object.keys(completionDatabase).map((key) => ({
|
||||||
label: key,
|
label: key,
|
||||||
type: completionDatabase[key].category,
|
type: completionDatabase[key]!.category,
|
||||||
info: () => {
|
info: () => {
|
||||||
let div = document.createElement("div");
|
let div = document.createElement("div");
|
||||||
div.innerHTML = `
|
div.innerHTML = `
|
||||||
<h1 class="text-brightwhite text-base pb-1">${completionDatabase[key].name} [<em class="text-white">${completionDatabase[key].category}</em>]</h1>
|
<h1 class="text-brightwhite text-base pb-1">${completionDatabase[key]!.name} [<em class="text-white">${completionDatabase[key]!.category}</em>]</h1>
|
||||||
<p class="text-base pl-4">${completionDatabase[key].description}</p>
|
<p class="text-base pl-4">${completionDatabase[key]!.description}</p>
|
||||||
<div class="overflow-hidden overflow-scroll rounded px-2 ml-4 mt-2 bg-neutral-800"><code class="text-sm">${completionDatabase[key].example}</code></div>
|
<div class="overflow-hidden overflow-scroll rounded px-2 ml-4 mt-2 bg-neutral-800"><code class="text-sm">${completionDatabase[key]!.example}</code></div>
|
||||||
`;
|
`;
|
||||||
div.classList.add("px-4", "py-2", "rounded-lg", "w-92");
|
div.classList.add("px-4", "py-2", "rounded-lg", "w-92");
|
||||||
return div;
|
return div;
|
||||||
@ -1000,6 +999,7 @@ export const toposCompletions = (context: CompletionContext) => {
|
|||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
return null
|
||||||
};
|
};
|
||||||
|
|
||||||
export const soundCompletions = (context: CompletionContext) => {
|
export const soundCompletions = (context: CompletionContext) => {
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const amplitude = (application: Editor): string => {
|
export const amplitude = (application: Editor): string => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -21,7 +21,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
beat(.5)::snd('cp').vel($(1)%10 / 10).out()`,
|
beat(.5)::snd('cp').vel($(1)%10 / 10).out()`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Amplitude Enveloppe
|
## Amplitude Enveloppe
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ beat(.25)::sound('sawtooth')
|
|||||||
.smooth().out();
|
.smooth().out();
|
||||||
`,
|
`,
|
||||||
true,
|
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:
|
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:
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ beat(.25)::sound('sawtooth')
|
|||||||
.smooth().out();
|
.smooth().out();
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const audio_basics = (application: Editor): string => {
|
export const audio_basics = (application: Editor): string => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -23,7 +23,7 @@ beat(1) && sound('bd').out()
|
|||||||
beat(0.5) && sound('hh').out()
|
beat(0.5) && sound('hh').out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
These commands, in plain english, can be translated to:
|
These commands, in plain english, can be translated to:
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ 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();
|
beat(0.5) && sound('hh').delay(0.25).delaytime(0.125).out();
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
Now, it translates as follows:
|
Now, it translates as follows:
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ ${makeExample(
|
|||||||
beat(1)::sound('cp').out(2)
|
beat(1)::sound('cp').out(2)
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
Try to remove <ic>.out</ic>. You will see that no sound is playing at all!
|
Try to remove <ic>.out</ic>. You will see that no sound is playing at all!
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ beat(1) :: sound('pad').n(1)
|
|||||||
.velocity(0.25)
|
.velocity(0.25)
|
||||||
.pan(usine()).release(2).out()`,
|
.pan(usine()).release(2).out()`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Picking a specific sound
|
## Picking a specific sound
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ ${makeExample(
|
|||||||
beat(1) && sound('kick').n([1,2,3,4,5,6,7,8].pick()).out()
|
beat(1) && sound('kick').n([1,2,3,4,5,6,7,8].pick()).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
You can also use the <ic>:</ic> to pick a sample number directly from the <ic>sound</ic> function:
|
You can also use the <ic>:</ic> to pick a sample number directly from the <ic>sound</ic> function:
|
||||||
|
|
||||||
@ -127,7 +127,7 @@ ${makeExample(
|
|||||||
// Move your mouse to change the sample being used!
|
// Move your mouse to change the sample being used!
|
||||||
beat(.25) && sound('ST09').n(Math.floor(mouseX())).out()`,
|
beat(.25) && sound('ST09').n(Math.floor(mouseX())).out()`,
|
||||||
true,
|
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.
|
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.
|
||||||
@ -160,7 +160,7 @@ beat(1)::sound('hh').out()
|
|||||||
beat(2)::sound('cp').orbit(2).room(0.5).size(8).out()
|
beat(2)::sound('cp').orbit(2).room(0.5).size(8).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## The art of chaining
|
## The art of chaining
|
||||||
|
|
||||||
@ -175,7 +175,7 @@ beat(0.25) && sound('fhh')
|
|||||||
.cutoff(usine(1/2) * 5000)
|
.cutoff(usine(1/2) * 5000)
|
||||||
.out()`,
|
.out()`,
|
||||||
true,
|
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.
|
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.
|
||||||
`;
|
`;
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const distortion = (application: Editor): string => {
|
export const distortion = (application: Editor): string => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -24,7 +24,7 @@ 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()
|
beat(.5)::snd('pad').crush([16, 8, 4].beat(2)).clip(.5).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const effects = (application: Editor): string => {
|
export const effects = (application: Editor): string => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -29,7 +29,7 @@ ${makeExample(
|
|||||||
beat(2)::snd('cp').room(0.5).size(4).out()
|
beat(2)::snd('cp').room(0.5).size(4).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
## Delay
|
## Delay
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ beat(2)::snd('cp').delay(0.5).delaytime(0.75).delayfb(0.8).out()
|
|||||||
beat(4)::snd('snare').out()
|
beat(4)::snd('snare').out()
|
||||||
beat(1)::snd('kick').out()`,
|
beat(1)::snd('kick').out()`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Phaser
|
## Phaser
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ rhythm(.5, 7, 8)::sound('wt_stereo')
|
|||||||
.room(0.5).size(4).out()
|
.room(0.5).size(4).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Distorsion, saturation, destruction
|
## Distorsion, saturation, destruction
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ 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()
|
beat(.5)::snd('pad').crush([16, 8, 4].beat(2)).clip(.5).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
## Vibrato
|
## Vibrato
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ beat(1) :: sound('triangle')
|
|||||||
.vibmod([1,2,4,8].beat(2))
|
.vibmod([1,2,4,8].beat(2))
|
||||||
.out()`,
|
.out()`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Compression
|
## Compression
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const filters = (application: Editor): string => {
|
export const filters = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -16,7 +16,7 @@ ${makeExample(
|
|||||||
"Filtering the high frequencies of an oscillator",
|
"Filtering the high frequencies of an oscillator",
|
||||||
`beat(.5) :: sound('sawtooth').cutoff(50 + usine(1/8) * 2000).out()`,
|
`beat(.5) :: sound('sawtooth').cutoff(50 + usine(1/8) * 2000).out()`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
These filters all come with their own set of parameters. Note that we are describing the parameters of the three different filter types here. Choose the right parameters depending on the filter type you are using:
|
These filters all come with their own set of parameters. Note that we are describing the parameters of the three different filter types here. Choose the right parameters depending on the filter type you are using:
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ ${makeExample(
|
|||||||
"Filtering a bass",
|
"Filtering a bass",
|
||||||
`beat(.5) :: sound('jvbass').lpf([250,1000,8000].beat()).out()`,
|
`beat(.5) :: sound('jvbass').lpf([250,1000,8000].beat()).out()`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
### Highpass filter
|
### Highpass filter
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ ${makeExample(
|
|||||||
"Filtering a noise source",
|
"Filtering a noise source",
|
||||||
`beat(.5) :: sound('gtr').hpf([250,1000, 2000, 3000, 4000].beat()).end(0.5).out()`,
|
`beat(.5) :: sound('gtr').hpf([250,1000, 2000, 3000, 4000].beat()).end(0.5).out()`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
### Bandpass filter
|
### Bandpass filter
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ ${makeExample(
|
|||||||
"Sweeping the filter on the same guitar sample",
|
"Sweeping the filter on the same guitar sample",
|
||||||
`beat(.5) :: sound('gtr').bandf(100 + usine(1/8) * 4000).end(0.5).out()`,
|
`beat(.5) :: sound('gtr').bandf(100 + usine(1/8) * 4000).end(0.5).out()`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
Alternatively, <ic>lpf</ic>, <ic>hpf</ic> and <ic>bpf</ic> can take a second argument, the **resonance**.
|
Alternatively, <ic>lpf</ic>, <ic>hpf</ic> and <ic>bpf</ic> can take a second argument, the **resonance**.
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ ${makeExample(
|
|||||||
"Filtering a bass",
|
"Filtering a bass",
|
||||||
`beat(.5) :: sound('jvbass').ftype(['12db', '24db'].beat(4)).lpf([250,1000,8000].beat()).out()`,
|
`beat(.5) :: sound('jvbass').ftype(['12db', '24db'].beat(4)).lpf([250,1000,8000].beat()).out()`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Filter envelopes
|
## Filter envelopes
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ ${makeExample(
|
|||||||
.cutoff(5000).lpa([0.05, 0.25, 0.5].beat(2))
|
.cutoff(5000).lpa([0.05, 0.25, 0.5].beat(2))
|
||||||
.lpenv(-8).lpq(10).out()`,
|
.lpenv(-8).lpq(10).out()`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
### Highpass envelope
|
### Highpass envelope
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ ${makeExample(
|
|||||||
.hcutoff(1000).hpa([0.05, 0.25, 0.5].beat(2))
|
.hcutoff(1000).hpa([0.05, 0.25, 0.5].beat(2))
|
||||||
.hpenv(8).hpq(10).out()`,
|
.hpenv(8).hpq(10).out()`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
### Bandpass envelope
|
### Bandpass envelope
|
||||||
|
|
||||||
@ -138,7 +138,7 @@ ${makeExample(
|
|||||||
.bpenv(-4).release(2).out()
|
.bpenv(-4).release(2).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
`;
|
`;
|
||||||
6
src/Docs/learning/audio_engine/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export { amplitude } from './amplitude';
|
||||||
|
export { effects } from './effects';
|
||||||
|
export { sampler } from './sampler';
|
||||||
|
export { synths } from './synths';
|
||||||
|
export { filters } from './filters';
|
||||||
|
export { audio_basics } from './audio_basics';
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const sampler = (application: Editor): string => {
|
export const sampler = (application: Editor): string => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -39,7 +39,7 @@ beat(.5)::snd('pad').begin(0.2)
|
|||||||
.clip(1).out()
|
.clip(1).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
|
|
||||||
## Playback speed / pitching samples
|
## Playback speed / pitching samples
|
||||||
@ -53,7 +53,7 @@ beat(0.5)::sound('notes')
|
|||||||
.speed([1,2,3,4].palindrome().beat(0.5)).out()
|
.speed([1,2,3,4].palindrome().beat(0.5)).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
It also works by using negative values. It reverses the playback:
|
It also works by using negative values. It reverses the playback:
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ beat(0.5)::sound('notes')
|
|||||||
.speed(-[1,2,3,4].palindrome().beat(0.5)).out()
|
.speed(-[1,2,3,4].palindrome().beat(0.5)).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
Of course you can play melodies using samples:
|
Of course you can play melodies using samples:
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ beat(0.5)::sound('notes')
|
|||||||
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.5)).out()
|
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.5)).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Panning
|
## Panning
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ beat(0.25)::sound('notes')
|
|||||||
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.25)).out()
|
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.25)).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
## Looping over a sample
|
## Looping over a sample
|
||||||
@ -110,7 +110,7 @@ beat(0.25)::sound('fikea').loop(1)
|
|||||||
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.25)).out()
|
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.25)).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Stretching a sample
|
## Stretching a sample
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ ${makeExample(
|
|||||||
beat(4) :: sound('amen1').n(11).stretch(4).out()
|
beat(4) :: sound('amen1').n(11).stretch(4).out()
|
||||||
beat(1) :: sound('kick').shape(0.35).out()`,
|
beat(1) :: sound('kick').shape(0.35).out()`,
|
||||||
true,
|
true,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
## Cutting samples
|
## Cutting samples
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ beat(0.25)::sound('notes')
|
|||||||
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.25)).out()
|
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.25)).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
You can also use <ic>clip</ic> to cut the sample everytime a new sample comes in:
|
You can also use <ic>clip</ic> to cut the sample everytime a new sample comes in:
|
||||||
|
|
||||||
@ -155,7 +155,7 @@ beat(0.125)::sound('notes')
|
|||||||
+ [-12,12].beat()).out()
|
+ [-12,12].beat()).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Adding vibrato to samples
|
## Adding vibrato to samples
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ ${makeExample(
|
|||||||
beat(1)::sound('fhang').vib([1, 2, 4].bar()).vibmod([0.5, 2].beat()).out()
|
beat(1)::sound('fhang').vib([1, 2, 4].bar()).vibmod([0.5, 2].beat()).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
`;
|
`;
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const synths = (application: Editor): string => {
|
export const synths = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory, key_shortcut } from "../../Documentation";
|
import { makeExampleFactory, key_shortcut } from "../Documentation";
|
||||||
|
|
||||||
export const midi = (application: Editor): string => {
|
export const midi = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -27,7 +27,7 @@ ${makeExample(
|
|||||||
midi_outputs()
|
midi_outputs()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>midi_output(output_name: string)</ic>: enter your desired output to connect to it.
|
- <ic>midi_output(output_name: string)</ic>: enter your desired output to connect to it.
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ ${makeExample(
|
|||||||
midi_output("MIDI Rocket-Trumpet")
|
midi_output("MIDI Rocket-Trumpet")
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
That's it! You are now ready to play with MIDI.
|
That's it! You are now ready to play with MIDI.
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ ${makeExample(
|
|||||||
rhythm(.5, 5, 8) :: midi(50).out()
|
rhythm(.5, 5, 8) :: midi(50).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"MIDI note using three parameters: note, velocity, channel",
|
"MIDI note using three parameters: note, velocity, channel",
|
||||||
@ -64,7 +64,7 @@ ${makeExample(
|
|||||||
rhythm(.5, 5, 8) :: midi(50, 50 + usine(.5) * 20, 0).out()
|
rhythm(.5, 5, 8) :: midi(50, 50 + usine(.5) * 20, 0).out()
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"MIDI note by passing an object",
|
"MIDI note by passing an object",
|
||||||
@ -73,7 +73,7 @@ ${makeExample(
|
|||||||
rhythm(.5, 5, 8) :: midi({note: 50, velocity: 50 + usine(.5) * 20, channel: 0}).out()
|
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:
|
We can now have some fun and starting playing a small piano piece:
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ beat(.75) && midi([64, 67, 69].beat()).sustain(0.05).out()
|
|||||||
beat(.25) && midi([64, 67, 69].beat() + 24).sustain(0.05).out()
|
beat(.25) && midi([64, 67, 69].beat() + 24).sustain(0.05).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Control and Program Changes
|
## Control and Program Changes
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ 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})
|
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>).
|
- <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>).
|
||||||
|
|
||||||
@ -110,7 +110,7 @@ ${makeExample(
|
|||||||
program_change([1,2,3,4,5,6,7,8].pick(), 1)
|
program_change([1,2,3,4,5,6,7,8].pick(), 1)
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
## System Exclusive Messages
|
## System Exclusive Messages
|
||||||
@ -124,7 +124,7 @@ ${makeExample(
|
|||||||
sysex(0x90, 0x40, 0x7f)
|
sysex(0x90, 0x40, 0x7f)
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Clock
|
## Clock
|
||||||
|
|
||||||
@ -136,7 +136,7 @@ ${makeExample(
|
|||||||
beat(.25) && midi_clock() // Sending clock to MIDI device from the global buffer
|
beat(.25) && midi_clock() // Sending clock to MIDI device from the global buffer
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Using midi with ziffers
|
## Using midi with ziffers
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ ${makeExample(
|
|||||||
z1('0 2 e 5 2 q 4 2').midi().port(2).channel(4).out()
|
z1('0 2 e 5 2 q 4 2').midi().port(2).channel(4).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Setting the channel within the pattern",
|
"Setting the channel within the pattern",
|
||||||
@ -156,7 +156,7 @@ ${makeExample(
|
|||||||
z1('(0 2 e 5 2):0 (4 2):1').midi().out()
|
z1('(0 2 e 5 2):0 (4 2):1').midi().out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
export const osc = (application: Editor): string => {
|
export const osc = (application: Editor): string => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -31,7 +31,7 @@ beat(1)::getOsc()
|
|||||||
// 1 : {data: Array(2), address: '/lala'}
|
// 1 : {data: Array(2), address: '/lala'}
|
||||||
// 2 : {data: Array(2), address: '/lala'}`,
|
// 2 : {data: Array(2), address: '/lala'}`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
### Filtered messages
|
### Filtered messages
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ beat(1)::getOsc("/lala")
|
|||||||
// 2 : (2) [82, 'bob']
|
// 2 : (2) [82, 'bob']
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Output
|
## Output
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ ${makeExample(
|
|||||||
beat(1)::sound('cp').speed(2).vel(0.5).osc()
|
beat(1)::sound('cp').speed(2).vel(0.5).osc()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
This is a simple **OSC** message that will inherit all the properties of the sound. You can also send customized OSC messages using the <ic>osc()</ic> function:
|
This is a simple **OSC** message that will inherit all the properties of the sound. You can also send customized OSC messages using the <ic>osc()</ic> function:
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ ${makeExample(
|
|||||||
osc('/my/osc/address', 5000, 1, 2, 3)
|
osc('/my/osc/address', 5000, 1, 2, 3)
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
3
src/Docs/learning/samples/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { loading_samples } from './loading_samples';
|
||||||
|
export { sample_banks } from './sample_banks';
|
||||||
|
export { sample_list } from './sample_list';
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const loading_samples = (application: Editor): string => {
|
export const loading_samples = (application: Editor): string => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -19,7 +19,7 @@ ${makeExample(
|
|||||||
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
||||||
}, 'github:tidalcycles/Dirt-Samples/master/');`,
|
}, 'github:tidalcycles/Dirt-Samples/master/');`,
|
||||||
true,
|
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:
|
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:
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ ${makeExample(
|
|||||||
`rhythm(.5, 5, 8)::sound('bd').n(ir(1,2)).end(1).out()
|
`rhythm(.5, 5, 8)::sound('bd').n(ir(1,2)).end(1).out()
|
||||||
`,
|
`,
|
||||||
true,
|
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:
|
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:
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ samples("github:Bubobubobubobubo/Dough-Samples/main");
|
|||||||
samples("github:Bubobubobubobubo/Dough-Amiga/main");
|
samples("github:Bubobubobubobubo/Dough-Amiga/main");
|
||||||
`,
|
`,
|
||||||
true,
|
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!
|
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!
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ samples("shabda:ocean")
|
|||||||
beat(1)::sound('ocean').clip(1).out()
|
beat(1)::sound('ocean').clip(1).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
You can also use the <ic>.n</ic> attribute like usual to load a different sample.
|
You can also use the <ic>.n</ic> attribute like usual to load a different sample.
|
||||||
`;
|
`;
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const sample_banks = (application: Editor): string => {
|
export const sample_banks = (application: Editor): string => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const samples_to_markdown = (
|
export const samples_to_markdown = (
|
||||||
application: Editor,
|
application: Editor,
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const cyclical_time = (app: Editor): string => {
|
export const cyclical_time = (app: Editor): string => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -22,7 +22,7 @@ ${makeExample(
|
|||||||
beat([1,1/2,1/4,1/8].beat(2)) :: sound('hat').n(0).out()
|
beat([1,1/2,1/4,1/8].beat(2)) :: sound('hat').n(0).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Some sort of ringtone",
|
"Some sort of ringtone",
|
||||||
@ -42,7 +42,7 @@ 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();
|
beat([1,0.75].beat(2)) :: blip([50, 100].beat(2)).pan(r(0,1)).out();
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Beat can match multiple values",
|
"Beat can match multiple values",
|
||||||
@ -50,7 +50,7 @@ ${makeExample(
|
|||||||
beat([.5, 1.25])::sound('hat').out()
|
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>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.
|
- <ic>number</ic>: if <ic>number = 1</ic>, the function will return <ic>true</ic> every pulse. Lists can be used too.
|
||||||
@ -64,7 +64,7 @@ pulse([24, 16])::sound('hat').ad(0, .02).out()
|
|||||||
pulse([48, [36,24].dur(4, 1)])::sound('fhardkick').ad(0, .1).out()
|
pulse([48, [36,24].dur(4, 1)])::sound('fhardkick').ad(0, .1).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"pulse is the OG rhythmic function in Topos",
|
"pulse is the OG rhythmic function in Topos",
|
||||||
`
|
`
|
||||||
@ -72,7 +72,7 @@ pulse([48, 24, 16].beat(4)) :: sound('linnhats').out()
|
|||||||
beat(1)::snd(['bd', '808oh'].beat(1)).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.
|
- <ic>bar(n: number | number[] = 1, offset: number = 1)</ic>: return true every _n_ bars.
|
||||||
@ -86,7 +86,7 @@ bar(1)::sound('kick').out()
|
|||||||
beat(1)::sound('hat').speed(2).out()
|
beat(1)::sound('hat').speed(2).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -98,7 +98,7 @@ beat(1, 0.5)::sound('hat').speed(4).out()
|
|||||||
bar(1, 0.5)::sound('sn').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.
|
- <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.
|
||||||
|
|
||||||
@ -110,7 +110,7 @@ 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
|
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
|
## XOX Style sequencers
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ seq('ooxo')::sound('fsoftsnare').out()
|
|||||||
seq('xoxo', 0.25)::sound('fhh').out()
|
seq('xoxo', 0.25)::sound('fhh').out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Another sequence using more complex parameters",
|
"Another sequence using more complex parameters",
|
||||||
@ -136,7 +136,7 @@ seq('ooxo', [1, 2].bar())::sound('fsoftsnare').speed(0.5).out()
|
|||||||
seq(['xoxoxoxx', 'xxoo'].bar())::sound('fhh').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>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>expr: string</ic>: any string composed of <ic>x</ic> or <ic>o</ic> like so: <ic>"xooxoxxoxoo"</ic>.
|
||||||
@ -160,7 +160,7 @@ function complexPat() {
|
|||||||
fullseq('xooxooxx', 4) ? simplePat() : complexPat()
|
fullseq('xooxooxx', 4) ? simplePat() : complexPat()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -182,7 +182,7 @@ rhythm(.5, 7, 8)::sound('sine')
|
|||||||
rhythm(.5, 3, 8)::sound('sine').freq(500).ad(0, .5).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.
|
- <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.
|
||||||
@ -195,7 +195,7 @@ ${makeExample(
|
|||||||
oneuclid(7,16) :: snd('east').end(0.5).n(irand(3,5)).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>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.
|
- <ic>binrhythm(divisor: number, n: number): boolean: boolean</ic>: iterator-less version of the binary rhythm generator.
|
||||||
@ -208,7 +208,7 @@ 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()
|
beat(.5) && bin($(2), 34) && snd('snare').n([3,5].beat(1)).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"binrhythm for fast cool binary rhythms!",
|
"binrhythm for fast cool binary rhythms!",
|
||||||
@ -222,7 +222,7 @@ binrhythm([.5, .25].beat(1), 30) && snd('wt_granular').n(a)
|
|||||||
.room(1).size(1).out()
|
.room(1).size(1).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Submarine jungle music",
|
"Submarine jungle music",
|
||||||
@ -234,7 +234,7 @@ beat(.5) && bin($(1), 911) && snd('ST69').n([2,3,4].beat())
|
|||||||
beat(.5) && sound('amencutup').n(irand(2,7)).shape(0.3).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.
|
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.
|
||||||
|
|
||||||
@ -248,7 +248,7 @@ prob(60)::beat(.5) && euclid($(2), 3, 8) && snd('mash')
|
|||||||
prob(80)::beat(.5) && sound(['hh', 'hat'].pick()).out()
|
prob(80)::beat(.5) && sound(['hh', 'hat'].pick()).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
import pulses from "./pulses.svg";
|
import pulses from "./pulses.svg";
|
||||||
|
|
||||||
export const linear_time = (app: Editor): string => {
|
export const linear_time = (app: Editor): string => {
|
||||||
@ -27,7 +27,7 @@ ${makeExample(
|
|||||||
log(\`\$\{cbar()}\, \$\{cbeat()\}, \$\{cpulse()\}\`)
|
log(\`\$\{cbar()}\, \$\{cbeat()\}, \$\{cpulse()\}\`)
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
### BPM and PPQN
|
### BPM and PPQN
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ if((cbar() % 4) > 1) {
|
|||||||
beat([.5, .5, 1, .25].beat(0.5)) :: sound('shaker').out()
|
beat([.5, .5, 1, .25].beat(0.5)) :: sound('shaker').out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Time Warping
|
## Time Warping
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ flip(3) :: beat([.25,.5].beat(.5)) :: sound('dr')
|
|||||||
beat(.25) :: warp([12, 48, 24, 1, 120, 30].pick())
|
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>.
|
- <ic>beat_warp(beat: number)</ic>: this function jumps to the _n_ beat of the clock. The first beat is <ic>1</ic>.
|
||||||
|
|
||||||
@ -126,12 +126,12 @@ beat([.25,.125].beat(2))::snd('arpy')
|
|||||||
.cutoff(usine(.5) * 5000).resonance(20).gain(0.3)
|
.cutoff(usine(.5) * 5000).resonance(20).gain(0.3)
|
||||||
.end(0.8).room(0.9).size(0.9).n(3).out();
|
.end(0.8).room(0.9).size(0.9).n(3).out();
|
||||||
beat(.5) :: snd('arpy').note(
|
beat(.5) :: snd('arpy').note(
|
||||||
[30, 33, 35].repeatAll(4).beat(1) - [12,0].beat(0.5)).out()
|
[30, 33, 35].repeat(4).beat(1) - [12,0].beat(0.5)).out()
|
||||||
// Comment me to stop warping!
|
// Comment me to stop warping!
|
||||||
beat(1) :: beat_warp([2,4,5,10,11].pick())
|
beat(1) :: beat_warp([2,4,5,10,11].pick())
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Transport-based rhythm generators
|
## Transport-based rhythm generators
|
||||||
|
|
||||||
@ -145,7 +145,7 @@ 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
|
onbeat(1.5,2.5,3.5, 3.75)::snd('hat').gain(r(0.9,1.1)).out() // Cool high-hats
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Let's do something more complex",
|
"Let's do something more complex",
|
||||||
@ -157,7 +157,7 @@ beat([.25, 1/8].beat(1.5))::snd('hat').n(2)
|
|||||||
.pan(usine()).out()
|
.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.
|
- <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.
|
||||||
|
|
||||||
@ -172,19 +172,19 @@ 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()
|
oncount([1,3,5.5,7,7.5,8],8) :: sound('hh').gain(irand(1.0,4.0)).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Using oncount to create rhythms with a custom meter",
|
"Using oncount to create rhythms with a custom meter",
|
||||||
`
|
`
|
||||||
bpm(200)
|
tempo(200)
|
||||||
oncount([1, 5, 9, 13],16) :: sound('808bd').n(4).shape(0.5).gain(1.0).out()
|
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([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([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()
|
oncount([1, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16],16) :: sound('hh').out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const long_forms = (app: Editor): string => {
|
export const long_forms = (app: Editor): string => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -20,7 +20,7 @@ ${makeExample(
|
|||||||
script([1,2,3,4].bar(8))
|
script([1,2,3,4].bar(8))
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
You can also give a specific duration to each section using <ic>.dur</ic>:
|
You can also give a specific duration to each section using <ic>.dur</ic>:
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ ${makeExample(
|
|||||||
script([1,2,3,4].dur(8, 2, 16, 4))
|
script([1,2,3,4].dur(8, 2, 16, 4))
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- **Use universes as well**. Transitions between universes are _seamless_, instantaneous. Just switch to different content if you ever hit the limitations of the current _universe_.
|
- **Use universes as well**. Transitions between universes are _seamless_, instantaneous. Just switch to different content if you ever hit the limitations of the current _universe_.
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ ${makeExample(
|
|||||||
flip(4) :: beat(1) :: snd('kick').out()
|
flip(4) :: beat(1) :: snd('kick').out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Clapping on the edge",
|
"Clapping on the edge",
|
||||||
@ -57,7 +57,7 @@ flip(2.5) :: beat(.5) :: snd('bd').out()
|
|||||||
beat(.25) :: sound('hat').end(0.1).cutoff(1200).pan(usine(1/4)).out()
|
beat(.25) :: sound('hat').end(0.1).cutoff(1200).pan(usine(1/4)).out()
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Good old true and false",
|
"Good old true and false",
|
||||||
@ -69,7 +69,7 @@ if (flip(4, 75)) {
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ic>flip</ic> is extremely powerful and is used internally for a lot of other Topos functions. You can also use it to think about **longer durations** spanning over multiple bars. Here is a silly composition that is using <ic>flip</ic> to generate a 4 bars long pattern.
|
<ic>flip</ic> is extremely powerful and is used internally for a lot of other Topos functions. You can also use it to think about **longer durations** spanning over multiple bars. Here is a silly composition that is using <ic>flip</ic> to generate a 4 bars long pattern.
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ if (flip(8)) {
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
You can use it everywhere to spice things up, including as a method parameter picker:
|
You can use it everywhere to spice things up, including as a method parameter picker:
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ ${makeExample(
|
|||||||
beat(.5)::snd(flip(2) ? 'kick' : 'hat').out()
|
beat(.5)::snd(flip(2) ? 'kick' : 'hat').out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>flipbar(n: number = 1)</ic>: this method works just like <ic>flip</ic> but counts in bars instead of beats. It allows you to think about even larger time cycles. You can also pair it with regular <ic>flip</ic> for writing complex and long-spanning algorithmic beats.
|
- <ic>flipbar(n: number = 1)</ic>: this method works just like <ic>flip</ic> but counts in bars instead of beats. It allows you to think about even larger time cycles. You can also pair it with regular <ic>flip</ic> for writing complex and long-spanning algorithmic beats.
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ flipbar(2) && a()
|
|||||||
flipbar(3) && b()
|
flipbar(3) && b()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Alternating over four bars",
|
"Alternating over four bars",
|
||||||
`
|
`
|
||||||
@ -132,7 +132,7 @@ flipbar(2)
|
|||||||
: beat(.5) && snd(['east', 'east:2'].beat(1)).out()
|
: beat(.5) && snd(['east', 'east:2'].beat(1)).out()
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
|
|
||||||
- <ic>onbar(bars: number | number[], n: number)</ic>: The second argument, <ic>n</ic>, is used to divide the time in a period of <ic>n</ic> consecutive bars. The first argument should be a bar number or a list of bar numbers to play on. For example, <ic>onbar([1, 4], 5)</ic> will return <ic>true</ic> on bar <ic>1</ic> and <ic>4</ic> but return <ic>false</ic> the rest of the time. You can easily divide time that way.
|
- <ic>onbar(bars: number | number[], n: number)</ic>: The second argument, <ic>n</ic>, is used to divide the time in a period of <ic>n</ic> consecutive bars. The first argument should be a bar number or a list of bar numbers to play on. For example, <ic>onbar([1, 4], 5)</ic> will return <ic>true</ic> on bar <ic>1</ic> and <ic>4</ic> but return <ic>false</ic> the rest of the time. You can easily divide time that way.
|
||||||
@ -156,7 +156,7 @@ if (onbar([1,2], 4)) {
|
|||||||
rhythm(.5, 2, 7) :: snd('snare').n(3).out();
|
rhythm(.5, 2, 7) :: snd('snare').n(3).out();
|
||||||
}`,
|
}`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
@ -1,4 +1,4 @@
|
|||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import times from "./times.svg";
|
import times from "./times.svg";
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { key_shortcut, makeExampleFactory } from "../../Documentation";
|
import { key_shortcut, makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
export const bonus = (application: Editor): string => {
|
export const bonus = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -39,7 +39,7 @@ ${makeExample(
|
|||||||
"Hydra integration",
|
"Hydra integration",
|
||||||
`beat(4) :: hydra.osc(3, 0.5, 2).out()`,
|
`beat(4) :: hydra.osc(3, 0.5, 2).out()`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
Close the documentation to see the effect: ${key_shortcut(
|
Close the documentation to see the effect: ${key_shortcut(
|
||||||
"Ctrl+D",
|
"Ctrl+D",
|
||||||
@ -56,7 +56,7 @@ beat(4) :: stop_hydra() // this one
|
|||||||
beat(4) :: hydra.hush() // or this one
|
beat(4) :: hydra.hush() // or this one
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
### Changing the resolution
|
### Changing the resolution
|
||||||
@ -67,7 +67,7 @@ ${makeExample(
|
|||||||
"Changing Hydra resolution",
|
"Changing Hydra resolution",
|
||||||
`hydra.setResolution(1024, 768)`,
|
`hydra.setResolution(1024, 768)`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
@ -101,6 +101,6 @@ beat(0.25)::gif({
|
|||||||
posY: ir(1, 800), // CSS Vertical Position
|
posY: ir(1, 800), // CSS Vertical Position
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
5
src/Docs/more/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export { about } from './about';
|
||||||
|
export { bonus } from './bonus';
|
||||||
|
export { oscilloscope } from './oscilloscope';
|
||||||
|
export { synchronisation } from './synchronisation';
|
||||||
|
export { visualization } from './visualization.ts';
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
export const oscilloscope = (application: Editor): string => {
|
export const oscilloscope = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -15,7 +15,7 @@ ${makeExample(
|
|||||||
beat(1)::sound('sine').freq(200).ad(0, .2).scope().out()
|
beat(1)::sound('sine').freq(200).ad(0, .2).scope().out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
Here is a layout of the scope configuration options:
|
Here is a layout of the scope configuration options:
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ scope({
|
|||||||
})
|
})
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Demo with multiple scope mode",
|
"Demo with multiple scope mode",
|
||||||
@ -57,7 +57,7 @@ scope({enabled: true, thickness: 8,
|
|||||||
size: 0.5, fftSize: 2048})
|
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 :)
|
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 { type Editor } from "../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
export const synchronisation = (app: Editor): string => {
|
export const synchronisation = (app: Editor): string => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { key_shortcut, makeExampleFactory } from "../../Documentation";
|
import { key_shortcut, makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
export const visualization = (application: Editor): string => {
|
export const visualization = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -19,9 +19,12 @@ While Topos is mainly being developed as a live coding environment for algorithm
|
|||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Hydra integration",
|
"Hydra integration",
|
||||||
`beat(4) :: hydra.osc(3, 0.5, 2).out()`,
|
`
|
||||||
false,
|
loadHydra() // Load Hydra first!
|
||||||
)}
|
beat(4) :: hydra.osc(3, 0.5, 2).out()
|
||||||
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
Close the documentation to see the effect: ${key_shortcut(
|
Close the documentation to see the effect: ${key_shortcut(
|
||||||
"Ctrl+D",
|
"Ctrl+D",
|
||||||
@ -34,11 +37,10 @@ Stopping **Hydra** is simple:
|
|||||||
${makeExample(
|
${makeExample(
|
||||||
"Stopping Hydra",
|
"Stopping Hydra",
|
||||||
`
|
`
|
||||||
beat(4) :: stop_hydra() // this one
|
|
||||||
beat(4) :: hydra.hush() // or this one
|
beat(4) :: hydra.hush() // or this one
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
### Changing the resolution
|
### Changing the resolution
|
||||||
|
|
||||||
@ -48,7 +50,7 @@ ${makeExample(
|
|||||||
"Changing Hydra resolution",
|
"Changing Hydra resolution",
|
||||||
`hydra.setResolution(1024, 768)`,
|
`hydra.setResolution(1024, 768)`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
### Hydra documentation
|
### Hydra documentation
|
||||||
|
|
||||||
@ -82,7 +84,7 @@ beat(0.25)::gif({
|
|||||||
posY: ir(1, 800), // CSS Vertical Position
|
posY: ir(1, 800), // CSS Vertical Position
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Canvas live coding
|
## Canvas live coding
|
||||||
|
|
||||||
@ -118,29 +120,29 @@ beat(0.5) && clear() && draw(context => {
|
|||||||
})
|
})
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Using draw with events and shapes",
|
"Using draw with events and shapes",
|
||||||
`
|
`
|
||||||
beat(0.25) && sound("bass1:5").pitch(rI(1,6)).draw(x => {
|
beat(0.25) && sound("bass1:5").pitch(rI(1,6)).draw(x => {
|
||||||
donut(x.pitch)
|
donut(x.pitch)
|
||||||
}).out()
|
}).out()
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Using draw with ziffers and shapes",
|
"Using draw with ziffers and shapes",
|
||||||
`
|
`
|
||||||
z1("1/8 (0 2 1 4)+(2 1)").sound("sine").ad(0.05,.25).clear()
|
z1("1/8 (0 2 1 4)+(2 1)").sound("sine").ad(0.05,.25).clear()
|
||||||
.draw(x => {
|
.draw(x => {
|
||||||
pie({slices:7,eaten:(7-x.pitch-1),fillStyle:"green", rotate: 250})
|
pie({slices:7,eaten:(7-x.pitch-1),fillStyle:"green", rotate: 250})
|
||||||
}).log("pitch").out()
|
}).log("pitch").out()
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
* <ic<image(url, x, y, width, height, rotation)</ic> - Draws an image to a canvas.
|
* <ic<image(url, x, y, width, height, rotation)</ic> - Draws an image to a canvas.
|
||||||
|
|
||||||
@ -166,22 +168,22 @@ Text can be drawn to canvas using the <ic>drawText()</ic> function. The function
|
|||||||
* <ic>drawText(text, fontSize, rotation, font, x, y)</ic> - Draws text to a canvas.
|
* <ic>drawText(text, fontSize, rotation, font, x, y)</ic> - Draws text to a canvas.
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Writing to canvas",
|
"Writing to canvas",
|
||||||
`
|
`
|
||||||
beat(0.5) && clear() && drawText("Hello world!", 100, 0, "Arial", 100, 100)
|
beat(0.5) && clear() && drawText("Hello world!", 100, 0, "Arial", 100, 100)
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
* <ic>randomChar(number, min, max)</ic> - Returns a number of random characters from given unicode range.
|
* <ic>randomChar(number, min, max)</ic> - Returns a number of random characters from given unicode range.
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Drawing random characters to canvas",
|
"Drawing random characters to canvas",
|
||||||
`
|
`
|
||||||
beat(0.5) && clear() && drawText(randomChar(10,1000,2000),30)
|
beat(0.5) && clear() && drawText(randomChar(10,1000,2000),30)
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
* <ic>emoji(size)</ic> - Returns a random emojis as text.
|
* <ic>emoji(size)</ic> - Returns a random emojis as text.
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../Documentation";
|
||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
|
|
||||||
export const chaining = (application: Editor): string => {
|
export const chaining = (application: Editor): string => {
|
||||||
@ -14,7 +14,7 @@ ${makeExample(
|
|||||||
beat(1)::sound('bd').speed(2).lpf(500).out()
|
beat(1)::sound('bd').speed(2).lpf(500).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
Method chains become fun if you add just a little bit of complexity to them. You can start to add conditions, start to register complex chains to be re-used later on, etc.. We will not remind you how to write basic chains. The whole documentation is full of examples! Let's explore more delicate patterns!
|
Method chains become fun if you add just a little bit of complexity to them. You can start to add conditions, start to register complex chains to be re-used later on, etc.. We will not remind you how to write basic chains. The whole documentation is full of examples! Let's explore more delicate patterns!
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ register('juxrev', n=>n.pan([0, 1]).speed([1, -1]))
|
|||||||
beat(1)::sound('fhh').juxrev().out()
|
beat(1)::sound('fhh').juxrev().out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
This is an extremely powerful construct. For example, you can use it to create synthesizer presets and reuse them later on. You can also define parameters for your registered functions. For example:
|
This is an extremely powerful construct. For example, you can use it to create synthesizer presets and reuse them later on. You can also define parameters for your registered functions. For example:
|
||||||
|
|
||||||
@ -52,15 +52,15 @@ rhythm(.25, [6, 8].beat(), 12)::sound('sine')
|
|||||||
.note([0, 2, 4, 5].scale('minor', 50).beat(0.5))
|
.note([0, 2, 4, 5].scale('minor', 50).beat(0.5))
|
||||||
.sub(8).out()`,
|
.sub(8).out()`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Registering chain for all events
|
## Registering chain for all events
|
||||||
|
|
||||||
The chain can also be registered automatically for all events. This is useful if you want to add a specific effect to all your events.
|
The chain can also be registered automatically for all events. This is useful if you want to add a specific effect to all your events.
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Registering chain to all events at once",
|
"Registering chain to all events at once",
|
||||||
`
|
`
|
||||||
z0("h 9 ^ <7 5 3 1>")
|
z0("h 9 ^ <7 5 3 1>")
|
||||||
.sound("sine")
|
.sound("sine")
|
||||||
.out()
|
.out()
|
||||||
@ -71,8 +71,8 @@ z1("0 4 3 2")
|
|||||||
|
|
||||||
all(x=>x.room(1).delay(rI(0,0.5)))
|
all(x=>x.room(1).delay(rI(0,0.5)))
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Logging values from the chain
|
## Logging values from the chain
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ ${makeExample(
|
|||||||
beat(1) :: sound("sine").pitch(rI(1,6)).log("note").out()
|
beat(1) :: sound("sine").pitch(rI(1,6)).log("note").out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Logging values from ziffers pattern",
|
"Logging values from ziffers pattern",
|
||||||
@ -92,7 +92,7 @@ ${makeExample(
|
|||||||
z1("0 3 2 5").scale("rocritonic").sound("sine").log("pitch","note","key").out()
|
z1("0 3 2 5").scale("rocritonic").sound("sine").log("pitch","note","key").out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Conditional chaining
|
## Conditional chaining
|
||||||
|
|
||||||
@ -108,7 +108,7 @@ beat(.5) && sound('fhh')
|
|||||||
.rarely(s => s.room(0.5).size(8).speed(0.5))
|
.rarely(s => s.room(0.5).size(8).speed(0.5))
|
||||||
.out()`,
|
.out()`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Chance to play a random note",
|
"Chance to play a random note",
|
||||||
`
|
`
|
||||||
@ -120,7 +120,7 @@ beat(.5) && sound('pluck').note(60)
|
|||||||
.room(0.5).size(3)
|
.room(0.5).size(3)
|
||||||
.out()`,
|
.out()`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
There is a growing collection of probability and chance methods you can use:
|
There is a growing collection of probability and chance methods you can use:
|
||||||
|
|
||||||
@ -152,7 +152,7 @@ ${makeExample(
|
|||||||
.sometimes(s => s.velocity(irand(50,100)))
|
.sometimes(s => s.velocity(irand(50,100)))
|
||||||
.out()`,
|
.out()`,
|
||||||
true,
|
true,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
## Ziffers
|
## Ziffers
|
||||||
|
|
||||||
@ -175,6 +175,6 @@ z1('s 0 5 7 0 3 7 0 2 7 0 1 7 0 1 6 5 4 3 2')
|
|||||||
.room(0.5).size(0.5).out()
|
.room(0.5).size(0.5).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)};
|
)};
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
export const functions = (application: Editor): string => {
|
export const functions = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -20,7 +20,7 @@ ${makeExample(
|
|||||||
beat(1) :: script(1)
|
beat(1) :: script(1)
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Calling mutliple scripts at the same time.",
|
"Calling mutliple scripts at the same time.",
|
||||||
@ -28,7 +28,7 @@ ${makeExample(
|
|||||||
beat(1) :: script(1, 3, 5)
|
beat(1) :: script(1, 3, 5)
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Math functions
|
## Math functions
|
||||||
|
|
||||||
@ -52,33 +52,6 @@ ${makeExample(
|
|||||||
"Scaling an LFO",
|
"Scaling an LFO",
|
||||||
`usine(1/2).linlin(0, 1, 0, 100)`,
|
`usine(1/2).linlin(0, 1, 0, 100)`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Delay functions
|
|
||||||
|
|
||||||
- <ic>delay(ms: number, func: Function): void</ic>: Delays the execution of a function by a given number of milliseconds.
|
|
||||||
|
|
||||||
${makeExample(
|
|
||||||
"Phased woodblocks",
|
|
||||||
`
|
|
||||||
// Some very low-budget version of phase music
|
|
||||||
beat(.5) :: delay(usine(.125) * 80, () => sound('east').out())
|
|
||||||
beat(.5) :: delay(50, () => sound('east').out())
|
|
||||||
`,
|
|
||||||
true,
|
|
||||||
)}
|
|
||||||
|
|
||||||
- <ic>delayr(ms: number, nb: number, func: Function): void</ic>: Delays the execution of a function by a given number of milliseconds, repeated a given number of times.
|
|
||||||
|
|
||||||
${makeExample(
|
|
||||||
"Another woodblock texture",
|
|
||||||
`
|
|
||||||
beat(1) :: delayr(50, 4, () => sound('east').speed([0.5,.25].beat()).out())
|
|
||||||
flip(2) :: beat(2) :: delayr(150, 4, () => sound('east').speed([0.5,.25].beat() * 4).out())
|
|
||||||
`,
|
|
||||||
true,
|
|
||||||
)};
|
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
export const generators = (application: Editor): string => {
|
export const generators = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -15,8 +15,8 @@ Once the generator is cached the values will be returned from the named cache ev
|
|||||||
The resulted values can be played using either <ic>pitch()</ic> or <ic>freq()</ic> or as Ziffers patterns. When playing the values using <ic>pitch()</ic> different scales and chained methods can be used to alter the result, for example <ic>mod(value: number)</ic> to limit the integer range or <ic>scale(name: string)</ic> etc. to change the resulting note.
|
The resulted values can be played using either <ic>pitch()</ic> or <ic>freq()</ic> or as Ziffers patterns. When playing the values using <ic>pitch()</ic> different scales and chained methods can be used to alter the result, for example <ic>mod(value: number)</ic> to limit the integer range or <ic>scale(name: string)</ic> etc. to change the resulting note.
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Simple looping generator function",
|
"Simple looping generator function",
|
||||||
`
|
`
|
||||||
function* simple() {
|
function* simple() {
|
||||||
let x = 0;
|
let x = 0;
|
||||||
while (x < 12) {
|
while (x < 12) {
|
||||||
@ -27,12 +27,12 @@ function* simple() {
|
|||||||
|
|
||||||
beat(.25) && sound("triangle").pitch(cache("simple",simple())).scale("minor").out()
|
beat(.25) && sound("triangle").pitch(cache("simple",simple())).scale("minor").out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Infinite frequency generator",
|
"Infinite frequency generator",
|
||||||
`
|
`
|
||||||
function* poly(x=0) {
|
function* poly(x=0) {
|
||||||
while (true) {
|
while (true) {
|
||||||
const s = Math.tan(x/10)+Math.sin(x/20);
|
const s = Math.tan(x/10)+Math.sin(x/20);
|
||||||
@ -43,8 +43,8 @@ ${makeExample(
|
|||||||
|
|
||||||
beat(.125) && sound("triangle").freq(cache("mathyshit",poly())).out()
|
beat(.125) && sound("triangle").freq(cache("mathyshit",poly())).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
When you want to dance with a dynamical system in controlled musical chaos, Topos is waiting for you:
|
When you want to dance with a dynamical system in controlled musical chaos, Topos is waiting for you:
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ ${makeExample(
|
|||||||
.log("freq").out()
|
.log("freq").out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Henon and his discrete music",
|
"Henon and his discrete music",
|
||||||
@ -123,7 +123,7 @@ ${makeExample(
|
|||||||
.log("freq").out()
|
.log("freq").out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
|
|
||||||
## OEIS integer sequences
|
## OEIS integer sequences
|
||||||
@ -153,7 +153,7 @@ Alternatively generators can be used with Ziffers to generate longer patterns. I
|
|||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Ziffers patterns using a generator functions",
|
"Ziffers patterns using a generator functions",
|
||||||
`
|
`
|
||||||
function* poly(x) {
|
function* poly(x) {
|
||||||
while (true) {
|
while (true) {
|
||||||
yield 64 * Math.pow(x, 6) - 480 * Math.pow(x, 4) + 720 * Math.pow(x, 2);
|
yield 64 * Math.pow(x, 6) - 480 * Math.pow(x, 4) + 720 * Math.pow(x, 2);
|
||||||
7
src/Docs/patterns/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export { chaining } from './chaining';
|
||||||
|
export { functions } from './functions';
|
||||||
|
export { generators } from './generators';
|
||||||
|
export { lfos } from './lfos';
|
||||||
|
export { patterns } from './patterns';
|
||||||
|
export { probabilities } from './probabilities';
|
||||||
|
export { variables } from './variables';
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
export const lfos = (application: Editor): string => {
|
export const lfos = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -17,7 +17,7 @@ ${makeExample(
|
|||||||
"Modulating the speed of a sample player using a sine LFO",
|
"Modulating the speed of a sample player using a sine LFO",
|
||||||
`beat(.25) && snd('cp').speed(1 + usine(0.25) * 2).out()`,
|
`beat(.25) && snd('cp').speed(1 + usine(0.25) * 2).out()`,
|
||||||
true,
|
true,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
- <ic>triangle(freq: number = 1, phase: number = 0): number</ic>: returns a triangle oscillation between <ic>-1</ic> and <ic>1</ic>.
|
- <ic>triangle(freq: number = 1, phase: number = 0): number</ic>: returns a triangle oscillation between <ic>-1</ic> and <ic>1</ic>.
|
||||||
- <ic>utriangle(freq: number = 1, phase: number = 0): number</ic>: returns a triangle oscillation between <ic>0</ic> and <ic>1</ic>. The <ic>u</ic> stands for _unipolar_.
|
- <ic>utriangle(freq: number = 1, phase: number = 0): number</ic>: returns a triangle oscillation between <ic>0</ic> and <ic>1</ic>. The <ic>u</ic> stands for _unipolar_.
|
||||||
@ -26,7 +26,7 @@ ${makeExample(
|
|||||||
"Modulating the speed of a sample player using a triangle LFO",
|
"Modulating the speed of a sample player using a triangle LFO",
|
||||||
`beat(.25) && snd('cp').speed(1 + utriangle(0.25) * 2).out()`,
|
`beat(.25) && snd('cp').speed(1 + utriangle(0.25) * 2).out()`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>saw(freq: number = 1, phase: number = 0): number</ic>: returns a sawtooth-like oscillation between <ic>-1</ic> and <ic>1</ic>.
|
- <ic>saw(freq: number = 1, phase: number = 0): number</ic>: returns a sawtooth-like oscillation between <ic>-1</ic> and <ic>1</ic>.
|
||||||
- <ic>usaw(freq: number = 1, phase: number = 0): number</ic>: returns a sawtooth-like oscillation between <ic>0</ic> and <ic>1</ic>. The <ic>u</ic> stands for _unipolar_.
|
- <ic>usaw(freq: number = 1, phase: number = 0): number</ic>: returns a sawtooth-like oscillation between <ic>0</ic> and <ic>1</ic>. The <ic>u</ic> stands for _unipolar_.
|
||||||
@ -35,7 +35,7 @@ ${makeExample(
|
|||||||
"Modulating the speed of a sample player using a saw LFO",
|
"Modulating the speed of a sample player using a saw LFO",
|
||||||
`beat(.25) && snd('cp').speed(1 + usaw(0.25) * 2).out()`,
|
`beat(.25) && snd('cp').speed(1 + usaw(0.25) * 2).out()`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>square(freq: number = 1, duty: number = .5): number</ic>: returns a square wave oscillation between <ic>-1</ic> and <ic>1</ic>. You can also control the duty cycle using the <ic>duty</ic> parameter.
|
- <ic>square(freq: number = 1, duty: number = .5): number</ic>: returns a square wave oscillation between <ic>-1</ic> and <ic>1</ic>. You can also control the duty cycle using the <ic>duty</ic> parameter.
|
||||||
- <ic>usquare(freq: number = 1, duty: number = .5): number</ic>: returns a square wave oscillation between <ic>0</ic> and <ic>1</ic>. The <ic>u</ic> stands for _unipolar_. You can also control the duty cycle using the <ic>duty</ic> parameter.
|
- <ic>usquare(freq: number = 1, duty: number = .5): number</ic>: returns a square wave oscillation between <ic>0</ic> and <ic>1</ic>. The <ic>u</ic> stands for _unipolar_. You can also control the duty cycle using the <ic>duty</ic> parameter.
|
||||||
@ -44,7 +44,7 @@ ${makeExample(
|
|||||||
"Modulating the speed of a sample player using a square LFO",
|
"Modulating the speed of a sample player using a square LFO",
|
||||||
`beat(.25) && snd('cp').speed(1 + usquare(0.25, 0, 0.25) * 2).out()`,
|
`beat(.25) && snd('cp').speed(1 + usquare(0.25, 0, 0.25) * 2).out()`,
|
||||||
true,
|
true,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
- <ic>noise(times: number = 1)</ic>: returns a random value between -1 and 1.
|
- <ic>noise(times: number = 1)</ic>: returns a random value between -1 and 1.
|
||||||
- <ic>unoise(times: number = 1)</ic>: returns a random value between 0 and 1.
|
- <ic>unoise(times: number = 1)</ic>: returns a random value between 0 and 1.
|
||||||
@ -53,7 +53,7 @@ ${makeExample(
|
|||||||
"Modulating the speed of a sample player using noise",
|
"Modulating the speed of a sample player using noise",
|
||||||
`beat(.25) && snd('cp').speed(1 + noise() * 2).out()`,
|
`beat(.25) && snd('cp').speed(1 + noise() * 2).out()`,
|
||||||
true,
|
true,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
export const patterns = (application: Editor): string => {
|
export const patterns = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -15,7 +15,7 @@ ${makeExample(
|
|||||||
beat(1)::sound('kick').out()
|
beat(1)::sound('kick').out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
can be turned into something more interesting like this easily:
|
can be turned into something more interesting like this easily:
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ beat([1, 0.5, 0.25].dur(0.75, 0.25, 1) / c)::sound(['kick', 'fsoftsnare'].beat(0
|
|||||||
.ad(0, .25).shape(usine(1/2)*0.5).speed([1, 2, 4].beat(0.5)).out()
|
.ad(0, .25).shape(usine(1/2)*0.5).speed([1, 2, 4].beat(0.5)).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
**Topos** comes with a lot of array methods to deal with musical patterns of increasing complexity. Some knowledge of patterns and how to use them will help you to break out of simple loops and repeating structures. The most basic JavaScript data structure is the [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array). Topos is extending it with custom methods to describe patterns that evolve over time. These methods can often be chained to compose more complex expressions: <ic>[1, 2, 3].repeatOdd(5).palindrome().beat()</ic>.
|
**Topos** comes with a lot of array methods to deal with musical patterns of increasing complexity. Some knowledge of patterns and how to use them will help you to break out of simple loops and repeating structures. The most basic JavaScript data structure is the [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array). Topos is extending it with custom methods to describe patterns that evolve over time. These methods can often be chained to compose more complex expressions: <ic>[1, 2, 3].repeatOdd(5).palindrome().beat()</ic>.
|
||||||
@ -45,7 +45,7 @@ beat([0.5, 1].beat(4)) :: sound('kick').out()
|
|||||||
beat(2)::snd('snare').shape(.5).out()
|
beat(2)::snd('snare').shape(.5).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Using beat to create arpeggios",
|
"Using beat to create arpeggios",
|
||||||
`
|
`
|
||||||
@ -63,7 +63,7 @@ beat([.5, .25].beat(0.5)) :: sound('sine')
|
|||||||
.out()
|
.out()
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Cool ambiance",
|
"Cool ambiance",
|
||||||
`
|
`
|
||||||
@ -74,7 +74,7 @@ flip(4)::beat(2)::snd('pad').n(2).shape(.5)
|
|||||||
.orbit(2).room(0.9).size(0.9).release(0.5).out()
|
.orbit(2).room(0.9).size(0.9).release(0.5).out()
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>bar(value: number = 1)</ic>: returns the next value every bar (if <ic>value = 1</ic>). Using a larger value will return the next value every <ic>n</ic> bars.
|
- <ic>bar(value: number = 1)</ic>: returns the next value every bar (if <ic>value = 1</ic>). Using a larger value will return the next value every <ic>n</ic> bars.
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ beat([1/4, 1/2].dur(1.5, 0.5))::sound(['jvbass', 'fikea'].bar())
|
|||||||
.out()
|
.out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Using beat and bar in the same example",
|
"Using beat and bar in the same example",
|
||||||
@ -102,7 +102,7 @@ beat([1, 0.5].beat()) :: sound(['bass3'].bar())
|
|||||||
.speed([1,2,3].beat())
|
.speed([1,2,3].beat())
|
||||||
.out()
|
.out()
|
||||||
`,
|
`,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>dur(...list: numbers[])</ic> : keeps the same value for a duration of <ic>n</ic> beats corresponding to the <ic>nth</ic> number of the list you provide.
|
- <ic>dur(...list: numbers[])</ic> : keeps the same value for a duration of <ic>n</ic> beats corresponding to the <ic>nth</ic> number of the list you provide.
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ beat(1)::sound(['kick', 'fsnare'].dur(3, 1))
|
|||||||
.n([0,3].dur(3, 1)).out()
|
.n([0,3].dur(3, 1)).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Patterning with ternary statements",
|
"Patterning with ternary statements",
|
||||||
@ -137,7 +137,7 @@ onbeat([0.5,0.8].beat(1),2) :: sound('snare').out()
|
|||||||
onbeat(0.5,0.8,1,1.5,2,2.5,3,4) :: sound('hh').out()
|
onbeat(0.5,0.8,1,1.5,2,2.5,3,4) :: sound('hh').out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Iteration using a counter
|
## Iteration using a counter
|
||||||
|
|
||||||
@ -150,7 +150,7 @@ ${makeExample(
|
|||||||
beat(0.5) :: sound("bd").gain(line(0,1,0.01).$("ramp")).out()
|
beat(0.5) :: sound("bd").gain(line(0,1,0.01).$("ramp")).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Manipulating notes and scales
|
## Manipulating notes and scales
|
||||||
|
|
||||||
@ -165,7 +165,7 @@ beat(0.25) :: snd('sine')
|
|||||||
.scale("minor").ad(0, .25).out()
|
.scale("minor").ad(0, .25).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>semitones(number[], ...args?)</ic>: Create scale from semitone intervals.
|
- <ic>semitones(number[], ...args?)</ic>: Create scale from semitone intervals.
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ ${makeExample(
|
|||||||
.semitones(1, 1, 3, 1, 1, 2, 3).out()
|
.semitones(1, 1, 3, 1, 1, 2, 3).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>cents(number[], ...args?)</ic>: Create scale from cent intervals.
|
- <ic>cents(number[], ...args?)</ic>: Create scale from cent intervals.
|
||||||
|
|
||||||
@ -184,11 +184,11 @@ ${makeExample(
|
|||||||
"Play pitches from scale created from cent intervals",
|
"Play pitches from scale created from cent intervals",
|
||||||
`
|
`
|
||||||
rhythm([0.5,0.25].beat(1),14,16) :: sound('pluck')
|
rhythm([0.5,0.25].beat(1),14,16) :: sound('pluck')
|
||||||
.stretch(iR(1,5)).pitch(iR(0,6)).key(57)
|
.stretch(ir(1,5)).pitch(ir(0,6)).key(57)
|
||||||
.cents(120,270,540,670,785,950,1215).out()
|
.cents(120,270,540,670,785,950,1215).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>ratios(number[], ...args?)</ic>: Create scale from ratios.
|
- <ic>ratios(number[], ...args?)</ic>: Create scale from ratios.
|
||||||
|
|
||||||
@ -200,7 +200,7 @@ ${makeExample(
|
|||||||
.ratios(2/11,4/11,6/11,8/11,10/11,11/11).out()
|
.ratios(2/11,4/11,6/11,8/11,10/11,11/11).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>edo(number, scale?: string|number[])</ic>: Create scale from equal divisions of the octave. Creates chromatic scale by default.
|
- <ic>edo(number, scale?: string|number[])</ic>: Create scale from equal divisions of the octave. Creates chromatic scale by default.
|
||||||
|
|
||||||
@ -213,13 +213,13 @@ flipbar(1) :: rhythm(.25,14,16) :: sound("ST10:30").stretch(3).gain(0.5)
|
|||||||
.octave(r(-6,6))
|
.octave(r(-6,6))
|
||||||
.edo(666,"rocritonic")
|
.edo(666,"rocritonic")
|
||||||
.out()
|
.out()
|
||||||
rhythm(2.0,26,32) :: sound("ST20").n([22,5,24,34,31,5,11,19].pick()).stretch(rI(1,6))
|
rhythm(2.0,26,32) :: sound("ST20").n([22,5,24,34,31,5,11,19].pick()).stretch(ir(1,6))
|
||||||
.pitch(rI(127,300))
|
.pitch(ir(127,300))
|
||||||
.edo(666)
|
.edo(666)
|
||||||
.out()
|
.out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
- <ic>scale(scale: string, base note: number)</ic>: Map each element of the list to the closest note of the slected scale. [0, 2, 3, 5 ].scale("major", 50) returns [50, 52, <ic>54</ic>, 55]. You can use western scale names like (Major, Minor, Minor pentatonic ...) or [zeitler](https://ianring.com/musictheory/scales/traditions/zeitler) scale names. Alternatively you can also use the integers as used by Ian Ring in his [study of scales](https://ianring.com/musictheory/scales/).
|
- <ic>scale(scale: string, base note: number)</ic>: Map each element of the list to the closest note of the slected scale. [0, 2, 3, 5 ].scale("major", 50) returns [50, 52, <ic>54</ic>, 55]. You can use western scale names like (Major, Minor, Minor pentatonic ...) or [zeitler](https://ianring.com/musictheory/scales/traditions/zeitler) scale names. Alternatively you can also use the integers as used by Ian Ring in his [study of scales](https://ianring.com/musictheory/scales/).
|
||||||
@ -232,7 +232,7 @@ beat(1) :: snd('gtr')
|
|||||||
.out()
|
.out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>scaleArp(scale: string, mask: number)</ic>: extrapolate a custom-masked scale from each list elements. [0].scale("major", 3) returns [0,2,4]. <ic>scaleArp</ic> supports the same scales as <ic>scale</ic>.
|
- <ic>scaleArp(scale: string, mask: number)</ic>: extrapolate a custom-masked scale from each list elements. [0].scale("major", 3) returns [0,2,4]. <ic>scaleArp</ic> supports the same scales as <ic>scale</ic>.
|
||||||
|
|
||||||
@ -244,7 +244,7 @@ beat(1) :: snd('gtr')
|
|||||||
.out()
|
.out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Iteration using the mouse
|
## Iteration using the mouse
|
||||||
|
|
||||||
@ -260,7 +260,7 @@ beat(0.25)::sound('wt_piano')
|
|||||||
.ad(0, .2).out()
|
.ad(0, .2).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Simple data operations
|
## Simple data operations
|
||||||
|
|
||||||
@ -278,7 +278,7 @@ beat([1,.5,.25].beat()) :: snd('wt_stereo')
|
|||||||
.lpad(4, 0, .25).sustain(0.125).out()
|
.lpad(4, 0, .25).sustain(0.125).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>random(index: number)</ic>: pick a random element in the given list.
|
- <ic>random(index: number)</ic>: pick a random element in the given list.
|
||||||
- <ic>rand(index: number)</ic>: shorter alias for the same method.
|
- <ic>rand(index: number)</ic>: shorter alias for the same method.
|
||||||
@ -297,7 +297,7 @@ beat([.5, 1].rand() / 2) :: snd(
|
|||||||
.end(0.5).out()
|
.end(0.5).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>pick()</ic>: pick a random element in the list.
|
- <ic>pick()</ic>: pick a random element in the list.
|
||||||
|
|
||||||
@ -309,7 +309,7 @@ beat(0.25)::sound(['ftabla', 'fwood'].pick())
|
|||||||
.room(0.5).size(1).out()
|
.room(0.5).size(1).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>degrade(amount: number)</ic>: removes _n_% of the list elements. Lists can be degraded as long as one element remains. The amount of degradation is given as a percentage.
|
- <ic>degrade(amount: number)</ic>: removes _n_% of the list elements. Lists can be degraded as long as one element remains. The amount of degradation is given as a percentage.
|
||||||
|
|
||||||
@ -320,7 +320,7 @@ ${makeExample(
|
|||||||
beat(.25)::snd('amencutup').n([1,2,3,4,5,6,7,8,9].degrade(20).loop($(1))).out()
|
beat(.25)::snd('amencutup').n([1,2,3,4,5,6,7,8,9].degrade(20).loop($(1))).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>repeat(amount: number)</ic>: repeat every list elements _n_ times.
|
- <ic>repeat(amount: number)</ic>: repeat every list elements _n_ times.
|
||||||
- <ic>repeatEven(amount: number)</ic>: repeat every pair element of the list _n_ times.
|
- <ic>repeatEven(amount: number)</ic>: repeat every pair element of the list _n_ times.
|
||||||
@ -332,7 +332,7 @@ ${makeExample(
|
|||||||
beat(.25)::sound('amencutup').n([1,2,3,4,5,6,7,8].repeat(4).beat(.25)).out()
|
beat(.25)::sound('amencutup').n([1,2,3,4,5,6,7,8].repeat(4).beat(.25)).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>loop(index: number)</ic>: loop takes one argument, the _index_. It allows you to iterate over a list using an iterator such as a counter. This is super useful to control how you are accessing values in a list without relying on a temporal method such as <ic>.beat()</ic> or </ic>.bar()</ic>.
|
- <ic>loop(index: number)</ic>: loop takes one argument, the _index_. It allows you to iterate over a list using an iterator such as a counter. This is super useful to control how you are accessing values in a list without relying on a temporal method such as <ic>.beat()</ic> or </ic>.bar()</ic>.
|
||||||
|
|
||||||
@ -342,7 +342,7 @@ ${makeExample(
|
|||||||
beat(1) :: sound('numbers').n([1,2,3,4,5].loop($(3, 10, 2))).out()
|
beat(1) :: sound('numbers').n([1,2,3,4,5].loop($(3, 10, 2))).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>shuffle(): this</ic>: shuffles a list! Simple enough!
|
- <ic>shuffle(): this</ic>: shuffles a list! Simple enough!
|
||||||
|
|
||||||
@ -352,7 +352,7 @@ ${makeExample(
|
|||||||
beat(1) :: sound('numbers').n([1,2,3,4,5].shuffle().loop($(1)).out()
|
beat(1) :: sound('numbers').n([1,2,3,4,5].shuffle().loop($(1)).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>rotate(steps: number)</ic>: rotate a list to the right _n_ times. The last value become the first, rinse and repeat.
|
- <ic>rotate(steps: number)</ic>: rotate a list to the right _n_ times. The last value become the first, rinse and repeat.
|
||||||
|
|
||||||
@ -370,7 +370,7 @@ beat(.25) :: snd('sine').fmi([1.99, 2])
|
|||||||
.out()
|
.out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Filtering
|
## Filtering
|
||||||
|
|
||||||
@ -383,7 +383,7 @@ ${makeExample(
|
|||||||
beat(1)::snd('sine').sustain(0.1).freq([100,100,100,100,200].unique().beat()).out()
|
beat(1)::snd('sine').sustain(0.1).freq([100,100,100,100,200].unique().beat()).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Simple math operations
|
## Simple math operations
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
export const probabilities = (application: Editor): string => {
|
export const probabilities = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -18,7 +18,7 @@ ${makeExample(
|
|||||||
rhythm(0.125, 10, 16) :: sound('sid').n(4).note(50 + irand(50, 62) % 8).out()
|
rhythm(0.125, 10, 16) :: sound('sid').n(4).note(50 + irand(50, 62) % 8).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
- <ic>prob(p: number)</ic>: return <ic>true</ic> _p_% of time, <ic>false</ic> in other cases.
|
- <ic>prob(p: number)</ic>: return <ic>true</ic> _p_% of time, <ic>false</ic> in other cases.
|
||||||
@ -33,7 +33,7 @@ prob(60) :: script(2);
|
|||||||
prob(80) :: script(toss() ? script(3) : script(4))
|
prob(80) :: script(toss() ? script(3) : script(4))
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>seed(val: number|string)</ic>: sets the seed of the random number generator. You can use a number or a string. The same seed will always return the same sequence of random numbers.
|
- <ic>seed(val: number|string)</ic>: sets the seed of the random number generator. You can use a number or a string. The same seed will always return the same sequence of random numbers.
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ ${makeExample(
|
|||||||
rarely(8) :: sound('east').out(); // Rarely in 8 beats is even less
|
rarely(8) :: sound('east').out(); // Rarely in 8 beats is even less
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Using chance with other operators",
|
"Using chance with other operators",
|
||||||
@ -76,7 +76,7 @@ ${makeExample(
|
|||||||
sometimes() :: onbeat(1,3) :: sound('snare').out();
|
sometimes() :: onbeat(1,3) :: sound('snare').out();
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Using chance with chaining",
|
"Using chance with chaining",
|
||||||
@ -93,6 +93,6 @@ ${makeExample(
|
|||||||
.out()
|
.out()
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
export const variables = (application: Editor): string => {
|
export const variables = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -18,7 +18,7 @@ ${makeExample(
|
|||||||
global.my_variable = 2
|
global.my_variable = 2
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Getting that variable back and printing!",
|
"Getting that variable back and printing!",
|
||||||
@ -27,7 +27,7 @@ ${makeExample(
|
|||||||
log(global.my_variable)
|
log(global.my_variable)
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
Now your scripts can share information with each other!
|
Now your scripts can share information with each other!
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ ${makeExample(
|
|||||||
rhythm(.25, 6, 8) :: sound('dr').n($(1)).end(.25).out()
|
rhythm(.25, 6, 8) :: sound('dr').n($(1)).end(.25).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Using a more complex counter",
|
"Using a more complex counter",
|
||||||
@ -61,7 +61,7 @@ ${makeExample(
|
|||||||
rhythm(.25, 6, 8) :: sound('dr').n($(1, 20, 5)).end(.25).out()
|
rhythm(.25, 6, 8) :: sound('dr').n($(1, 20, 5)).end(.25).out()
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Calling the drunk mechanism",
|
"Calling the drunk mechanism",
|
||||||
@ -70,7 +70,7 @@ ${makeExample(
|
|||||||
rhythm(.25, 6, 8) :: sound('dr').n(drunk()).end(.25).out()
|
rhythm(.25, 6, 8) :: sound('dr').n(drunk()).end(.25).out()
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
6
src/Docs/patterns/ziffers/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export { ziffers_basics } from './ziffers_basics';
|
||||||
|
export { ziffers_scales } from './ziffers_scales';
|
||||||
|
export { ziffers_rhythm } from './ziffers_rhythm';
|
||||||
|
export { ziffers_algorithmic } from './ziffers_algorithmic';
|
||||||
|
export { ziffers_tonnetz } from './ziffers_tonnetz';
|
||||||
|
export { ziffers_syncing } from './ziffers_syncing';
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const ziffers_algorithmic = (application: Editor): string => {
|
export const ziffers_algorithmic = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const ziffers_basics = (application: Editor): string => {
|
export const ziffers_basics = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -30,7 +30,7 @@ z3('can can:2').sound().gain(1).cutoff(osci).out()
|
|||||||
z4('1/4 kick kick snare kick').sound().gain(1).cutoff(osci).out()
|
z4('1/4 kick kick snare kick').sound().gain(1).cutoff(osci).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Evaluation
|
## Evaluation
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ ${makeExample(
|
|||||||
z1('0.25 0 1 2 3 4 5 6 7 8 9').sound('wt_stereo')
|
z1('0.25 0 1 2 3 4 5 6 7 8 9').sound('wt_stereo')
|
||||||
.adsr(0, .1, 0, 0).out()`,
|
.adsr(0, .1, 0, 0).out()`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Escaped pitches using curly brackets",
|
"Escaped pitches using curly brackets",
|
||||||
@ -72,7 +72,7 @@ ${makeExample(
|
|||||||
.cutoff(usaw(1/2) * 4000)
|
.cutoff(usaw(1/2) * 4000)
|
||||||
.room(0.9).size(0.9).out()`,
|
.room(0.9).size(0.9).out()`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Durations using fractions and floating point numbers",
|
"Durations using fractions and floating point numbers",
|
||||||
@ -82,7 +82,7 @@ z1('1/8 0 2 4 0 2 4 1/4 0 3 5 0.25 _ 0 7 0 7')
|
|||||||
.adsr(0, .1, 0, 0).delayfb(0.45).out()
|
.adsr(0, .1, 0, 0).delayfb(0.45).out()
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Disco was invented thanks to Ziffers",
|
"Disco was invented thanks to Ziffers",
|
||||||
@ -92,7 +92,7 @@ beat(1)::snd('bd').out(); beat(2)::snd('sd').out()
|
|||||||
beat(3) :: snd('cp').room(0.5).size(0.5).orbit(2).out()
|
beat(3) :: snd('cp').room(0.5).size(0.5).orbit(2).out()
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Accidentals and rests for nice melodies",
|
"Accidentals and rests for nice melodies",
|
||||||
@ -104,7 +104,7 @@ z1('^ 1/8 0 1 b2 3 4 _ 4 b5 4 3 b2 1 0')
|
|||||||
.adsr(0, .1, 0, 0).out()
|
.adsr(0, .1, 0, 0).out()
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Repeat items n-times",
|
"Repeat items n-times",
|
||||||
@ -117,7 +117,7 @@ z2('1/8 _ 0!4 5!4 4!2 7!2')
|
|||||||
.shape(0.2).sustain(0.1).out()
|
.shape(0.2).sustain(0.1).out()
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Subdivided durations",
|
"Subdivided durations",
|
||||||
@ -127,7 +127,7 @@ z1('w [0 [5 [3 7]]] h [0 4]')
|
|||||||
.fmi(usine(.5)).fmh(2).out()
|
.fmi(usine(.5)).fmh(2).out()
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Rests
|
## Rests
|
||||||
|
|
||||||
@ -138,7 +138,7 @@ z1('q 0 ^ e0 r _ 0 _ r 4 ^4 4')
|
|||||||
.sound('sine').scale("godian").out()
|
.sound('sine').scale("godian").out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Rests with durations",
|
"Rests with durations",
|
||||||
@ -147,7 +147,7 @@ ${makeExample(
|
|||||||
.sound('sine').scale("aeryptian").out()
|
.sound('sine').scale("aeryptian").out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Chords
|
## Chords
|
||||||
|
|
||||||
@ -232,7 +232,7 @@ ${makeExample(
|
|||||||
z1("(i v vi%-3 iv%-2)@(s 0 2 0 1 2 1 0 2)")
|
z1("(i v vi%-3 iv%-2)@(s 0 2 0 1 2 1 0 2)")
|
||||||
.sound("sine").out()
|
.sound("sine").out()
|
||||||
`,
|
`,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Arpeggio from named chords with durations",
|
"Arpeggio from named chords with durations",
|
||||||
@ -241,7 +241,7 @@ z1("_ Gm7 ^ C9 D7 Gm7")
|
|||||||
.arpeggio("e 0 2 q 3 e 1 2")
|
.arpeggio("e 0 2 q 3 e 1 2")
|
||||||
.sound("sine").out()
|
.sound("sine").out()
|
||||||
`,
|
`,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Arpeggio from roman chords with inversions",
|
"Arpeggio from roman chords with inversions",
|
||||||
@ -251,7 +251,7 @@ ${makeExample(
|
|||||||
.noteLength(0.125)
|
.noteLength(0.125)
|
||||||
.sound("sine").out()
|
.sound("sine").out()
|
||||||
`,
|
`,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Chaining
|
## Chaining
|
||||||
|
|
||||||
@ -264,7 +264,7 @@ z1('0 1 2 3').key('G3')
|
|||||||
.scale('minor').sound('sine').out()
|
.scale('minor').sound('sine').out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"More complex chaining",
|
"More complex chaining",
|
||||||
@ -272,7 +272,7 @@ ${makeExample(
|
|||||||
z1('0 1 2 3 4').key('G3').scale('minor').sound('sine').often(n => n.pitch+=3).rarely(s => s.delay(0.5)).out()
|
z1('0 1 2 3 4').key('G3').scale('minor').sound('sine').often(n => n.pitch+=3).rarely(s => s.delay(0.5)).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Alternative way for inputting options",
|
"Alternative way for inputting options",
|
||||||
@ -280,7 +280,7 @@ ${makeExample(
|
|||||||
z1('0 3 2 4',{key: 'D3', scale: 'minor pentatonic'}).sound('sine').out()
|
z1('0 3 2 4',{key: 'D3', scale: 'minor pentatonic'}).sound('sine').out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## String prototypes
|
## String prototypes
|
||||||
|
|
||||||
@ -294,7 +294,7 @@ ${makeExample(
|
|||||||
"q 2 7 8 6".z2({key: "C2", scale: "aeolian"}).sound('sine').scale("minor").out()
|
"q 2 7 8 6".z2({key: "C2", scale: "aeolian"}).sound('sine').scale("minor").out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const ziffers_rhythm = (application: Editor): string => {
|
export const ziffers_rhythm = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -14,7 +14,7 @@ ${makeExample(
|
|||||||
z1('q 0 0 4 4 5 5 h4 q 3 3 2 2 1 1 h0').sound('sine').out()
|
z1('q 0 0 4 4 5 5 h4 q 3 3 2 2 1 1 h0').sound('sine').out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Fraction durations",
|
"Fraction durations",
|
||||||
@ -23,7 +23,7 @@ ${makeExample(
|
|||||||
.sound('sine').out()
|
.sound('sine').out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Decimal durations",
|
"Decimal durations",
|
||||||
@ -32,7 +32,7 @@ z1('0.25 5 1 2 6 0.125 3 8 0.5 4 1.0 0')
|
|||||||
.sound('sine').scale("galian").out()
|
.sound('sine').scale("galian").out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## List of all duration characters
|
## List of all duration characters
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ ${makeExample(
|
|||||||
z1('bd [hh hh]').octave(-2).sound('sine').out()
|
z1('bd [hh hh]').octave(-2).sound('sine').out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"More complex pattern",
|
"More complex pattern",
|
||||||
@ -103,7 +103,7 @@ ${makeExample(
|
|||||||
z1('bd [hh <hh <cp cp:2>>]').octave(-2).sound('sine').out()
|
z1('bd [hh <hh <cp cp:2>>]').octave(-2).sound('sine').out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Pitched samples",
|
"Pitched samples",
|
||||||
@ -113,7 +113,7 @@ ${makeExample(
|
|||||||
.adsr(0.25,0.125,0.125,0.25).out()
|
.adsr(0.25,0.125,0.125,0.25).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Pitched samples from list operation",
|
"Pitched samples from list operation",
|
||||||
@ -124,7 +124,7 @@ ${makeExample(
|
|||||||
.sound().out()
|
.sound().out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Pitched samples with list notation",
|
"Pitched samples with list notation",
|
||||||
@ -134,7 +134,7 @@ ${makeExample(
|
|||||||
.adsr(0.25,0.125,0.125,0.25).out()
|
.adsr(0.25,0.125,0.125,0.25).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Sample indices",
|
"Sample indices",
|
||||||
@ -143,7 +143,7 @@ ${makeExample(
|
|||||||
.octave(-1).sound("east").out()
|
.octave(-1).sound("east").out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Pitched samples with sample indices",
|
"Pitched samples with sample indices",
|
||||||
@ -151,7 +151,7 @@ ${makeExample(
|
|||||||
z1('_e 1@east:2 4@bd:3 6@arp:2 9@baa').sound().out()
|
z1('_e 1@east:2 4@bd:3 6@arp:2 9@baa').sound().out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const ziffers_scales = (application: Editor): string => {
|
export const ziffers_scales = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -95,7 +95,7 @@ ${makeExample(
|
|||||||
onbeat(1,1.5,3) :: sound('bd').cutoff(100 + usine(.25) * 1000).out()
|
onbeat(1,1.5,3) :: sound('bd').cutoff(100 + usine(.25) * 1000).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const ziffers_syncing = (application: Editor): string => {
|
export const ziffers_syncing = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -13,13 +13,13 @@ Ziffers patterns can be synced to any event by using **cue**, **sync**, **wait**
|
|||||||
The <ic>cue(name: string)</ic> methods can be used to send cue messages for ziffers patterns. The <ic>wait(name: string)</ic> method is used to wait for the cue message to be received before starting the next cycle.
|
The <ic>cue(name: string)</ic> methods can be used to send cue messages for ziffers patterns. The <ic>wait(name: string)</ic> method is used to wait for the cue message to be received before starting the next cycle.
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Sending cue from event and wait",
|
"Sending cue from event and wait",
|
||||||
`
|
`
|
||||||
beat(4.0) :: sound("bd").cue("foo").out()
|
beat(4.0) :: sound("bd").cue("foo").out()
|
||||||
z1("e 0 3 2 1 2 1").wait("foo").sound("sine").out()
|
z1("e 0 3 2 1 2 1").wait("foo").sound("sine").out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
The <ic>sync(name: string)</ic> method is used to sync the ziffers pattern to the cue message.
|
The <ic>sync(name: string)</ic> method is used to sync the ziffers pattern to the cue message.
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ beat([2.0,0.5,1.5].bar(1)) ::
|
|||||||
.dur(0.5).out()
|
.dur(0.5).out()
|
||||||
`,
|
`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Automatic sync for ziffers patterns
|
## Automatic sync for ziffers patterns
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const ziffers_tonnetz = (application: Editor): string => {
|
export const ziffers_tonnetz = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -208,15 +208,18 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
z1("1.0 047{10}")
|
z1("1.0 047{10}")
|
||||||
.scale('chromatic')
|
.scale('chromatic')
|
||||||
.tetraTonnetz("o p18 q15 l13 n51 p19 q15")
|
.tetraTonnetz("o p18 q15 p14p47r76 l13 n51 x p19rr39 r12")
|
||||||
.sound("sawtooth")
|
.sound("sawtooth")
|
||||||
.cutoff(500 + usine(1/8) * 2000)
|
.cutoff(500 + usine(1/8) * 2000)
|
||||||
.adsr(.5,0.05,0.25,0.5)
|
.adsr(.5,0.05,0.25,0.5)
|
||||||
.dur(2.0)
|
.dur(2.0)
|
||||||
|
.log("pitch")
|
||||||
.out()`,
|
.out()`,
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
It is quite convenient to observe the resulting chords using <ic>log("pitch")</ic> when you have many operations. As with functions for triads, the way to **compose functions** is to write them **without spaces**. Note that text strings that are not functions operate like the **identity transformation**. Only enabled functions will alter the final result.
|
||||||
|
|
||||||
## Cyclic methods
|
## Cyclic methods
|
||||||
|
|
||||||
In addition to the traditional tonnetz transformations, Ziffers implements cyclic methods that can be used to cycle through the tonnetz space. Cyclic methods turns individual pitch classes to chords using the tonnetz. The cyclic methods are:
|
In addition to the traditional tonnetz transformations, Ziffers implements cyclic methods that can be used to cycle through the tonnetz space. Cyclic methods turns individual pitch classes to chords using the tonnetz. The cyclic methods are:
|
||||||
@ -28,11 +28,10 @@ import {
|
|||||||
} from "@codemirror/autocomplete";
|
} from "@codemirror/autocomplete";
|
||||||
import { lintKeymap } from "@codemirror/lint";
|
import { lintKeymap } from "@codemirror/lint";
|
||||||
import { Compartment } from "@codemirror/state";
|
import { Compartment } from "@codemirror/state";
|
||||||
import { Editor } from "./main";
|
import { Editor } from "../main";
|
||||||
import { EditorView } from "codemirror";
|
import { EditorView } from "codemirror";
|
||||||
import { javascript } from "@codemirror/lang-javascript";
|
import { javascript } from "@codemirror/lang-javascript";
|
||||||
import { inlineHoveringTips } from "./documentation/inlineHelp";
|
import { inlineHoveringTips, toposCompletions, soundCompletions } from "../Docs/inlineHelp";
|
||||||
import { toposCompletions, soundCompletions } from "./documentation/inlineHelp";
|
|
||||||
import { javascriptLanguage } from "@codemirror/lang-javascript";
|
import { javascriptLanguage } from "@codemirror/lang-javascript";
|
||||||
|
|
||||||
export const getCodeMirrorTheme = (theme: { [key: string]: string }): Extension => {
|
export const getCodeMirrorTheme = (theme: { [key: string]: string }): Extension => {
|
||||||
@ -66,29 +65,29 @@ export const getCodeMirrorTheme = (theme: { [key: string]: string }): Extension
|
|||||||
selection_background = theme["selection_background"];
|
selection_background = theme["selection_background"];
|
||||||
const toposTheme = EditorView.theme({
|
const toposTheme = EditorView.theme({
|
||||||
"&": {
|
"&": {
|
||||||
color: background,
|
color: background || "",
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
fontSize: "24px",
|
fontSize: "24px",
|
||||||
fontFamily: "IBM Plex Mono",
|
fontFamily: "IBM Plex Mono",
|
||||||
},
|
},
|
||||||
".cm-content": {
|
".cm-content": {
|
||||||
caretColor: cursor,
|
caretColor: cursor || '',
|
||||||
fontFamily: "IBM Plex Mono",
|
fontFamily: "IBM Plex Mono",
|
||||||
},
|
},
|
||||||
".cm-line": {
|
".cm-line": {
|
||||||
color: `${brightwhite}`,
|
color: `${brightwhite}`,
|
||||||
},
|
},
|
||||||
".cm-cursor, .cm-dropCursor": {
|
".cm-cursor, .cm-dropCursor": {
|
||||||
borderLeftColor: cursor,
|
borderLeftColor: cursor || 'white',
|
||||||
},
|
},
|
||||||
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":
|
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":
|
||||||
{
|
{
|
||||||
backgroundColor: brightwhite,
|
backgroundColor: brightwhite || 'black',
|
||||||
border: `1px solid ${brightwhite}`,
|
border: `1px solid ${brightwhite}`,
|
||||||
},
|
},
|
||||||
".cm-panels": {
|
".cm-panels": {
|
||||||
backgroundColor: selection_background,
|
backgroundColor: selection_background || 'gray',
|
||||||
color: red,
|
color: red || '',
|
||||||
},
|
},
|
||||||
".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
|
".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
|
||||||
".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" },
|
".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" },
|
||||||
@ -97,30 +96,30 @@ export const getCodeMirrorTheme = (theme: { [key: string]: string }): Extension
|
|||||||
outline: `1px solid ${magenta}`,
|
outline: `1px solid ${magenta}`,
|
||||||
},
|
},
|
||||||
".cm-searchMatch.cm-searchMatch-selected": {
|
".cm-searchMatch.cm-searchMatch-selected": {
|
||||||
backgroundColor: red,
|
backgroundColor: red || '',
|
||||||
},
|
},
|
||||||
".cm-activeLine": {
|
".cm-activeLine": {
|
||||||
backgroundColor: `rgba(${(parseInt(selection_background.slice(1, 3), 16))}, ${(parseInt(selection_background.slice(3, 5), 16))}, ${(parseInt(selection_background.slice(5, 7), 16))}, 0.25)`,
|
backgroundColor: `rgba(${(parseInt(selection_background!.slice(1, 3), 16))}, ${(parseInt(selection_background!.slice(3, 5), 16))}, ${(parseInt(selection_background!.slice(5, 7), 16))}, 0.25)`,
|
||||||
},
|
},
|
||||||
".cm-selectionMatch": {
|
".cm-selectionMatch": {
|
||||||
backgroundColor: `rgba(${(parseInt(selection_background.slice(1, 3), 16))}, ${(parseInt(selection_background.slice(3, 5), 16))}, ${(parseInt(selection_background.slice(5, 7), 16))}, 0.25)`,
|
backgroundColor: `rgba(${(parseInt(selection_background!.slice(1, 3), 16))}, ${(parseInt(selection_background!.slice(3, 5), 16))}, ${(parseInt(selection_background!.slice(5, 7), 16))}, 0.25)`,
|
||||||
outline: `1px solid ${brightwhite}`,
|
outline: `1px solid ${brightwhite}`,
|
||||||
},
|
},
|
||||||
"&.cm-focused .cm-matchingBracket": {
|
"&.cm-focused .cm-matchingBracket": {
|
||||||
color: `rgba(${(parseInt(selection_background.slice(1, 3), 16))}, ${(parseInt(selection_background.slice(3, 5), 16))}, ${(parseInt(selection_background.slice(5, 7), 16))}, 0.25)`,
|
color: `rgba(${(parseInt(selection_background!.slice(1, 3), 16))}, ${(parseInt(selection_background!.slice(3, 5), 16))}, ${(parseInt(selection_background!.slice(5, 7), 16))}, 0.25)`,
|
||||||
},
|
},
|
||||||
"&.cm-focused .cm-nonmatchingBracket": {
|
"&.cm-focused .cm-nonmatchingBracket": {
|
||||||
color: yellow,
|
color: yellow || '',
|
||||||
},
|
},
|
||||||
|
|
||||||
".cm-gutters": {
|
".cm-gutters": {
|
||||||
//backgroundColor: base00,
|
//backgroundColor: base00,
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
color: foreground,
|
color: foreground || '',
|
||||||
},
|
},
|
||||||
".cm-activeLineGutter": {
|
".cm-activeLineGutter": {
|
||||||
backgroundColor: selection_background,
|
backgroundColor: selection_background || '',
|
||||||
color: selection_foreground,
|
color: selection_foreground || '',
|
||||||
},
|
},
|
||||||
|
|
||||||
".cm-foldPlaceholder": {
|
".cm-foldPlaceholder": {
|
||||||
@ -129,17 +128,17 @@ export const getCodeMirrorTheme = (theme: { [key: string]: string }): Extension
|
|||||||
},
|
},
|
||||||
".cm-tooltip": {
|
".cm-tooltip": {
|
||||||
border: "none",
|
border: "none",
|
||||||
backgroundColor: background,
|
backgroundColor: background || '',
|
||||||
},
|
},
|
||||||
".cm-tooltip .cm-tooltip-arrow:before": {},
|
".cm-tooltip .cm-tooltip-arrow:before": {},
|
||||||
".cm-tooltip .cm-tooltip-arrow:after": {
|
".cm-tooltip .cm-tooltip-arrow:after": {
|
||||||
borderTopColor: background,
|
borderTopColor: background || '',
|
||||||
borderBottomColor: background,
|
borderBottomColor: background || '',
|
||||||
},
|
},
|
||||||
".cm-tooltip-autocomplete": {
|
".cm-tooltip-autocomplete": {
|
||||||
"& > ul > li[aria-selected]": {
|
"& > ul > li[aria-selected]": {
|
||||||
backgroundColor: background,
|
backgroundColor: background || '',
|
||||||
color: brightwhite,
|
color: brightwhite || '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -321,7 +320,7 @@ export const installEditor = (app: Editor) => {
|
|||||||
),
|
),
|
||||||
editorSetup,
|
editorSetup,
|
||||||
app.themeCompartment.of(
|
app.themeCompartment.of(
|
||||||
getCodeMirrorTheme(app.getColorScheme("Tomorrow Night Burns")),
|
getCodeMirrorTheme(app.getColorScheme("Batman")),
|
||||||
// debug
|
// debug
|
||||||
),
|
),
|
||||||
app.chosenLanguage.of(javascript()),
|
app.chosenLanguage.of(javascript()),
|
||||||
@ -344,7 +343,7 @@ export const installEditor = (app: Editor) => {
|
|||||||
),
|
),
|
||||||
keymap.of([indentWithTab]),
|
keymap.of([indentWithTab]),
|
||||||
],
|
],
|
||||||
doc: app.universes[app.selected_universe].global.candidate,
|
doc: app.universes[app.selected_universe]!.global.candidate,
|
||||||
});
|
});
|
||||||
app.view = new EditorView({
|
app.view = new EditorView({
|
||||||
parent: document.getElementById("editor") as HTMLElement,
|
parent: document.getElementById("editor") as HTMLElement,
|
||||||
@ -1,9 +1,7 @@
|
|||||||
// import { tutorial_universe } from "./universes/tutorial";
|
|
||||||
import { gzipSync, decompressSync, strFromU8 } from "fflate";
|
import { gzipSync, decompressSync, strFromU8 } from "fflate";
|
||||||
// import { examples } from "./examples/excerpts";
|
import { type Editor } from "../main";
|
||||||
import { type Editor } from "./main";
|
|
||||||
import { uniqueNamesGenerator, colors, animals } from "unique-names-generator";
|
import { uniqueNamesGenerator, colors, animals } from "unique-names-generator";
|
||||||
import { tryEvaluate } from "./Evaluator";
|
import { tryEvaluate } from "../Evaluator";
|
||||||
export type Universes = { [key: string]: Universe };
|
export type Universes = { [key: string]: Universe };
|
||||||
|
|
||||||
export interface Universe {
|
export interface Universe {
|
||||||
@ -181,7 +179,7 @@ export class AppSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get_universe() {
|
get_universe() {
|
||||||
this.universes.universe_name;
|
this.universes["universe_name"];
|
||||||
}
|
}
|
||||||
|
|
||||||
get data(): Settings {
|
get data(): Settings {
|
||||||
@ -358,7 +356,7 @@ export const loadUniverse = (
|
|||||||
// Updating the editor View to reflect the selected universe
|
// Updating the editor View to reflect the selected universe
|
||||||
app.updateEditorView();
|
app.updateEditorView();
|
||||||
// Evaluating the initialisation script for the selected universe
|
// Evaluating the initialisation script for the selected universe
|
||||||
tryEvaluate(app, app.universes[app.selected_universe.toString()].init);
|
tryEvaluate(app, app.universes[app.selected_universe.toString()]!.init);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const openUniverseModal = (): void => {
|
export const openUniverseModal = (): void => {
|
||||||
135
src/Evaluator.ts
@ -1,131 +1,32 @@
|
|||||||
import type { Editor } from "./main";
|
import type { Editor } from "./main";
|
||||||
import type { File } from "./FileManagement";
|
import type { File } from "./Editor/FileManagement";
|
||||||
|
|
||||||
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
const codeReplace = (code: string): string => {
|
|
||||||
return code.replace(/->|::/g, "&&");
|
|
||||||
};
|
|
||||||
|
|
||||||
const tryCatchWrapper = async (
|
|
||||||
application: Editor,
|
|
||||||
code: string,
|
|
||||||
): Promise<boolean> => {
|
|
||||||
/**
|
|
||||||
* Wraps the provided code in a try-catch block and executes it.
|
|
||||||
*
|
|
||||||
* @param application - The editor application.
|
|
||||||
* @param code - The code to be executed.
|
|
||||||
* @returns A promise that resolves to a boolean indicating whether the code executed successfully or not.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
async function tryCatchWrapper(application: Editor, code: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await new Function(`"use strict"; ${codeReplace(code)}`).call(
|
await new Function(`"use strict"; ${code}`).call(application.api);
|
||||||
application.api,
|
|
||||||
);
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
application.interface.error_line.innerHTML = error as string;
|
console.error(error);
|
||||||
application.api._reportError(error as string);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const cache = new Map<string, Function>();
|
|
||||||
const MAX_CACHE_SIZE = 40;
|
|
||||||
|
|
||||||
const addFunctionToCache = (code: string, fn: Function) => {
|
|
||||||
/**
|
|
||||||
* Adds a function to the cache.
|
|
||||||
* @param code - The code associated with the function.
|
|
||||||
* @param fn - The function to be added to the cache.
|
|
||||||
*/
|
|
||||||
if (cache.size >= MAX_CACHE_SIZE) {
|
|
||||||
cache.delete(cache.keys().next().value);
|
|
||||||
}
|
|
||||||
cache.set(code, fn);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const tryEvaluate = async (
|
|
||||||
application: Editor,
|
|
||||||
code: File,
|
|
||||||
timeout = 5000,
|
|
||||||
): Promise<void> => {
|
|
||||||
/**
|
|
||||||
* Tries to evaluate the provided code within a specified timeout period.
|
|
||||||
* Increments the evaluation count of the code file.
|
|
||||||
* If the code is valid, updates the committed code and adds the evaluated function to the cache.
|
|
||||||
* If the code is invalid, retries the evaluation.
|
|
||||||
* @param application - The editor application.
|
|
||||||
* @param code - The code file to evaluate.
|
|
||||||
* @param timeout - The timeout period in milliseconds (default: 5000).
|
|
||||||
* @returns A Promise that resolves when the evaluation is complete.
|
|
||||||
*/
|
|
||||||
code.evaluations!++;
|
|
||||||
const candidateCode = code.candidate;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const cachedFunction = cache.get(candidateCode);
|
|
||||||
if (cachedFunction) {
|
|
||||||
cachedFunction.call(application.api);
|
|
||||||
} else {
|
|
||||||
const wrappedCode = `let i = ${code.evaluations}; ${candidateCode}`;
|
|
||||||
const isCodeValid = await Promise.race([
|
|
||||||
tryCatchWrapper(application, wrappedCode),
|
|
||||||
delay(timeout),
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
export async function tryEvaluate(application: Editor, code: File): Promise<void> {
|
||||||
|
const wrappedCode = `let i = ${code.evaluations}; ${code.candidate}`;
|
||||||
|
const isCodeValid = await tryCatchWrapper(application, wrappedCode);
|
||||||
if (isCodeValid) {
|
if (isCodeValid) {
|
||||||
code.committed = code.candidate;
|
code.committed = code.candidate;
|
||||||
const newFunction = new Function(
|
|
||||||
`"use strict"; ${codeReplace(wrappedCode)}`,
|
|
||||||
);
|
|
||||||
addFunctionToCache(candidateCode, newFunction);
|
|
||||||
} else {
|
} else {
|
||||||
application.api.logOnce("Compilation error!");
|
console.error("Compilation error!");
|
||||||
await evaluate(application, code, timeout);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
application.interface.error_line.innerHTML = error as string;
|
|
||||||
application.api._reportError(error as string);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const evaluate = async (
|
export async function evaluateOnce(application: Editor, code: File): Promise<void> {
|
||||||
application: Editor,
|
const wrappedCode = `let i = ${code.evaluations}; ${code.candidate}`;
|
||||||
code: File,
|
const isCodeValid = await tryCatchWrapper(application, wrappedCode);
|
||||||
timeout = 1000,
|
if (isCodeValid) {
|
||||||
): Promise<void> => {
|
code.committed = code.candidate;
|
||||||
/**
|
} else {
|
||||||
* Evaluates the given code using the provided application and timeout.
|
console.error("Compilation error!");
|
||||||
* @param application The editor application.
|
|
||||||
* @param code The code file to evaluate.
|
|
||||||
* @param timeout The timeout value in milliseconds (default: 1000).
|
|
||||||
* @returns A Promise that resolves when the evaluation is complete.
|
|
||||||
*/
|
|
||||||
|
|
||||||
try {
|
|
||||||
await Promise.race([
|
|
||||||
tryCatchWrapper(application, code.committed as string),
|
|
||||||
delay(timeout),
|
|
||||||
]);
|
|
||||||
if (code.evaluations) code.evaluations++;
|
|
||||||
} catch (error) {
|
|
||||||
application.interface.error_line.innerHTML = error as string;
|
|
||||||
console.log(error);
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const evaluateOnce = async (
|
|
||||||
application: Editor,
|
|
||||||
code: string,
|
|
||||||
): Promise<void> => {
|
|
||||||
/**
|
|
||||||
* Evaluates the code once without any caching or error-handling mechanisms besides the tryCatchWrapper.
|
|
||||||
*
|
|
||||||
* @param application - The application object that contains the Editor API.
|
|
||||||
* @param code - The code to be evaluated.
|
|
||||||
* @returns A promise that resolves when the code has been evaluated.
|
|
||||||
*/
|
|
||||||
await tryCatchWrapper(application, code);
|
|
||||||
};
|
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { UserAPI } from "../API";
|
import { UserAPI } from "../API/API";
|
||||||
import { AppSettings } from "../FileManagement";
|
import { MidiEvent } from "../Classes/MidiEvent";
|
||||||
|
import { AppSettings } from "../Editor/FileManagement";
|
||||||
|
|
||||||
export type MidiNoteEvent = {
|
export type MidiNoteEvent = {
|
||||||
note: number;
|
note: number;
|
||||||
@ -64,7 +65,7 @@ export class MidiConnection {
|
|||||||
constructor(api: UserAPI, settings: AppSettings) {
|
constructor(api: UserAPI, settings: AppSettings) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.lastBPM = api.tempo();
|
this.lastBPM = api.app.clock.bpm;
|
||||||
this.roundedBPM = this.lastBPM;
|
this.roundedBPM = this.lastBPM;
|
||||||
this.initializeMidiAccess();
|
this.initializeMidiAccess();
|
||||||
}
|
}
|
||||||
@ -104,7 +105,13 @@ export class MidiConnection {
|
|||||||
this.currentOutputIndex >= 0 &&
|
this.currentOutputIndex >= 0 &&
|
||||||
this.currentOutputIndex < this.midiOutputs.length
|
this.currentOutputIndex < this.midiOutputs.length
|
||||||
) {
|
) {
|
||||||
return this.midiOutputs[this.currentOutputIndex].name;
|
const output = this.midiOutputs[this.currentOutputIndex];
|
||||||
|
if (output) {
|
||||||
|
return output.name;
|
||||||
|
} else {
|
||||||
|
console.error("MIDI output is undefined.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error("No MIDI output selected or available.");
|
console.error("No MIDI output selected or available.");
|
||||||
return null;
|
return null;
|
||||||
@ -253,7 +260,7 @@ export class MidiConnection {
|
|||||||
this.midiClockInput = this.midiInputs[clockInputIndex];
|
this.midiClockInput = this.midiInputs[clockInputIndex];
|
||||||
this.registerMidiInputListener(clockInputIndex);
|
this.registerMidiInputListener(clockInputIndex);
|
||||||
this.settings.midi_clock_input =
|
this.settings.midi_clock_input =
|
||||||
this.midiClockInput.name || undefined;
|
this.midiClockInput?.name ?? undefined;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -277,7 +284,7 @@ export class MidiConnection {
|
|||||||
this.currentInputIndex = parseInt(value);
|
this.currentInputIndex = parseInt(value);
|
||||||
this.registerMidiInputListener(this.currentInputIndex);
|
this.registerMidiInputListener(this.currentInputIndex);
|
||||||
this.settings.default_midi_input =
|
this.settings.default_midi_input =
|
||||||
this.midiInputs[this.currentInputIndex].name || undefined;
|
this.midiInputs[this.currentInputIndex]?.name || undefined;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -291,36 +298,37 @@ export class MidiConnection {
|
|||||||
const input = this.midiInputs[inputIndex];
|
const input = this.midiInputs[inputIndex];
|
||||||
if (input && !input.onmidimessage) {
|
if (input && !input.onmidimessage) {
|
||||||
input.onmidimessage = (event: Event) => {
|
input.onmidimessage = (event: Event) => {
|
||||||
const message = event as MIDIMessageEvent;
|
// @ts-ignore
|
||||||
|
const message: MidiEvent = event as MIDIMessageEvent;
|
||||||
/* MIDI CLOCK */
|
/* MIDI CLOCK */
|
||||||
if (input.name === this.settings.midi_clock_input) {
|
if (input.name === this.settings.midi_clock_input) {
|
||||||
if (message.data[0] === 0xf8) {
|
if (message['data'][0] === 0xf8) {
|
||||||
if (this.skipOnError > 0) {
|
if (this.skipOnError > 0) {
|
||||||
this.skipOnError -= 1;
|
this.skipOnError -= 1;
|
||||||
} else {
|
} else {
|
||||||
this.onMidiClock(event.timeStamp);
|
this.onMidiClock(event.timeStamp);
|
||||||
}
|
}
|
||||||
} else if (message.data[0] === 0xfa) {
|
} else if (message["data"]![0] === 0xfa) {
|
||||||
console.log("MIDI start received");
|
console.log("MIDI start received");
|
||||||
this.api.stop();
|
this.api.stop();
|
||||||
this.api.play();
|
this.api.play();
|
||||||
} else if (message.data[0] === 0xfc) {
|
} else if (message["data"]![0] === 0xfc) {
|
||||||
console.log("MIDI stop received");
|
console.log("MIDI stop received");
|
||||||
this.api.pause();
|
this.api.pause();
|
||||||
} else if (message.data[0] === 0xfb) {
|
} else if (message["data"]![0] === 0xfb) {
|
||||||
console.log("MIDI continue received");
|
console.log("MIDI continue received");
|
||||||
this.api.play();
|
this.api.play();
|
||||||
} else if (message.data[0] === 0xfe) {
|
} else if (message["data"]![0] === 0xfe) {
|
||||||
console.log("MIDI active sensing received");
|
console.log("MIDI active sensing received");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* DEFAULT MIDI INPUT */
|
/* DEFAULT MIDI INPUT */
|
||||||
if (input.name === this.settings.default_midi_input) {
|
if (input.name === this.settings.default_midi_input) {
|
||||||
// If message is one of note ons
|
// If message is one of note ons
|
||||||
if (message.data[0] >= 0x90 && message.data[0] <= 0x9f) {
|
if (message["data"][0] >= 0x90 && message["data"]![0] <= 0x9f) {
|
||||||
const channel = message.data[0] - 0x90 + 1;
|
const channel = message["data"]![0] - 0x90 + 1;
|
||||||
const note = message.data[1];
|
const note = message["data"]![1];
|
||||||
const velocity = message.data[2];
|
const velocity = message["data"]![2];
|
||||||
|
|
||||||
this.lastNote = {
|
this.lastNote = {
|
||||||
note,
|
note,
|
||||||
@ -361,24 +369,24 @@ export class MidiConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If note off
|
// If note off
|
||||||
if (message.data[0] >= 0x80 && message.data[0] <= 0x8f) {
|
if (message["data"]![0] >= 0x80 && message["data"]![0] <= 0x8f) {
|
||||||
const channel = message.data[0] - 0x80 + 1;
|
const channel = message["data"]![0] - 0x80 + 1;
|
||||||
const note = message.data[1];
|
const note = message["data"]![1];
|
||||||
this.removeFromActiveNotes(note, channel);
|
this.removeFromActiveNotes(note, channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If message is one of CCs
|
// If message is one of CCs
|
||||||
if (message.data[0] >= 0xb0 && message.data[0] <= 0xbf) {
|
if (message["data"]![0] >= 0xb0 && message["data"]![0] <= 0xbf) {
|
||||||
const channel = message.data[0] - 0xb0 + 1;
|
const channel = message["data"]![0] - 0xb0 + 1;
|
||||||
const control = message.data[1];
|
const control = message["data"]![1];
|
||||||
const value = message.data[2];
|
const value = message["data"]![2];
|
||||||
|
|
||||||
this.lastCC[control] = value;
|
this.lastCC[control] = value;
|
||||||
if (this.lastCCInChannel[channel]) {
|
if (this.lastCCInChannel[channel]) {
|
||||||
this.lastCCInChannel[channel][control] = value;
|
this.lastCCInChannel[channel]![control] = value;
|
||||||
} else {
|
} else {
|
||||||
this.lastCCInChannel[channel] = {};
|
this.lastCCInChannel[channel] = {};
|
||||||
this.lastCCInChannel[channel][control] = value;
|
this.lastCCInChannel[channel]![control] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log(`CC: ${control} VALUE: ${value} CHANNEL: ${channel}`);
|
//console.log(`CC: ${control} VALUE: ${value} CHANNEL: ${channel}`);
|
||||||
@ -578,8 +586,7 @@ export class MidiConnection {
|
|||||||
if (typeof output === "number") {
|
if (typeof output === "number") {
|
||||||
if (output < 0 || output >= this.midiOutputs.length) {
|
if (output < 0 || output >= this.midiOutputs.length) {
|
||||||
console.error(
|
console.error(
|
||||||
`Invalid MIDI output index. Index must be in the range 0-${
|
`Invalid MIDI output index. Index must be in the range 0-${this.midiOutputs.length - 1
|
||||||
this.midiOutputs.length - 1
|
|
||||||
}.`,
|
}.`,
|
||||||
);
|
);
|
||||||
return this.currentOutputIndex;
|
return this.currentOutputIndex;
|
||||||
@ -608,8 +615,7 @@ export class MidiConnection {
|
|||||||
if (typeof input === "number") {
|
if (typeof input === "number") {
|
||||||
if (input < 0 || input >= this.midiInputs.length) {
|
if (input < 0 || input >= this.midiInputs.length) {
|
||||||
console.error(
|
console.error(
|
||||||
`Invalid MIDI input index. Index must be in the range 0-${
|
`Invalid MIDI input index. Index must be in the range 0-${this.midiInputs.length - 1
|
||||||
this.midiInputs.length - 1
|
|
||||||
}.`,
|
}.`,
|
||||||
);
|
);
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@ -9,6 +9,17 @@ export interface OSCMessage {
|
|||||||
export let outputSocket = new WebSocket("ws://localhost:3000");
|
export let outputSocket = new WebSocket("ws://localhost:3000");
|
||||||
export let inputSocket = new WebSocket("ws://localhost:3001");
|
export let inputSocket = new WebSocket("ws://localhost:3001");
|
||||||
|
|
||||||
|
|
||||||
|
outputSocket.onerror = (error: Event) => {
|
||||||
|
console.log("[Topos] Failed to connect to OSC daemon:", error.type);
|
||||||
|
console.log("[Topos] Note: the daemon must be started before Topos");
|
||||||
|
};
|
||||||
|
|
||||||
|
inputSocket.onerror = (error: Event) => {
|
||||||
|
console.log("[Topos] Failed to connect to OSC daemon:", error.type);
|
||||||
|
console.log("[Topos] Note: the daemon must be started before Topos");
|
||||||
|
};
|
||||||
|
|
||||||
export let oscMessages: any[] = [];
|
export let oscMessages: any[] = [];
|
||||||
inputSocket.addEventListener("message", (event) => {
|
inputSocket.addEventListener("message", (event) => {
|
||||||
let data = JSON.parse(event.data);
|
let data = JSON.parse(event.data);
|
||||||
@ -19,7 +30,7 @@ inputSocket.addEventListener("message", (event) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
outputSocket.onopen = function (event) {
|
outputSocket.onopen = function(event) {
|
||||||
console.log("Connected to WebSocket Server");
|
console.log("Connected to WebSocket Server");
|
||||||
// Send an OSC-like message
|
// Send an OSC-like message
|
||||||
outputSocket.send(
|
outputSocket.send(
|
||||||
@ -30,11 +41,11 @@ outputSocket.onopen = function (event) {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
outputSocket.onerror = function (error) {
|
outputSocket.onerror = function(error) {
|
||||||
console.log("Websocket Error:", error);
|
console.log("Websocket Error:", error);
|
||||||
};
|
};
|
||||||
|
|
||||||
outputSocket.onmessage = function (event) {
|
outputSocket.onmessage = function(event) {
|
||||||
console.log("Received: ", event.data);
|
console.log("Received: ", event.data);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,7 +8,10 @@
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { registerSound, onTriggerSample } from "superdough";
|
import { registerSound, onTriggerSample } from "superdough";
|
||||||
|
|
||||||
export const isAudioFile = (filename: string) => ['wav', 'mp3'].includes(filename.split('.').slice(-1)[0]);
|
export const isAudioFile = (filename: string) => {
|
||||||
|
const extension = filename.split('.').slice(-1)[0];
|
||||||
|
return extension !== undefined && ['wav', 'mp3'].includes(extension);
|
||||||
|
};
|
||||||
|
|
||||||
interface samplesDBConfig {
|
interface samplesDBConfig {
|
||||||
dbName: string,
|
dbName: string,
|
||||||
|
|||||||
@ -1,65 +0,0 @@
|
|||||||
import { tryEvaluate } from "./Evaluator";
|
|
||||||
const zeroPad = (num, places) => String(num).padStart(places, "0");
|
|
||||||
|
|
||||||
export class TransportNode extends AudioWorkletNode {
|
|
||||||
constructor(context, options, application) {
|
|
||||||
super(context, "transport", options);
|
|
||||||
this.app = application;
|
|
||||||
this.port.addEventListener("message", this.handleMessage);
|
|
||||||
this.port.start();
|
|
||||||
this.timeviewer = document.getElementById("timeviewer");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @type {(this: MessagePort, ev: MessageEvent<any>) => any} */
|
|
||||||
handleMessage = (message) => {
|
|
||||||
if(message.data) {
|
|
||||||
if (message.data.type === "bang") {
|
|
||||||
if(this.app.clock.running) {
|
|
||||||
if (this.app.settings.send_clock) {
|
|
||||||
this.app.api.MidiConnection.sendMidiClock();
|
|
||||||
}
|
|
||||||
const futureTimeStamp = this.app.clock.convertTicksToTimeposition(
|
|
||||||
this.app.clock.tick
|
|
||||||
);
|
|
||||||
this.app.clock.time_position = futureTimeStamp;
|
|
||||||
this.timeviewer.innerHTML = `${zeroPad(futureTimeStamp.bar, 2)}:${futureTimeStamp.beat + 1
|
|
||||||
}:${zeroPad(futureTimeStamp.pulse, 2)} / ${this.app.clock.bpm}`;
|
|
||||||
if (this.app.exampleIsPlaying) {
|
|
||||||
tryEvaluate(this.app, this.app.example_buffer);
|
|
||||||
} else {
|
|
||||||
tryEvaluate(this.app, this.app.global_buffer);
|
|
||||||
}
|
|
||||||
this.app.clock.incrementTick(message.data.bpm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
start() {
|
|
||||||
this.port.postMessage({ type: "start" });
|
|
||||||
}
|
|
||||||
|
|
||||||
pause() {
|
|
||||||
this.port.postMessage({ type: "pause" });
|
|
||||||
}
|
|
||||||
|
|
||||||
resume() {
|
|
||||||
this.port.postMessage({ type: "resume" });
|
|
||||||
}
|
|
||||||
|
|
||||||
setBPM(bpm) {
|
|
||||||
this.port.postMessage({ type: "bpm", value: bpm });
|
|
||||||
}
|
|
||||||
|
|
||||||
setPPQN(ppqn) {
|
|
||||||
this.port.postMessage({ type: "ppqn", value: ppqn });
|
|
||||||
}
|
|
||||||
|
|
||||||
setNudge(nudge) {
|
|
||||||
this.port.postMessage({ type: "nudge", value: nudge });
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
this.port.postMessage({type: "stop" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
class TransportProcessor extends AudioWorkletProcessor {
|
|
||||||
constructor(options) {
|
|
||||||
super(options);
|
|
||||||
this.port.addEventListener("message", this.handleMessage);
|
|
||||||
this.port.start();
|
|
||||||
this.nudge = 0;
|
|
||||||
this.started = false;
|
|
||||||
this.bpm = 120;
|
|
||||||
this.ppqn = 48;
|
|
||||||
this.currentPulsePosition = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMessage = (message) => {
|
|
||||||
if (message.data && message.data.type === "ping") {
|
|
||||||
this.port.postMessage(message.data);
|
|
||||||
} else if (message.data.type === "start") {
|
|
||||||
this.started = true;
|
|
||||||
} else if (message.data.type === "pause") {
|
|
||||||
this.started = false;
|
|
||||||
} else if (message.data.type === "stop") {
|
|
||||||
this.started = false;
|
|
||||||
} else if (message.data.type === "bpm") {
|
|
||||||
this.bpm = message.data.value;
|
|
||||||
this.currentPulsePosition = currentTime;
|
|
||||||
} else if (message.data.type === "ppqn") {
|
|
||||||
this.ppqn = message.data.value;
|
|
||||||
this.currentPulsePosition = currentTime;
|
|
||||||
} else if (message.data.type === "nudge") {
|
|
||||||
this.nudge = message.data.value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
process(inputs, outputs, parameters) {
|
|
||||||
if (this.started) {
|
|
||||||
const adjustedCurrentTime = currentTime + this.nudge / 100;
|
|
||||||
const beatNumber = adjustedCurrentTime / (60 / this.bpm);
|
|
||||||
const currentPulsePosition = Math.ceil(beatNumber * this.ppqn);
|
|
||||||
if (currentPulsePosition > this.currentPulsePosition) {
|
|
||||||
this.currentPulsePosition = currentPulsePosition;
|
|
||||||
this.port.postMessage({ type: "bang", bpm: this.bpm });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
registerProcessor("transport", TransportProcessor);
|
|
||||||
@ -6,8 +6,8 @@ import {
|
|||||||
resolvePitchBend,
|
resolvePitchBend,
|
||||||
safeScale,
|
safeScale,
|
||||||
} from "zifferjs";
|
} from "zifferjs";
|
||||||
import { SkipEvent } from "./SkipEvent";
|
import { SkipEvent } from "../Classes/SkipEvent";
|
||||||
import { SoundParams } from "./SoundEvent";
|
import { SoundParams } from "../Classes/SoundEvent";
|
||||||
import { centsToSemitones, edoToSemitones, ratiosToSemitones } from "zifferjs/src/scale";
|
import { centsToSemitones, edoToSemitones, ratiosToSemitones } from "zifferjs/src/scale";
|
||||||
import { safeMod } from "zifferjs/src/utils";
|
import { safeMod } from "zifferjs/src/utils";
|
||||||
|
|
||||||
@ -211,12 +211,12 @@ export class AbstractEvent {
|
|||||||
* @param func - The function to be applied to the Event
|
* @param func - The function to be applied to the Event
|
||||||
* @returns The transformed Event
|
* @returns The transformed Event
|
||||||
*/
|
*/
|
||||||
return this.modify(func).update();
|
return this.modify(func)["update"]();
|
||||||
};
|
};
|
||||||
|
|
||||||
mod = (value: number): AbstractEvent => {
|
mod = (value: number): AbstractEvent => {
|
||||||
this.values.originalPitch = safeMod(this.values.originalPitch, value);
|
this.values["originalPitch"] = safeMod(this.values["originalPitch"], value);
|
||||||
return this.update();
|
return this["update"]();
|
||||||
}
|
}
|
||||||
|
|
||||||
noteLength = (
|
noteLength = (
|
||||||
@ -230,17 +230,18 @@ export class AbstractEvent {
|
|||||||
value = Array.isArray(value) ? value.concat(kwargs) : [value, ...kwargs];
|
value = Array.isArray(value) ? value.concat(kwargs) : [value, ...kwargs];
|
||||||
}
|
}
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
this.values.dur = value.map((v) =>
|
this.values["dur"] = value.map((v) =>
|
||||||
this.app.clock.convertPulseToSecond(v * 4 * this.app.clock.ppqn),
|
this.app.clock.convertPulseToSecond(v * 4 * this.app.clock.ppqn),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.values.dur = this.app.clock.convertPulseToSecond(
|
this.values["dur"] = this.app.clock.convertPulseToSecond(
|
||||||
value * 4 * this.app.clock.ppqn,
|
value * 4 * this.app.clock.ppqn,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if(this.current) {
|
if(this["current"]) {
|
||||||
value = Array.isArray(value) ? value[this.index%value.length] : value;
|
// @ts-ignore
|
||||||
this.current.duration = value;
|
value = Array.isArray(value) ? value[this["index"]%value.length] : value;
|
||||||
|
this["current"].duration = value;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
@ -253,7 +254,9 @@ export class AbstractEvent {
|
|||||||
const n: number[] = [];
|
const n: number[] = [];
|
||||||
sound.forEach((str) => {
|
sound.forEach((str) => {
|
||||||
const parts = (str as string).split(":");
|
const parts = (str as string).split(":");
|
||||||
|
if (parts[0] !== undefined) {
|
||||||
s.push(parts[0]);
|
s.push(parts[0]);
|
||||||
|
}
|
||||||
if (parts[1]) {
|
if (parts[1]) {
|
||||||
n.push(parseInt(parts[1]));
|
n.push(parseInt(parts[1]));
|
||||||
}
|
}
|
||||||
@ -273,7 +276,7 @@ export class AbstractEvent {
|
|||||||
if (sound.includes(":")) {
|
if (sound.includes(":")) {
|
||||||
const vals = sound.split(":");
|
const vals = sound.split(":");
|
||||||
const s = vals[0];
|
const s = vals[0];
|
||||||
const n = parseInt(vals[1]);
|
const n = parseInt(vals[1] ?? '');
|
||||||
return {
|
return {
|
||||||
s,
|
s,
|
||||||
n,
|
n,
|
||||||
@ -319,9 +322,9 @@ export abstract class AudibleEvent extends AbstractEvent {
|
|||||||
}
|
}
|
||||||
this.values["paramOctave"] = value;
|
this.values["paramOctave"] = value;
|
||||||
if (
|
if (
|
||||||
this.values.key &&
|
this.values['key'] &&
|
||||||
(this.values.pitch || this.values.pitch === 0) &&
|
(this.values['pitch'] || this.values['pitch'] === 0) &&
|
||||||
this.values.parsedScale
|
this.values['parsedScale']
|
||||||
) {
|
) {
|
||||||
return this.update();
|
return this.update();
|
||||||
}
|
}
|
||||||
@ -340,8 +343,8 @@ export abstract class AudibleEvent extends AbstractEvent {
|
|||||||
}
|
}
|
||||||
this.values["key"] = value;
|
this.values["key"] = value;
|
||||||
if (
|
if (
|
||||||
(this.values.pitch || this.values.pitch === 0) &&
|
(this.values['pitch'] || this.values['pitch'] === 0) &&
|
||||||
this.values.parsedScale
|
this.values['parsedScale']
|
||||||
) {
|
) {
|
||||||
return this.update();
|
return this.update();
|
||||||
}
|
}
|
||||||
@ -350,9 +353,9 @@ export abstract class AudibleEvent extends AbstractEvent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
defaultPitchKeyScale() {
|
defaultPitchKeyScale() {
|
||||||
if (!this.values.key) this.values.key = 60;
|
if (!this.values["key"]) this.values["key"] = 60;
|
||||||
if (!(this.values.pitch || this.values.pitch === 0)) this.values.pitch = 0;
|
if (!(this.values["pitch"] || this.values["pitch"] === 0)) this.values["pitch"] = 0;
|
||||||
if (!this.values.parsedScale) this.values.parsedScale = safeScale("major");
|
if (!this.values["parsedScale"]) this.values["parsedScale"] = safeScale("major");
|
||||||
}
|
}
|
||||||
|
|
||||||
scale = (
|
scale = (
|
||||||
@ -368,9 +371,9 @@ export abstract class AudibleEvent extends AbstractEvent {
|
|||||||
value = Array.isArray(value) ? value.concat(kwargs) : [value, ...kwargs];
|
value = Array.isArray(value) ? value.concat(kwargs) : [value, ...kwargs];
|
||||||
}
|
}
|
||||||
if (typeof value === "string" || typeof value === "number") {
|
if (typeof value === "string" || typeof value === "number") {
|
||||||
this.values.parsedScale = safeScale(value) as number[];
|
this.values["parsedScale"] = safeScale(value) as number[];
|
||||||
} else if (Array.isArray(value)) {
|
} else if (Array.isArray(value)) {
|
||||||
this.values.parsedScale = value.map((v) => safeScale(v));
|
this.values["parsedScale"] = value.map((v) => safeScale(v));
|
||||||
}
|
}
|
||||||
this.defaultPitchKeyScale();
|
this.defaultPitchKeyScale();
|
||||||
return this.update();
|
return this.update();
|
||||||
@ -378,7 +381,7 @@ export abstract class AudibleEvent extends AbstractEvent {
|
|||||||
|
|
||||||
semitones(values: number|number[], ...rest: number[]) {
|
semitones(values: number|number[], ...rest: number[]) {
|
||||||
const scaleValues = typeof values === "number" ? [values, ...rest] : values;
|
const scaleValues = typeof values === "number" ? [values, ...rest] : values;
|
||||||
this.values.parsedScale = safeScale(scaleValues);
|
this.values["parsedScale"] = safeScale(scaleValues);
|
||||||
this.defaultPitchKeyScale();
|
this.defaultPitchKeyScale();
|
||||||
return this.update();
|
return this.update();
|
||||||
}
|
}
|
||||||
@ -386,20 +389,20 @@ export abstract class AudibleEvent extends AbstractEvent {
|
|||||||
|
|
||||||
cents(values: number|number[], ...rest: number[]) {
|
cents(values: number|number[], ...rest: number[]) {
|
||||||
const scaleValues = typeof values === "number" ? [values, ...rest] : values;
|
const scaleValues = typeof values === "number" ? [values, ...rest] : values;
|
||||||
this.values.parsedScale = safeScale(centsToSemitones(scaleValues));
|
this.values["parsedScale"] = safeScale(centsToSemitones(scaleValues));
|
||||||
this.defaultPitchKeyScale();
|
this.defaultPitchKeyScale();
|
||||||
return this.update();
|
return this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
ratios(values: number|number[], ...rest: number[]) {
|
ratios(values: number|number[], ...rest: number[]) {
|
||||||
const scaleValues = typeof values === "number" ? [values, ...rest] : values;
|
const scaleValues = typeof values === "number" ? [values, ...rest] : values;
|
||||||
this.values.parsedScale = safeScale(ratiosToSemitones(scaleValues));
|
this.values["parsedScale"] = safeScale(ratiosToSemitones(scaleValues));
|
||||||
this.defaultPitchKeyScale();
|
this.defaultPitchKeyScale();
|
||||||
return this.update();
|
return this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
edo(value: number, intervals: string|number[] = new Array(value).fill(1)) {
|
edo(value: number, intervals: string|number[] = new Array(value).fill(1)) {
|
||||||
this.values.parsedScale = edoToSemitones(value, intervals);
|
this.values["parsedScale"] = edoToSemitones(value, intervals);
|
||||||
this.defaultPitchKeyScale();
|
this.defaultPitchKeyScale();
|
||||||
return this.update();
|
return this.update();
|
||||||
}
|
}
|
||||||
@ -436,8 +439,8 @@ export abstract class AudibleEvent extends AbstractEvent {
|
|||||||
|
|
||||||
public invert = (howMany: number = 0) => {
|
public invert = (howMany: number = 0) => {
|
||||||
if(howMany === 0) return this;
|
if(howMany === 0) return this;
|
||||||
if (this.values.note) {
|
if (this.values["note"]) {
|
||||||
let notes = [...this.values.note];
|
let notes = [...this.values["note"]];
|
||||||
notes = howMany < 0 ? [...notes].reverse() : notes;
|
notes = howMany < 0 ? [...notes].reverse() : notes;
|
||||||
for (let i = 0; i < Math.abs(howMany); i++) {
|
for (let i = 0; i < Math.abs(howMany); i++) {
|
||||||
notes[i % notes.length] += howMany <= 0 ? -12 : 12;
|
notes[i % notes.length] += howMany <= 0 ? -12 : 12;
|
||||||
@ -468,11 +471,11 @@ export abstract class AudibleEvent extends AbstractEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public draw = (lambda: Function) => {
|
public draw = (lambda: Function) => {
|
||||||
lambda(this.values, (this.app.interface.drawings as HTMLCanvasElement).getContext("2d"));
|
lambda(this.values, (this.app.interface.feedback as HTMLCanvasElement).getContext("2d"));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public clear = () => {
|
public override clear = () => {
|
||||||
this.app.api.clear();
|
this.app.api.clear();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -499,7 +502,7 @@ export abstract class AudibleEvent extends AbstractEvent {
|
|||||||
this.values["note"].push(midiNote);
|
this.values["note"].push(midiNote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.values.bend.length === 0) delete this.values.bend;
|
if (this.values["bend"].length === 0) delete this.values["bend"];
|
||||||
} else {
|
} else {
|
||||||
const midiNote = freqToMidi(value);
|
const midiNote = freqToMidi(value);
|
||||||
if (midiNote % 1 !== 0) {
|
if (midiNote % 1 !== 0) {
|
||||||
@ -524,8 +527,8 @@ export abstract class AudibleEvent extends AbstractEvent {
|
|||||||
|
|
||||||
runChain = (): this => {
|
runChain = (): this => {
|
||||||
// chainAll is defined using all() in the API
|
// chainAll is defined using all() in the API
|
||||||
if("chainAll" in this && typeof this.chainAll === "function") {
|
if("chainAll" in this && typeof this["chainAll"] === "function") {
|
||||||
this.values = this.chainAll().values;
|
this.values = this["chainAll"]().values;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||