Begin breaking up the API file
This commit is contained in:
2678
src/API.ts
2678
src/API.ts
File diff suppressed because it is too large
Load Diff
652
src/API/API.ts
Normal file
652
src/API/API.ts
Normal file
@ -0,0 +1,652 @@
|
|||||||
|
import * as Transport from './Transport';
|
||||||
|
import * as Mouse from './Mouse';
|
||||||
|
import * as Theme from './Theme';
|
||||||
|
import * as Canvas from './Canvas';
|
||||||
|
import * as Cache from './Cache';
|
||||||
|
import * as Script from './Script';
|
||||||
|
import * as Drunk from './Drunk';
|
||||||
|
import * as Warp from './Warp';
|
||||||
|
import * as Mathematics from './Math';
|
||||||
|
import * as Ziffers from './Ziffers';
|
||||||
|
import * as Filters from './Filters';
|
||||||
|
import * as LFO from './LFO';
|
||||||
|
import * as Probability from './Probabilities';
|
||||||
|
import * as OSC from './OSC';
|
||||||
|
import * as Randomness from './Randomness';
|
||||||
|
import * as Counter from './Counter';
|
||||||
|
import * as Sound from './Sound';
|
||||||
|
import * as Console from './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 "../DOM/Visuals/CanvasVisuals";
|
||||||
|
import { nearScales } from "zifferjs";
|
||||||
|
import { MidiConnection } from "../IO/MidiConnection";
|
||||||
|
import { evaluateOnce } from "../Evaluator";
|
||||||
|
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 { Speaker } from "../Extensions/StringExtensions";
|
||||||
|
import { getScaleNotes } from "zifferjs";
|
||||||
|
import { AbstractEvent, EventOperation } from "../Classes/AbstractEvents";
|
||||||
|
import drums from "../tidal-drum-machines.json";
|
||||||
|
|
||||||
|
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 } = {};
|
||||||
|
private counters: { [key: string]: any } = {};
|
||||||
|
private _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;
|
||||||
|
onMouseMove: (e: MouseEvent) => void;
|
||||||
|
mouseX: () => number;
|
||||||
|
mouseY: () => number;
|
||||||
|
noteX: () => number;
|
||||||
|
noteY: () => number;
|
||||||
|
tempo: (n?: number | undefined) => number;
|
||||||
|
bpb: (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;
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
|
||||||
|
constructor(public app: Editor) {
|
||||||
|
this.MidiConnection = new MidiConnection(this, app.settings);
|
||||||
|
this.global = {};
|
||||||
|
this.g = this.global;
|
||||||
|
this.time = Transport.time(this.app);
|
||||||
|
this.play = Transport.play(this.app);
|
||||||
|
this.pause = Transport.pause(this.app);
|
||||||
|
this.stop = Transport.stop(this.app);
|
||||||
|
this.silence = Transport.silence(this.app);
|
||||||
|
this.tempo = Transport.tempo(this.app);
|
||||||
|
this.bpb = Transport.bpb(this.app);
|
||||||
|
this.ppqn = Transport.ppqn(this.app);
|
||||||
|
this.time_signature = Transport.time_signature(this.app);
|
||||||
|
this.onMouseMove = Mouse.onmousemove(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.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.app);
|
||||||
|
this.resetAllFromCache = Cache.resetAllFromCache(this.app);
|
||||||
|
this.clearPatternCache = Cache.clearPatternCache(this.app);
|
||||||
|
this.removePatternFromCache = Cache.removePatternFromCache(this.app);
|
||||||
|
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.app);
|
||||||
|
this.drunk_max = Drunk.drunk_max(this.app);
|
||||||
|
this.drunk_min = Drunk.drunk_min(this.app);
|
||||||
|
this.drunk_wrap = Drunk.drunk_wrap(this.app);
|
||||||
|
this.warp = Warp.warp(this.app);
|
||||||
|
this.beat_warp = Warp.beat_warp(this.app);
|
||||||
|
this.min = Mathematics.min(this.app);
|
||||||
|
this.max = Mathematics.max(this.app);
|
||||||
|
this.mean = Mathematics.mean(this.app);
|
||||||
|
this.limit = Mathematics.limit(this.app);
|
||||||
|
this.abs = Mathematics.abs(this.app);
|
||||||
|
this.z = Ziffers.z(this.app);
|
||||||
|
Object.assign(this, Ziffers.generateZFunctions(this.app));
|
||||||
|
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.app);
|
||||||
|
this.oncount = Filters.oncount(this.app);
|
||||||
|
this.oneuclid = Filters.oneuclid(this.app);
|
||||||
|
this.euclid = Filters.euclid(this.app);
|
||||||
|
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.app);
|
||||||
|
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.app);
|
||||||
|
this.unoise = LFO.unoise(this.app);
|
||||||
|
this.prob = Probability.prob(this.app);
|
||||||
|
this.toss = Probability.toss(this.app);
|
||||||
|
this.odds = Probability.odds(this.app);
|
||||||
|
this.never = Probability.never(this.app);
|
||||||
|
this.almostNever = Probability.almostNever(this.app);
|
||||||
|
this.rarely = Probability.rarely(this.app);
|
||||||
|
this.scarcely = Probability.scarcely(this.app);
|
||||||
|
this.sometimes = Probability.sometimes(this.app);
|
||||||
|
this.often = Probability.often(this.app);
|
||||||
|
this.frequently = Probability.frequently(this.app);
|
||||||
|
this.almostAlways = Probability.almostAlways(this.app);
|
||||||
|
this.always = Probability.always(this.app);
|
||||||
|
this.dice = Probability.dice(this.app);
|
||||||
|
this.osc = OSC.osc(this.app);
|
||||||
|
this.getOSC = OSC.getOSC(this.app);
|
||||||
|
this.gif = Canvas.gif(this.app);
|
||||||
|
this.scope = Canvas.scope(this.app);
|
||||||
|
this.randI = Randomness.randI(this.app);
|
||||||
|
this.rand = Randomness.rand(this.app);
|
||||||
|
this.seed = Randomness.seed(this.app);
|
||||||
|
this.localSeededRandom = Randomness.localSeededRandom(this.app);
|
||||||
|
this.clearLocalSeed = Randomness.clearLocalSeed(this.app);
|
||||||
|
this.once = Counter.once(this.app);
|
||||||
|
this.counter = Counter.counter(this.app);
|
||||||
|
this.$ = this.counter;
|
||||||
|
this.count = this.counter;
|
||||||
|
this.i = Counter.i(this.app);
|
||||||
|
this.sound = Sound.sound(this.app);
|
||||||
|
this.snd = this.sound; // Alias
|
||||||
|
this.speak = Sound.speak(this.app);
|
||||||
|
this.log = Console.log(this.app);
|
||||||
|
this.logOnce = Console.logOnce(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.clearPatternCache();
|
||||||
|
this.stop();
|
||||||
|
this.play();
|
||||||
|
};
|
||||||
|
|
||||||
|
_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)
|
||||||
|
return {
|
||||||
|
line: parseInt(match[1], 10),
|
||||||
|
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 = 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
|
||||||
|
// =============================================================
|
||||||
|
|
||||||
|
cbar = (): number => {
|
||||||
|
/**
|
||||||
|
* Returns the current bar number
|
||||||
|
*
|
||||||
|
* @returns The current bar number
|
||||||
|
*/
|
||||||
|
return this.app.clock.time_position.bar + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
ctick = (): number => {
|
||||||
|
/**
|
||||||
|
* Returns the current tick number
|
||||||
|
*
|
||||||
|
* @returns The current tick number
|
||||||
|
*/
|
||||||
|
return this.app.clock.tick + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
cpulse = (): number => {
|
||||||
|
/**
|
||||||
|
* Returns the current pulse number
|
||||||
|
*
|
||||||
|
* @returns The current pulse number
|
||||||
|
*/
|
||||||
|
return this.app.clock.time_position.pulse + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
cbeat = (): number => {
|
||||||
|
/**
|
||||||
|
* Returns the current beat number
|
||||||
|
*
|
||||||
|
* @returns The current beat number
|
||||||
|
*/
|
||||||
|
return this.app.clock.time_position.beat + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
ebeat = (): number => {
|
||||||
|
/**
|
||||||
|
* Returns the current beat number since the origin of time
|
||||||
|
*/
|
||||||
|
return this.app.clock.beats_since_origin + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
epulse = (): number => {
|
||||||
|
/**
|
||||||
|
* Returns the current number of pulses elapsed since origin of time
|
||||||
|
*/
|
||||||
|
return this.app.clock.pulses_since_origin + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
nominator = (): number => {
|
||||||
|
/**
|
||||||
|
* Returns the current nominator of the time signature
|
||||||
|
*/
|
||||||
|
return this.app.clock.time_signature[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
meter = (): number => {
|
||||||
|
/**
|
||||||
|
* Returns the current meter (denominator of the time signature)
|
||||||
|
*/
|
||||||
|
return this.app.clock.time_signature[1];
|
||||||
|
};
|
||||||
|
|
||||||
|
denominator = this.meter;
|
||||||
|
|
||||||
|
pulsesForBar = (): number => {
|
||||||
|
/**
|
||||||
|
* Returns the number of pulses in a given bar
|
||||||
|
*/
|
||||||
|
return (this.tempo() * this.ppqn() * this.nominator()) / 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================
|
||||||
|
// 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.pulses_since_origin;
|
||||||
|
};
|
||||||
|
}
|
||||||
60
src/API/Cache.ts
Normal file
60
src/API/Cache.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { isGenerator, isGeneratorFunction, maybeToNumber } from "../Utils/Generic";
|
||||||
|
import { type Player } from "../Classes/ZPlayer";
|
||||||
|
|
||||||
|
|
||||||
|
export const generateCacheKey = (app: any) => (...args: any[]): string => {
|
||||||
|
return args.map((arg) => JSON.stringify(arg)).join(",");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resetAllFromCache = (app: any) => (): void => {
|
||||||
|
app.patternCache.forEach((player: Player) => player.reset());
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clearPatternCache = (app: any) => (): void => {
|
||||||
|
app.patternCache.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removePatternFromCache = (app: any) => (id: string): void => {
|
||||||
|
app.patternCache.delete(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const cache = (app: any) => (key: string, value: any) => {
|
||||||
|
if (value !== undefined) {
|
||||||
|
if (isGenerator(value)) {
|
||||||
|
if (app.patternCache.has(key)) {
|
||||||
|
const cachedValue = (app.patternCache.get(key) as Generator<any>).next().value;
|
||||||
|
if (cachedValue !== 0 && !cachedValue) {
|
||||||
|
const generator = value as unknown as Generator<any>;
|
||||||
|
app.patternCache.set(key, generator);
|
||||||
|
return maybeToNumber(generator.next().value);
|
||||||
|
}
|
||||||
|
return maybeToNumber(cachedValue);
|
||||||
|
} else {
|
||||||
|
const generator = value as unknown as Generator<any>;
|
||||||
|
app.patternCache.set(key, generator);
|
||||||
|
return maybeToNumber(generator.next().value);
|
||||||
|
}
|
||||||
|
} else if (isGeneratorFunction(value)) {
|
||||||
|
if (app.patternCache.has(key)) {
|
||||||
|
const cachedValue = (app.patternCache.get(key) as Generator<any>).next().value;
|
||||||
|
if (cachedValue || cachedValue === 0 || cachedValue === 0n) {
|
||||||
|
return maybeToNumber(cachedValue);
|
||||||
|
} else {
|
||||||
|
const generator = value();
|
||||||
|
app.patternCache.set(key, generator);
|
||||||
|
return maybeToNumber(generator.next().value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const generator = value();
|
||||||
|
app.patternCache.set(key, generator);
|
||||||
|
return maybeToNumber(generator.next().value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
app.patternCache.set(key, value);
|
||||||
|
return maybeToNumber(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return maybeToNumber(app.patternCache.get(key));
|
||||||
|
}
|
||||||
|
};
|
||||||
414
src/API/Canvas.ts
Normal file
414
src/API/Canvas.ts
Normal file
@ -0,0 +1,414 @@
|
|||||||
|
import { OscilloscopeConfig } from "../DOM/Visuals/Oscilloscope";
|
||||||
|
import { ShapeObject, 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 const w = (app: Editor) => (): number => {
|
||||||
|
const canvas: HTMLCanvasElement = app.interface.drawings 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.drawings 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.drawings 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.drawings 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.drawings 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.drawings 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.drawings as HTMLCanvasElement, x, y, angle, ...stops);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const draw = (app: Editor) => (func: Function): boolean => {
|
||||||
|
if (typeof func === "string") {
|
||||||
|
drawText(app.interface.drawings as HTMLCanvasElement, func, 24, 0, "Arial", wc(app)(), hc(app)(), "white", "none");
|
||||||
|
} else {
|
||||||
|
const canvas: HTMLCanvasElement = app.interface.drawings 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.drawings 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.drawings 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.drawings 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.drawings 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.drawings 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.drawings 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.drawings 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.drawings 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.drawings 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.drawings 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.drawings 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.drawings 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: any) => (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.pulse_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: any) => (config: OscilloscopeConfig): void => {
|
||||||
|
/**
|
||||||
|
* Configures the oscilloscope.
|
||||||
|
* @param config - The configuration object for the oscilloscope.
|
||||||
|
*/
|
||||||
|
app.osc = {
|
||||||
|
...app.osc,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
};
|
||||||
20
src/API/Console.ts
Normal file
20
src/API/Console.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export const log = (app: any) => (message: any) => {
|
||||||
|
/**
|
||||||
|
* Logs a message to the console and app-specific logger.
|
||||||
|
* @param message - The message to log.
|
||||||
|
*/
|
||||||
|
console.log(message);
|
||||||
|
app._logMessage(message, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logOnce = (app: any) => (message: any) => {
|
||||||
|
/**
|
||||||
|
* Logs a message to the console and app-specific logger, but only once.
|
||||||
|
* @param message - The message to log.
|
||||||
|
*/
|
||||||
|
if (app.onceEvaluator) {
|
||||||
|
console.log(message);
|
||||||
|
app._logMessage(message, false);
|
||||||
|
app.onceEvaluator = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
40
src/API/Counter.ts
Normal file
40
src/API/Counter.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
export const once = (app: any) => (): boolean => {
|
||||||
|
const firstTime = app.api.onceEvaluator;
|
||||||
|
app.api.onceEvaluator = false;
|
||||||
|
return firstTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const counter = (app: any) => (name: string | number, limit?: number, step?: number): number => {
|
||||||
|
if (!(name in app.counters)) {
|
||||||
|
app.counters[name] = {
|
||||||
|
value: 0,
|
||||||
|
step: step ?? 1,
|
||||||
|
limit,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if (app.counters[name].limit !== limit) {
|
||||||
|
app.counters[name].value = 0;
|
||||||
|
app.counters[name].limit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.counters[name].step !== step) {
|
||||||
|
app.counters[name].step = step ?? app.counters[name].step;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.counters[name].value += app.counters[name].step;
|
||||||
|
|
||||||
|
if (app.counters[name].limit !== undefined && app.counters[name].value > app.counters[name].limit) {
|
||||||
|
app.counters[name].value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.counters[name].value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const i = (app: any) => (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;
|
||||||
|
};
|
||||||
37
src/API/Drunk.ts
Normal file
37
src/API/Drunk.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
export const drunk = (app: any) => (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) {
|
||||||
|
app._drunk.position = n;
|
||||||
|
return app._drunk.getPosition();
|
||||||
|
}
|
||||||
|
app._drunk.step();
|
||||||
|
return app._drunk.getPosition();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drunk_max = (app: any) => (max: number): void => {
|
||||||
|
/**
|
||||||
|
* Sets the maximum value of the drunk mechanism.
|
||||||
|
* @param max - The maximum value of the drunk mechanism
|
||||||
|
*/
|
||||||
|
app._drunk.max = max;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drunk_min = (app: any) => (min: number): void => {
|
||||||
|
/**
|
||||||
|
* Sets the minimum value of the drunk mechanism.
|
||||||
|
* @param min - The minimum value of the drunk mechanism
|
||||||
|
*/
|
||||||
|
app._drunk.min = min;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drunk_wrap = (app: any) => (wrap: boolean): void => {
|
||||||
|
/**
|
||||||
|
* Sets whether the drunk mechanism should wrap around
|
||||||
|
* @param wrap - Whether the drunk mechanism should wrap around
|
||||||
|
*/
|
||||||
|
app._drunk.toggleWrap(wrap);
|
||||||
|
};
|
||||||
203
src/API/Filters.ts
Normal file
203
src/API/Filters.ts
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
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: any) => (n: number | number[] = 1, nudge: number = 0): boolean => {
|
||||||
|
const nArray = Array.isArray(n) ? n : [n];
|
||||||
|
const results: boolean[] = nArray.map(
|
||||||
|
(value) =>
|
||||||
|
(app.clock.pulses_since_origin - Math.floor(nudge * app.ppqn())) %
|
||||||
|
Math.floor(value * app.ppqn()) === 0,
|
||||||
|
);
|
||||||
|
return results.some((value) => value === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const bar = (app: any) => (n: number | number[] = 1, nudge: number = 0): boolean => {
|
||||||
|
const nArray = Array.isArray(n) ? n : [n];
|
||||||
|
const barLength = app.clock.time_signature[1] * app.ppqn();
|
||||||
|
const nudgeInPulses = Math.floor(nudge * barLength);
|
||||||
|
const results: boolean[] = nArray.map(
|
||||||
|
(value) =>
|
||||||
|
(app.clock.pulses_since_origin - nudgeInPulses) %
|
||||||
|
Math.floor(value * barLength) === 0,
|
||||||
|
);
|
||||||
|
return results.some((value) => value === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pulse = (app: any) => (n: number | number[] = 1, nudge: number = 0): boolean => {
|
||||||
|
const nArray = Array.isArray(n) ? n : [n];
|
||||||
|
const results: boolean[] = nArray.map(
|
||||||
|
(value) => (app.clock.pulses_since_origin - nudge) % value === 0,
|
||||||
|
);
|
||||||
|
return results.some((value) => value === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tick = (app: any) => (tick: number | number[], offset: number = 0): boolean => {
|
||||||
|
const nArray = Array.isArray(tick) ? tick : [tick];
|
||||||
|
const results: boolean[] = nArray.map(
|
||||||
|
(value) => app.clock.time_position.pulse === value + offset,
|
||||||
|
);
|
||||||
|
return results.some((value) => value === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dur = (app: any) => (n: number | number[]): boolean => {
|
||||||
|
let nums: number[] = Array.isArray(n) ? n : [n];
|
||||||
|
return beat(app)(nums.dur(...nums));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const flip = (app: any) => (chunk: number, ratio: number = 50): boolean => {
|
||||||
|
let realChunk = chunk * 2;
|
||||||
|
const time_pos = app.clock.pulses_since_origin;
|
||||||
|
const full_chunk = Math.floor(realChunk * app.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: any) => (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: any) => (
|
||||||
|
bars: number[] | number,
|
||||||
|
n: number = app.clock.time_signature[0],
|
||||||
|
): 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 = (app: any) => (...beat: number[]): boolean => {
|
||||||
|
let final_pulses: boolean[] = [];
|
||||||
|
beat.forEach((b) => {
|
||||||
|
let beatNumber = b % app.nominator() || app.nominator();
|
||||||
|
let integral_part = Math.floor(beatNumber);
|
||||||
|
integral_part = integral_part === 0 ? app.nominator() : integral_part;
|
||||||
|
let decimal_part = Math.floor((beatNumber - integral_part) * app.ppqn() + 1);
|
||||||
|
if (decimal_part <= 0)
|
||||||
|
decimal_part += app.ppqn() * app.nominator();
|
||||||
|
final_pulses.push(
|
||||||
|
integral_part === app.cbeat() && app.cpulse() === decimal_part,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return final_pulses.some((p) => p === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const oncount = (app: any) => (beats: number[] | number, count: number): boolean => {
|
||||||
|
if (typeof beats === "number") beats = [beats];
|
||||||
|
const origin = app.clock.pulses_since_origin;
|
||||||
|
let final_pulses: boolean[] = [];
|
||||||
|
beats.forEach((b) => {
|
||||||
|
b = b < 1 ? 0 : b - 1;
|
||||||
|
const beatInTicks = Math.ceil(b * app.ppqn());
|
||||||
|
const meterPosition = origin % (app.ppqn() * count);
|
||||||
|
final_pulses.push(meterPosition === beatInTicks);
|
||||||
|
});
|
||||||
|
return final_pulses.some((p) => p === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const oneuclid = (app: any) => (pulses: number, length: number, rotate: number = 0): boolean => {
|
||||||
|
const cycle = app._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 = (app: any) => (iterator: number, pulses: number, length: number, rotate: number = 0): boolean => {
|
||||||
|
/**
|
||||||
|
* Returns a Euclidean cycle of size length, with n pulses, rotated or not.
|
||||||
|
*/
|
||||||
|
return app._euclidean_cycle(pulses, length, rotate)[iterator % length];
|
||||||
|
};
|
||||||
|
export const ec = euclid;
|
||||||
|
|
||||||
|
export const rhythm = (app: any) => (div: number, pulses: number, length: number, rotate: number = 0): boolean => {
|
||||||
|
/**
|
||||||
|
* Returns a rhythm based on Euclidean cycle.
|
||||||
|
*/
|
||||||
|
return (
|
||||||
|
app.beat(div) && app._euclidean_cycle(pulses, length, rotate).beat(div)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const ry = rhythm;
|
||||||
|
|
||||||
|
export const nrhythm = (app: any) => (div: number, pulses: number, length: number, rotate: number = 0): boolean => {
|
||||||
|
/**
|
||||||
|
* Returns a negated rhythm based on Euclidean cycle.
|
||||||
|
*/
|
||||||
|
let rhythm = app._euclidean_cycle(pulses, length, rotate).map((n: any) => !n);
|
||||||
|
return (
|
||||||
|
app.beat(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];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const binrhythm = (app: any) => (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 app.beat(div) && tobin.beat(div);
|
||||||
|
};
|
||||||
|
export const bry = binrhythm;
|
||||||
65
src/API/LFO.ts
Normal file
65
src/API/LFO.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
export const line = (app: any) => (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: any) => (freq: number = 1, phase: number = 0): number => {
|
||||||
|
return Math.sin(2 * Math.PI * freq * (app.clock.ctx.currentTime - phase));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usine = (app: any) => (freq: number = 1, phase: number = 0): number => {
|
||||||
|
return ((sine(app)(freq, phase) + 1) / 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const saw = (app: any) => (freq: number = 1, phase: number = 0): number => {
|
||||||
|
return (((app.clock.ctx.currentTime * freq + phase) % 1) * 2 - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usaw = (app: any) => (freq: number = 1, phase: number = 0): number => {
|
||||||
|
return ((saw(app)(freq, phase) + 1) / 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const triangle = (app: any) => (freq: number = 1, phase: number = 0): number => {
|
||||||
|
return (Math.abs(saw(app)(freq, phase)) * 2 - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const utriangle = (app: any) => (freq: number = 1, phase: number = 0): number => {
|
||||||
|
return ((triangle(app)(freq, phase) + 1) / 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const square = (app: any) => (freq: number = 1, duty: number = 0.5): number => {
|
||||||
|
const period = 1 / freq;
|
||||||
|
const t = (Date.now() / 1000) % period;
|
||||||
|
return (t / period < duty ? 1 : -1);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usquare = (app: any) => (freq: number = 1, duty: number = 0.5): number => {
|
||||||
|
return ((square(app)(freq, duty) + 1) / 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const noise = (app: any) => (): number => {
|
||||||
|
return (app.randomGen() * 2 - 1); // Assuming randomGen() is defined in the app context
|
||||||
|
};
|
||||||
|
|
||||||
|
export const unoise = (app: any) => (): number => {
|
||||||
|
return ((noise(app)() + 1) / 2);
|
||||||
|
};
|
||||||
199
src/API/MIDI.ts
Normal file
199
src/API/MIDI.ts
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
import { getAllScaleNotes } from 'zifferjs';
|
||||||
|
import {
|
||||||
|
MidiCCEvent,
|
||||||
|
MidiNoteEvent,
|
||||||
|
} from "../IO/MidiConnection";
|
||||||
|
import { MidiEvent, MidiParams } from "../Classes/MidiEvent";
|
||||||
|
|
||||||
|
interface ControlChange {
|
||||||
|
channel: number;
|
||||||
|
control: number;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const midi_outputs = (app: any) => (): void => {
|
||||||
|
app._logMessage(app.MidiConnection.listMidiOutputs(), false);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const midi_output = (app: any) => (outputName: string): void => {
|
||||||
|
if (!outputName) {
|
||||||
|
console.log(app.MidiConnection.getCurrentMidiPort());
|
||||||
|
} else {
|
||||||
|
app.MidiConnection.switchMidiOutput(outputName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const midi = (app: any) => (
|
||||||
|
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 = (app: any) => (data: Array<number>): void => {
|
||||||
|
app.MidiConnection.sendSysExMessage(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pitch_bend = (app: any) => (value: number, channel: number): void => {
|
||||||
|
app.MidiConnection.sendPitchBend(value, channel);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const program_change = (app: any) => (program: number, channel: number): void => {
|
||||||
|
app.MidiConnection.sendProgramChange(program, channel);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const midi_clock = (app: any) => (): void => {
|
||||||
|
app.MidiConnection.sendMidiClock();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const control_change = (app: any) => ({
|
||||||
|
control = 20,
|
||||||
|
value = 0,
|
||||||
|
channel = 0,
|
||||||
|
}: ControlChange): void => {
|
||||||
|
app.MidiConnection.sendMidiControlChange(control, value, channel);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cc = control_change;
|
||||||
|
|
||||||
|
export const midi_panic = (app: any) => (): void => {
|
||||||
|
app.MidiConnection.panic();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const active_note_events = (app: any) => (
|
||||||
|
channel?: number,
|
||||||
|
): MidiNoteEvent[] | undefined => {
|
||||||
|
let events;
|
||||||
|
if (channel) {
|
||||||
|
events = app.MidiConnection.activeNotesFromChannel(channel);
|
||||||
|
} else {
|
||||||
|
events = app.MidiConnection.activeNotes;
|
||||||
|
}
|
||||||
|
if (events.length > 0) return events;
|
||||||
|
else return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const transmission = (app: any) => (): boolean => {
|
||||||
|
return app.MidiConnection.activeNotes.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const active_notes = (app: any) => (channel?: number): number[] | undefined => {
|
||||||
|
const events = active_note_events(app)(channel);
|
||||||
|
if (events && events.length > 0) return events.map((e) => e.note);
|
||||||
|
else return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const kill_active_notes = (app: any) => (): void => {
|
||||||
|
app.MidiConnection.activeNotes = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sticky_notes = (app: any) => (channel?: number): number[] | undefined => {
|
||||||
|
let notes;
|
||||||
|
if (channel) notes = app.MidiConnection.stickyNotesFromChannel(channel);
|
||||||
|
else notes = app.MidiConnection.stickyNotes;
|
||||||
|
if (notes.length > 0) return notes.map((e: any) => e.note);
|
||||||
|
else return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const kill_sticky_notes = (app: any) => (): void => {
|
||||||
|
app.MidiConnection.stickyNotes = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buffer = (app: any) => (channel?: number): boolean => {
|
||||||
|
if (channel)
|
||||||
|
return (
|
||||||
|
app.MidiConnection.findNoteFromBufferInChannel(channel) !== undefined
|
||||||
|
);
|
||||||
|
else return app.MidiConnection.noteInputBuffer.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buffer_event = (app: any) => (channel?: number): MidiNoteEvent | undefined => {
|
||||||
|
if (channel)
|
||||||
|
return app.MidiConnection.findNoteFromBufferInChannel(channel);
|
||||||
|
else return app.MidiConnection.noteInputBuffer.shift();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buffer_note = (app: any) => (channel?: number): number | undefined => {
|
||||||
|
const note = buffer_event(app)(channel);
|
||||||
|
return note ? note.note : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const last_note_event = (app: any) => (channel?: number): MidiNoteEvent | undefined => {
|
||||||
|
if (channel) return app.MidiConnection.lastNoteInChannel[channel];
|
||||||
|
else return app.MidiConnection.lastNote;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const last_note = (app: any) => (channel?: number): number => {
|
||||||
|
const note = last_note_event(app)(channel);
|
||||||
|
return note ? note.note : 60;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ccIn = (app: any) => (control: number, channel?: number): number => {
|
||||||
|
if (channel) {
|
||||||
|
if (app.MidiConnection.lastCCInChannel[channel]) {
|
||||||
|
return app.MidiConnection.lastCCInChannel[channel][control];
|
||||||
|
} else return 0;
|
||||||
|
} else return app.MidiConnection.lastCC[control] || 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const has_cc = (app: any) => (channel?: number): boolean => {
|
||||||
|
if (channel)
|
||||||
|
return (
|
||||||
|
app.MidiConnection.findCCFromBufferInChannel(channel) !== undefined
|
||||||
|
);
|
||||||
|
else return app.MidiConnection.ccInputBuffer.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buffer_cc = (app: any) => (channel?: number): MidiCCEvent | undefined => {
|
||||||
|
if (channel) return app.MidiConnection.findCCFromBufferInChannel(channel);
|
||||||
|
else return app.MidiConnection.ccInputBuffer.shift();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const show_scale = (app: any) => (
|
||||||
|
root: number | string,
|
||||||
|
scale: number | string,
|
||||||
|
channel: number = 0,
|
||||||
|
port: number | string = app.MidiConnection.currentOutputIndex || 0,
|
||||||
|
soundOff: boolean = false,
|
||||||
|
): void => {
|
||||||
|
if (!app.scale_aid || scale !== app.scale_aid) {
|
||||||
|
hide_scale(app)(root, scale, channel, port);
|
||||||
|
const scaleNotes = getAllScaleNotes(scale, root);
|
||||||
|
scaleNotes.forEach((note) => {
|
||||||
|
app.MidiConnection.sendMidiOn(note, channel, 1, port);
|
||||||
|
if (soundOff) app.MidiConnection.sendAllSoundOff(channel, port);
|
||||||
|
});
|
||||||
|
app.scale_aid = scale;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hide_scale = (app: any) => (
|
||||||
|
root: number | string = 0,
|
||||||
|
scale: number | string = 0,
|
||||||
|
channel: number = 0,
|
||||||
|
port: number | string = app.MidiConnection.currentOutputIndex || 0,
|
||||||
|
): void => {
|
||||||
|
const allNotes = Array.from(Array(128).keys());
|
||||||
|
allNotes.forEach((note) => {
|
||||||
|
app.MidiConnection.sendMidiOff(note, channel, port);
|
||||||
|
});
|
||||||
|
app.scale_aid = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const midi_notes_off = (app: any) => (
|
||||||
|
channel: number = 0,
|
||||||
|
port: number | string = app.MidiConnection.currentOutputIndex || 0,
|
||||||
|
): void => {
|
||||||
|
app.MidiConnection.sendAllNotesOff(channel, port);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const midi_sound_off = (app: any) => (
|
||||||
|
channel: number = 0,
|
||||||
|
port: number | string = app.MidiConnection.currentOutputIndex || 0,
|
||||||
|
): void => {
|
||||||
|
app.MidiConnection.sendAllSoundOff(channel, port);
|
||||||
|
};
|
||||||
36
src/API/Math.ts
Normal file
36
src/API/Math.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// mathFunctions.ts
|
||||||
|
export const min = (app: any) => (...values: number[]): number => {
|
||||||
|
/**
|
||||||
|
* Returns the minimum value of a list of numbers.
|
||||||
|
*/
|
||||||
|
return Math.min(...values);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const max = (app: any) => (...values: number[]): number => {
|
||||||
|
/**
|
||||||
|
* Returns the maximum value of a list of numbers.
|
||||||
|
*/
|
||||||
|
return Math.max(...values);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mean = (app: any) => (...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 = (app: any) => (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 = (app: any) => (value: number): number => {
|
||||||
|
/**
|
||||||
|
* Returns the absolute value of a number.
|
||||||
|
*/
|
||||||
|
return Math.abs(value);
|
||||||
|
};
|
||||||
33
src/API/Mouse.ts
Normal file
33
src/API/Mouse.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// mouse.ts
|
||||||
|
export const onmousemove = (app: any) => (e: MouseEvent): void => {
|
||||||
|
app._mouseX = e.pageX;
|
||||||
|
app._mouseY = e.pageY;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mouseX = (app: any) => (): number => {
|
||||||
|
/**
|
||||||
|
* @returns The current x position of the mouse
|
||||||
|
*/
|
||||||
|
return app._mouseX;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mouseY = (app: any) => (): number => {
|
||||||
|
/**
|
||||||
|
* @returns The current y position of the mouse
|
||||||
|
*/
|
||||||
|
return app._mouseY;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const noteX = (app: any) => (): 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: any) => (): number => {
|
||||||
|
/**
|
||||||
|
* @returns The current y position scaled to 0-127 using screen height
|
||||||
|
*/
|
||||||
|
return Math.floor((app._mouseY / document.body.clientHeight) * 127);
|
||||||
|
};
|
||||||
27
src/API/OSC.ts
Normal file
27
src/API/OSC.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { sendToServer, type OSCMessage } from "../IO/OSC";
|
||||||
|
|
||||||
|
export const osc = (app: any) => (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.nudge - app.clock.deviation)),
|
||||||
|
} as OSCMessage);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getOSC = (app: any) => (address?: string): any[] => {
|
||||||
|
/**
|
||||||
|
* Retrieves incoming OSC messages. Filters by address if provided.
|
||||||
|
*/
|
||||||
|
let oscMessages = app.oscMessages; // Assuming `oscMessages` is stored in `app`
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
53
src/API/Probabilities.ts
Normal file
53
src/API/Probabilities.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Probability.ts
|
||||||
|
|
||||||
|
export const prob = (app: any) => (p: number): boolean => {
|
||||||
|
return app.randomGen() * 100 < p;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toss = (app: any) => (): boolean => {
|
||||||
|
return app.randomGen() > 0.5;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const odds = (app: any) => (n: number, beats: number = 1): boolean => {
|
||||||
|
return app.randomGen() < (n * app.ppqn()) / (app.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const never = (app: any) => (beats: number = 1): boolean => {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const almostNever = (app: any) => (beats: number = 1): boolean => {
|
||||||
|
return app.randomGen() < (0.025 * app.ppqn()) / (app.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rarely = (app: any) => (beats: number = 1): boolean => {
|
||||||
|
return app.randomGen() < (0.1 * app.ppqn()) / (app.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const scarcely = (app: any) => (beats: number = 1): boolean => {
|
||||||
|
return app.randomGen() < (0.25 * app.ppqn()) / (app.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sometimes = (app: any) => (beats: number = 1): boolean => {
|
||||||
|
return app.randomGen() < (0.5 * app.ppqn()) / (app.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const often = (app: any) => (beats: number = 1): boolean => {
|
||||||
|
return app.randomGen() < (0.75 * app.ppqn()) / (app.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const frequently = (app: any) => (beats: number = 1): boolean => {
|
||||||
|
return app.randomGen() < (0.9 * app.ppqn()) / (app.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const almostAlways = (app: any) => (beats: number = 1): boolean => {
|
||||||
|
return app.randomGen() < (0.985 * app.ppqn()) / (app.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const always = (app: any) => (beats: number = 1): boolean => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dice = (app: any) => (sides: number): number => {
|
||||||
|
return Math.floor(app.randomGen() * sides) + 1;
|
||||||
|
};
|
||||||
33
src/API/Randomness.ts
Normal file
33
src/API/Randomness.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { seededRandom } from "zifferjs";
|
||||||
|
|
||||||
|
export const randI = (app: any) => (min: number, max: number): number => {
|
||||||
|
return Math.floor(app.randomGen() * (max - min + 1)) + min;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rand = (app: any) => (min: number, max: number): number => {
|
||||||
|
return app.randomGen() * (max - min) + min;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const seed = (app: any) => (seed: string | number): void => {
|
||||||
|
if (typeof seed === "number") seed = seed.toString();
|
||||||
|
if (app.currentSeed !== seed) {
|
||||||
|
app.currentSeed = seed;
|
||||||
|
app.randomGen = seededRandom(seed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const localSeededRandom = (app: any) => (seed: string | number): Function => {
|
||||||
|
if (typeof seed === "number") seed = seed.toString();
|
||||||
|
if (app.localSeeds.has(seed)) return app.localSeeds.get(seed) as Function;
|
||||||
|
const newSeededRandom = seededRandom(seed);
|
||||||
|
app.localSeeds.set(seed, newSeededRandom);
|
||||||
|
return newSeededRandom;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clearLocalSeed = (app: any) => (seed: string | number | undefined = undefined): void => {
|
||||||
|
if (seed) {
|
||||||
|
app.localSeeds.delete(seed.toString());
|
||||||
|
} else {
|
||||||
|
app.localSeeds.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
63
src/API/Script.ts
Normal file
63
src/API/Script.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { tryEvaluate } from "../Evaluator";
|
||||||
|
import { blinkScript } from "../DOM/Visuals/Blinkers";
|
||||||
|
import { template_universes } from "../Editor/FileManagement";
|
||||||
|
|
||||||
|
export const script = (app: any) => (...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: any) => (script: number): void => {
|
||||||
|
app.universes[app.selected_universe].locals[script] = {
|
||||||
|
candidate: "",
|
||||||
|
committed: "",
|
||||||
|
evaluations: 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const copy_script = (app: any) => (from: number, to: number): void => {
|
||||||
|
app.universes[app.selected_universe].locals[to] = {
|
||||||
|
...app.universes[app.selected_universe].locals[from],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const copy_universe = (app: any) => (from: string, to: string): void => {
|
||||||
|
app.universes[to] = {
|
||||||
|
...app.universes[from],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const delete_universe = (app: any) => (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: any) => (): 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();
|
||||||
|
};
|
||||||
43
src/API/Sound.ts
Normal file
43
src/API/Sound.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { SoundEvent } from "../Classes/SoundEvent";
|
||||||
|
import { SkipEvent } from "../Classes/SkipEvent";
|
||||||
|
|
||||||
|
export const sound = (app: any) => (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 = (app: any) => (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);
|
||||||
|
};
|
||||||
|
};
|
||||||
30
src/API/Theme.ts
Normal file
30
src/API/Theme.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
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)];
|
||||||
|
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);
|
||||||
|
};
|
||||||
76
src/API/Transport.ts
Normal file
76
src/API/Transport.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
export const time = (app: any) => (): number => {
|
||||||
|
return app.audioContext.currentTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const play = (app: any) => (): void => {
|
||||||
|
app.setButtonHighlighting("play", true);
|
||||||
|
app.MidiConnection.sendStartMessage();
|
||||||
|
app.clock.start();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pause = (app: any) => (): void => {
|
||||||
|
app.setButtonHighlighting("pause", true);
|
||||||
|
app.clock.pause();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stop = (app: any) => (): void => {
|
||||||
|
app.setButtonHighlighting("stop", true);
|
||||||
|
app.clock.stop();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const silence = (app: any) => (): void => {
|
||||||
|
return stop(app)();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tempo = (app: any) => (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 bpb = (app: any) => (n?: number): number => {
|
||||||
|
/**
|
||||||
|
* Sets or returns the number of beats per bar.
|
||||||
|
*/
|
||||||
|
if (n === undefined) return app.clock.time_signature[0];
|
||||||
|
|
||||||
|
if (n >= 1) {
|
||||||
|
app.clock.time_signature[0] = n;
|
||||||
|
} else {
|
||||||
|
console.error("Beats per bar must be at least 1.");
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ppqn = (app: any) => (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: any) => (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.time_signature = [numerator, denominator];
|
||||||
|
}
|
||||||
|
};
|
||||||
16
src/API/Warp.ts
Normal file
16
src/API/Warp.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export const warp = (app: any) => (n: number): void => {
|
||||||
|
/**
|
||||||
|
* Time-warp the clock by using the tick you wish to jump to.
|
||||||
|
*/
|
||||||
|
app.clock.tick = n;
|
||||||
|
app.clock.time_position = app.clock.convertTicksToTimeposition(n);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const beat_warp = (app: any) => (beat: number): void => {
|
||||||
|
/**
|
||||||
|
* Time-warp the clock by using the tick you wish to jump to.
|
||||||
|
*/
|
||||||
|
const ticks = beat * app.clock.ppqn;
|
||||||
|
app.clock.tick = ticks;
|
||||||
|
app.clock.time_position = app.clock.convertTicksToTimeposition(ticks);
|
||||||
|
};
|
||||||
73
src/API/Ziffers.ts
Normal file
73
src/API/Ziffers.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { InputOptions, Player } from "../Classes/ZPlayer";
|
||||||
|
import { generateCacheKey, removePatternFromCache } from "./Cache"
|
||||||
|
|
||||||
|
|
||||||
|
// ziffersFunctions.ts
|
||||||
|
export const z = (app: any) => (input: string | Generator<number>, options: InputOptions = {}, id: number | string = ""): Player => {
|
||||||
|
const zid = "z" + id.toString();
|
||||||
|
const key = id === "" ? generateCacheKey(app)(input, options) : zid;
|
||||||
|
|
||||||
|
const validSyntax = typeof input === "string" && !app.invalidPatterns[input]
|
||||||
|
|
||||||
|
let player;
|
||||||
|
let replace = false;
|
||||||
|
|
||||||
|
if (app.patternCache.has(key)) {
|
||||||
|
player = app.patternCache.get(key) as Player;
|
||||||
|
|
||||||
|
if (typeof input === "string" &&
|
||||||
|
player.input !== input &&
|
||||||
|
(player.atTheBeginning() || app.forceEvaluator)) {
|
||||||
|
replace = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((typeof input !== "string" || validSyntax) && (!player || replace)) {
|
||||||
|
if (typeof input === "string" && player && app.forceEvaluator) {
|
||||||
|
if (!player.updatePattern(input, options)) {
|
||||||
|
app.logOnce(`Invalid syntax: ${input}`);
|
||||||
|
};
|
||||||
|
app.forceEvaluator = false;
|
||||||
|
} else {
|
||||||
|
const newPlayer = player ? new Player(input, options, app, zid, player.nextEndTime()) : new Player(input, options, app, zid);
|
||||||
|
if (newPlayer.isValid()) {
|
||||||
|
player = newPlayer;
|
||||||
|
app.patternCache.set(key, player);
|
||||||
|
} else if (typeof input === "string") {
|
||||||
|
app.invalidPatterns[input] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player) {
|
||||||
|
if (player.atTheBeginning()) {
|
||||||
|
if (typeof input === "string" && !validSyntax) app.log(`Invalid syntax: ${input}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.ziffers.generator && player.ziffers.generatorDone) {
|
||||||
|
removePatternFromCache(app)(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 = (app: any) => {
|
||||||
|
const zFunctions: { [key: string]: (input: string, opts: InputOptions) => Player } = {};
|
||||||
|
for (let i = 0; i <= 16; i++) {
|
||||||
|
zFunctions[`z${i}`] = (input: string, opts: InputOptions = {}) => z(app)(input, opts, i);
|
||||||
|
}
|
||||||
|
return zFunctions;
|
||||||
|
};
|
||||||
@ -19,7 +19,7 @@ import {
|
|||||||
closeUniverseModal,
|
closeUniverseModal,
|
||||||
openUniverseModal,
|
openUniverseModal,
|
||||||
} from "../Editor/FileManagement";
|
} from "../Editor/FileManagement";
|
||||||
import { loadSamples } from "../API";
|
import { loadSamples } from "../API/API";
|
||||||
import { tryEvaluate } from "../Evaluator";
|
import { tryEvaluate } from "../Evaluator";
|
||||||
import { inlineHoveringTips } from "../Docs/inlineHelp";
|
import { inlineHoveringTips } from "../Docs/inlineHelp";
|
||||||
import { lineNumbers } from "@codemirror/view";
|
import { lineNumbers } from "@codemirror/view";
|
||||||
|
|||||||
@ -53,32 +53,5 @@ ${makeExample(
|
|||||||
`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,4 +1,4 @@
|
|||||||
import { UserAPI } from "../API";
|
import { UserAPI } from "../API/API";
|
||||||
import { AppSettings } from "../Editor/FileManagement";
|
import { AppSettings } from "../Editor/FileManagement";
|
||||||
|
|
||||||
export type MidiNoteEvent = {
|
export type MidiNoteEvent = {
|
||||||
@ -64,7 +64,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();
|
||||||
}
|
}
|
||||||
@ -294,33 +294,33 @@ export class MidiConnection {
|
|||||||
const message = event as MIDIMessageEvent;
|
const message = 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,17 +361,17 @@ 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]) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../main";
|
import { type Editor } from "../main";
|
||||||
import { AudibleEvent } from "./AbstractEvents";
|
import { AudibleEvent } from "../Classes/AbstractEvents";
|
||||||
import { sendToServer, type OSCMessage } from "../IO/OSC";
|
import { sendToServer, type OSCMessage } from "../IO/OSC";
|
||||||
import {
|
import {
|
||||||
filterObject,
|
filterObject,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { type UserAPI } from "../API";
|
import { type UserAPI } from "../API/API";
|
||||||
import { safeScale, stepsToScale } from "zifferjs";
|
import { safeScale, stepsToScale } from "zifferjs";
|
||||||
export { };
|
export { };
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { type UserAPI } from "../API";
|
import { type UserAPI } from "../API/API";
|
||||||
import { MidiEvent } from "../Classes/MidiEvent";
|
import { MidiEvent } from "../Classes/MidiEvent";
|
||||||
import { Player } from "../Classes/ZPlayer";
|
import { Player } from "../Classes/ZPlayer";
|
||||||
import { SoundEvent } from "../Classes/SoundEvent";
|
import { SoundEvent } from "../Classes/SoundEvent";
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { noteNameToMidi } from "zifferjs";
|
import { noteNameToMidi } from "zifferjs";
|
||||||
import { type UserAPI } from "../API";
|
import { type UserAPI } from "../API/API";
|
||||||
import { Player } from "../Classes/ZPlayer";
|
import { Player } from "../Classes/ZPlayer";
|
||||||
export {};
|
export {};
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import { installEditor } from "./Editor/EditorSetup";
|
|||||||
import { documentation_factory, documentation_pages, showDocumentation, updateDocumentationContent } from "./Docs/Documentation";
|
import { documentation_factory, documentation_pages, showDocumentation, updateDocumentationContent } from "./Docs/Documentation";
|
||||||
import { EditorView } from "codemirror";
|
import { EditorView } from "codemirror";
|
||||||
import { Clock } from "./Clock/Clock";
|
import { Clock } from "./Clock/Clock";
|
||||||
import { loadSamples, UserAPI } from "./API";
|
import { loadSamples, UserAPI } from "./API/API";
|
||||||
import * as oeis from "jisg";
|
import * as oeis from "jisg";
|
||||||
import * as zpatterns from "zifferjs/src/patterns.ts";
|
import * as zpatterns from "zifferjs/src/patterns.ts";
|
||||||
import { makeArrayExtensions } from "./Extensions/ArrayExtensions";
|
import { makeArrayExtensions } from "./Extensions/ArrayExtensions";
|
||||||
|
|||||||
Reference in New Issue
Block a user