Continue adaptation work

This commit is contained in:
2024-04-14 23:34:53 +02:00
parent b2a8e18b0e
commit 57f0c9dfe6
20 changed files with 1079 additions and 1055 deletions

View File

@ -83,10 +83,9 @@ export class UserAPI {
*/ */
public codeExamples: { [key: string]: string } = {}; public codeExamples: { [key: string]: string } = {};
public counters: { [key: string]: any } = {};
//@ts-ignore //@ts-ignore
private counters: { [key: string]: any } = {}; public _drunk: DrunkWalk = new DrunkWalk(-100, 100, false);
//@ts-ignore
private _drunk: DrunkWalk = new DrunkWalk(-100, 100, false);
public randomGen = Math.random; public randomGen = Math.random;
public currentSeed: string | undefined = undefined; public currentSeed: string | undefined = undefined;
public localSeeds = new Map<string, Function>(); public localSeeds = new Map<string, Function>();
@ -274,11 +273,11 @@ export class UserAPI {
this.MidiConnection = new MidiConnection(this, app.settings); this.MidiConnection = new MidiConnection(this, app.settings);
this.global = {}; this.global = {};
this.g = this.global; this.g = this.global;
this.time = Transport.time(this.app); this.time = Transport.time(this);
this.play = Transport.play(this.app); this.play = Transport.play(this);
this.pause = Transport.pause(this.app); this.pause = Transport.pause(this);
this.stop = Transport.stop(this.app); this.stop = Transport.stop(this);
this.silence = Transport.silence(this.app); this.silence = Transport.silence(this);
this.tempo = Transport.tempo(this.app); this.tempo = Transport.tempo(this.app);
this.bpb = Transport.bpb(this.app); this.bpb = Transport.bpb(this.app);
this.ppqn = Transport.ppqn(this.app); this.ppqn = Transport.ppqn(this.app);
@ -324,9 +323,9 @@ export class UserAPI {
this.animals = Canvas.animals(); this.animals = Canvas.animals();
this.expressions = Canvas.expressions(); this.expressions = Canvas.expressions();
this.generateCacheKey = Cache.generateCacheKey(); this.generateCacheKey = Cache.generateCacheKey();
this.resetAllFromCache = Cache.resetAllFromCache(this.app); this.resetAllFromCache = Cache.resetAllFromCache(this);
this.clearPatternCache = Cache.clearPatternCache(this.app); this.clearPatternCache = Cache.clearPatternCache(this);
this.removePatternFromCache = Cache.removePatternFromCache(this.app); this.removePatternFromCache = Cache.removePatternFromCache(this);
this.script = Script.script(this.app); this.script = Script.script(this.app);
this.s = this.script; this.s = this.script;
this.delete_script = Script.delete_script(this.app); this.delete_script = Script.delete_script(this.app);
@ -336,10 +335,10 @@ export class UserAPI {
this.copy_universe = Script.copy_universe(this.app); this.copy_universe = Script.copy_universe(this.app);
this.delete_universe = Script.delete_universe(this.app); this.delete_universe = Script.delete_universe(this.app);
this.big_bang = Script.big_bang(this.app); this.big_bang = Script.big_bang(this.app);
this.drunk = Drunk.drunk(this.app); this.drunk = Drunk.drunk(this);
this.drunk_max = Drunk.drunk_max(this.app); this.drunk_max = Drunk.drunk_max(this);
this.drunk_min = Drunk.drunk_min(this.app); this.drunk_min = Drunk.drunk_min(this);
this.drunk_wrap = Drunk.drunk_wrap(this.app); this.drunk_wrap = Drunk.drunk_wrap(this);
this.warp = Warp.warp(this.app); this.warp = Warp.warp(this.app);
this.beat_warp = Warp.beat_warp(this.app); this.beat_warp = Warp.beat_warp(this.app);
this.min = Mathematics.min(); this.min = Mathematics.min();
@ -359,7 +358,7 @@ export class UserAPI {
this.flip = Filters.flip(this.app); this.flip = Filters.flip(this.app);
this.flipbar = Filters.flipbar(this.app); this.flipbar = Filters.flipbar(this.app);
this.onbar = Filters.onbar(this.app); this.onbar = Filters.onbar(this.app);
this.onbeat = Filters.onbeat(this.app); this.onbeat = Filters.onbeat(this);
this.oncount = Filters.oncount(this.app); this.oncount = Filters.oncount(this.app);
this.oneuclid = Filters.oneuclid(this.app); this.oneuclid = Filters.oneuclid(this.app);
this.euclid = Filters.euclid(); this.euclid = Filters.euclid();
@ -378,23 +377,23 @@ export class UserAPI {
this.usaw = LFO.usaw(this.app); this.usaw = LFO.usaw(this.app);
this.triangle = LFO.triangle(this.app); this.triangle = LFO.triangle(this.app);
this.utriangle = LFO.utriangle(this.app); this.utriangle = LFO.utriangle(this.app);
this.square = LFO.square(); this.square = LFO.square(this.app);
this.usquare = LFO.usquare(); this.usquare = LFO.usquare(this.app);
this.noise = LFO.noise(this.app); this.noise = LFO.noise(this);
this.unoise = LFO.unoise(this.app); this.unoise = LFO.unoise(this);
this.prob = Probability.prob(this.app); this.prob = Probability.prob(this);
this.toss = Probability.toss(this.app); this.toss = Probability.toss(this);
this.odds = Probability.odds(this.app); this.odds = Probability.odds(this);
this.never = Probability.never(); this.never = Probability.never();
this.almostNever = Probability.almostNever(this.app); this.almostNever = Probability.almostNever(this);
this.rarely = Probability.rarely(this.app); this.rarely = Probability.rarely(this);
this.scarcely = Probability.scarcely(this.app); this.scarcely = Probability.scarcely(this);
this.sometimes = Probability.sometimes(this.app); this.sometimes = Probability.sometimes(this);
this.often = Probability.often(this.app); this.often = Probability.often(this);
this.frequently = Probability.frequently(this.app); this.frequently = Probability.frequently(this);
this.almostAlways = Probability.almostAlways(this.app); this.almostAlways = Probability.almostAlways(this);
this.always = Probability.always(); this.always = Probability.always();
this.dice = Probability.dice(this.app); this.dice = Probability.dice(this);
this.osc = OSC.osc(this.app); this.osc = OSC.osc(this.app);
this.getOSC = OSC.getOSC(this.app); this.getOSC = OSC.getOSC(this.app);
this.gif = Canvas.gif(this.app); this.gif = Canvas.gif(this.app);
@ -404,16 +403,16 @@ export class UserAPI {
this.seed = Randomness.seed(this.app); this.seed = Randomness.seed(this.app);
this.localSeededRandom = Randomness.localSeededRandom(this.app); this.localSeededRandom = Randomness.localSeededRandom(this.app);
this.clearLocalSeed = Randomness.clearLocalSeed(this.app); this.clearLocalSeed = Randomness.clearLocalSeed(this.app);
this.once = Counter.once(this.app); this.once = Counter.once(this);
this.counter = Counter.counter(this.app); this.counter = Counter.counter(this);
this.$ = this.counter; this.$ = this.counter;
this.count = this.counter; this.count = this.counter;
this.i = Counter.i(this.app); this.i = Counter.i(this.app);
this.sound = Sound.sound(this.app); this.sound = Sound.sound(this.app);
this.snd = this.sound; this.snd = this.sound;
this.speak = Sound.speak(); this.speak = Sound.speak();
this.log = Console.log(this.app); this.log = Console.log(this);
this.logOnce = Console.logOnce(this.app); this.logOnce = Console.logOnce(this);
this.cbar = Transport.cbar(this.app); this.cbar = Transport.cbar(this.app);
this.ctick = Transport.ctick(this.app); this.ctick = Transport.ctick(this.app);
this.cpulse = Transport.cpulse(this.app); this.cpulse = Transport.cpulse(this.app);
@ -425,7 +424,7 @@ export class UserAPI {
this.denominator = Transport.denominator(this.app); // Alias for meter this.denominator = Transport.denominator(this.app); // Alias for meter
this.pulsesForBar = Transport.pulsesForBar(this.app); this.pulsesForBar = Transport.pulsesForBar(this.app);
} }
public g: any; public g: any;
@ -474,7 +473,7 @@ export class UserAPI {
? code ? code
: (this.app.selectedExample as string); : (this.app.selectedExample as string);
} }
this.clearPatternCache(); this.patternCache.clear();
this.stop(); this.stop();
this.play(); this.play();
}; };
@ -609,7 +608,7 @@ export class UserAPI {
nearScales = nearScales; nearScales = nearScales;
public cue = (functionName: string | Function): void => { public cue = (functionName: string | Function): void => {
functionName = typeof functionName === "function" ? functionName.name : functionName; functionName = typeof functionName === "function" ? functionName.name : functionName;
this.cueTimes[functionName] = this.app.clock.pulses_since_origin; this.cueTimes[functionName] = this.app.clock.pulses_since_origin;
}; };
} }

View File

@ -1,60 +1,61 @@
import { isGenerator, isGeneratorFunction, maybeToNumber } from "../Utils/Generic"; import { isGenerator, isGeneratorFunction, maybeToNumber } from "../Utils/Generic";
import { type Player } from "../Classes/ZPlayer"; import { type Player } from "../Classes/ZPlayer";
import { type UserAPI } from "./API";
export const generateCacheKey = () => (...args: any[]): string => { export const generateCacheKey = () => (...args: any[]): string => {
return args.map((arg) => JSON.stringify(arg)).join(","); return args.map((arg) => JSON.stringify(arg)).join(",");
}; };
export const resetAllFromCache = (app: any) => (): void => { export const resetAllFromCache = (api: UserAPI) => (): void => {
app.patternCache.forEach((player: Player) => player.reset()); api.patternCache.forEach((player: Player) => player.reset());
}; };
export const clearPatternCache = (app: any) => (): void => { export const clearPatternCache = (api: UserAPI) => (): void => {
app.patternCache.clear(); api.patternCache.clear();
}; };
export const removePatternFromCache = (app: any) => (id: string): void => { export const removePatternFromCache = (api: UserAPI) => (id: string): void => {
app.patternCache.delete(id); api.patternCache.delete(id);
}; };
export const cache = (app: any) => (key: string, value: any) => { export const cache = (api: UserAPI) => (key: string, value: any) => {
if (value !== undefined) { if (value !== undefined) {
if (isGenerator(value)) { if (isGenerator(value)) {
if (app.patternCache.has(key)) { if (api.patternCache.has(key)) {
const cachedValue = (app.patternCache.get(key) as Generator<any>).next().value; const cachedValue = (api.patternCache.get(key) as Generator<any>).next().value;
if (cachedValue !== 0 && !cachedValue) { if (cachedValue !== 0 && !cachedValue) {
const generator = value as unknown as Generator<any>; const generator = value as unknown as Generator<any>;
app.patternCache.set(key, generator); api.patternCache.set(key, generator);
return maybeToNumber(generator.next().value); 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);
} }
return maybeToNumber(cachedValue);
} else {
const generator = value as unknown as Generator<any>;
api.patternCache.set(key, generator);
return maybeToNumber(generator.next().value);
}
} else if (isGeneratorFunction(value)) {
if (api.patternCache.has(key)) {
const cachedValue = (api.patternCache.get(key) as Generator<any>).next().value;
if (cachedValue || cachedValue === 0 || cachedValue === 0n) {
return maybeToNumber(cachedValue);
} else {
const generator = value();
api.patternCache.set(key, generator);
return maybeToNumber(generator.next().value);
}
} else {
const generator = value();
api.patternCache.set(key, generator);
return maybeToNumber(generator.next().value);
}
} else { } else {
return maybeToNumber(app.patternCache.get(key)); api.patternCache.set(key, value);
return maybeToNumber(value);
} }
} else {
return maybeToNumber(api.patternCache.get(key));
}
}; };

View File

@ -3,412 +3,412 @@ import { ShapeObject, createConicGradient, createLinearGradient, createRadialGra
import { Editor } from "../main"; import { Editor } from "../main";
export const w = (app: Editor) => (): number => { export const w = (app: Editor) => (): number => {
const canvas: HTMLCanvasElement = app.interface.drawings as HTMLCanvasElement; const canvas: HTMLCanvasElement = app.interface.drawings as HTMLCanvasElement;
return canvas.clientWidth; return canvas.clientWidth;
}; };
export const pulseLocation = (app: Editor) => (): number => { export const pulseLocation = (app: Editor) => (): number => {
return ((app.api.epulse() / app.api.pulsesForBar()) * w(app)()) % w(app)(); return ((app.api.epulse() / app.api.pulsesForBar()) * w(app)()) % w(app)();
}; };
export const clear = (app: Editor) => (): boolean => { export const clear = (app: Editor) => (): boolean => {
const canvas: HTMLCanvasElement = app.interface.drawings as HTMLCanvasElement; const canvas: HTMLCanvasElement = app.interface.drawings as HTMLCanvasElement;
const ctx = canvas.getContext("2d")!; const ctx = canvas.getContext("2d")!;
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.clearRect(0, 0, canvas.width, canvas.height);
return true; return true;
}; };
export const h = (app: Editor) => (): number => { export const h = (app: Editor) => (): number => {
const canvas: HTMLCanvasElement = app.interface.drawings as HTMLCanvasElement; const canvas: HTMLCanvasElement = app.interface.drawings as HTMLCanvasElement;
return canvas.clientHeight; return canvas.clientHeight;
}; };
export const hc = (app: Editor) => (): number => { export const hc = (app: Editor) => (): number => {
return h(app)() / 2; return h(app)() / 2;
}; };
export const wc = (app: Editor) => (): number => { export const wc = (app: Editor) => (): number => {
return w(app)() / 2; return w(app)() / 2;
}; };
export const background = (app: Editor) => (color: string | number, ...gb: number[]): boolean => { export const background = (app: Editor) => (color: string | number, ...gb: number[]): boolean => {
drawBackground(app.interface.drawings as HTMLCanvasElement, color, ...gb); drawBackground(app.interface.drawings as HTMLCanvasElement, color, ...gb);
return true; return true;
}; };
export const bg = background; export const bg = background;
export const linearGradient = (app: Editor) => (x1: number, y1: number, x2: number, y2: number, ...stops: (number | string)[]): CanvasGradient => { 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); 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)[]) => { 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); 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)[]) => { 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); return createConicGradient(app.interface.drawings as HTMLCanvasElement, x, y, angle, ...stops);
}; };
export const draw = (app: Editor) => (func: Function): boolean => { export const draw = (app: Editor) => (func: Function): boolean => {
if (typeof func === "string") { if (typeof func === "string") {
drawText(app.interface.drawings as HTMLCanvasElement, func, 24, 0, "Arial", wc(app)(), hc(app)(), "white", "none"); drawText(app.interface.drawings as HTMLCanvasElement, func, 24, 0, "Arial", wc(app)(), hc(app)(), "white", "none");
} else { } else {
const canvas: HTMLCanvasElement = app.interface.drawings as HTMLCanvasElement; const canvas: HTMLCanvasElement = app.interface.drawings as HTMLCanvasElement;
const ctx = canvas.getContext("2d")!; const ctx = canvas.getContext("2d")!;
func(ctx); func(ctx);
} }
return true; return true;
}; };
// Additional drawing and utility functions in canvas.ts // Additional drawing and utility functions in canvas.ts
export const balloid = (app: Editor) => ( export const balloid = (app: Editor) => (
curves: number | ShapeObject = 6, curves: number | ShapeObject = 6,
radius: number = hc(app)() / 2, radius: number = hc(app)() / 2,
curve: number = 1.5, curve: number = 1.5,
fillStyle: string = "white", fillStyle: string = "white",
secondary: string = "black", secondary: string = "black",
x: number = wc(app)(), x: number = wc(app)(),
y: number = hc(app)(), y: number = hc(app)(),
): boolean => { ): boolean => {
if (typeof curves === "object") { if (typeof curves === "object") {
fillStyle = curves.fillStyle || "white"; fillStyle = curves.fillStyle || "white";
x = curves.x || wc(app)(); x = curves.x || wc(app)();
y = curves.y || hc(app)(); y = curves.y || hc(app)();
curve = curves.curve || 1.5; curve = curves.curve || 1.5;
radius = curves.radius || hc(app)() / 2; radius = curves.radius || hc(app)() / 2;
curves = curves.curves || 6; curves = curves.curves || 6;
} }
drawBalloid(app.interface.drawings as HTMLCanvasElement, curves, radius, curve, fillStyle, secondary, x, y); drawBalloid(app.interface.drawings as HTMLCanvasElement, curves, radius, curve, fillStyle, secondary, x, y);
return true; return true;
}; };
export const equilateral = (app: Editor) => ( export const equilateral = (app: Editor) => (
radius: number | ShapeObject = hc(app)() / 3, radius: number | ShapeObject = hc(app)() / 3,
fillStyle: string = "white", fillStyle: string = "white",
rotation: number = 0, rotation: number = 0,
x: number = wc(app)(), x: number = wc(app)(),
y: number = hc(app)(), y: number = hc(app)(),
): boolean => { ): boolean => {
if (typeof radius === "object") { if (typeof radius === "object") {
fillStyle = radius.fillStyle || "white"; fillStyle = radius.fillStyle || "white";
x = radius.x || wc(app)(); x = radius.x || wc(app)();
y = radius.y || hc(app)(); y = radius.y || hc(app)();
rotation = radius.rotation || 0; rotation = radius.rotation || 0;
radius = radius.radius || hc(app)() / 3; radius = radius.radius || hc(app)() / 3;
} }
drawEquilateral(app.interface.drawings as HTMLCanvasElement, radius, fillStyle, rotation, x, y); drawEquilateral(app.interface.drawings as HTMLCanvasElement, radius, fillStyle, rotation, x, y);
return true; return true;
}; };
export const triangular = (app: Editor) => ( export const triangular = (app: Editor) => (
width: number | ShapeObject = hc(app)() / 3, width: number | ShapeObject = hc(app)() / 3,
height: number = hc(app)() / 3, height: number = hc(app)() / 3,
fillStyle: string = "white", fillStyle: string = "white",
rotation: number = 0, rotation: number = 0,
x: number = wc(app)(), x: number = wc(app)(),
y: number = hc(app)(), y: number = hc(app)(),
): boolean => { ): boolean => {
if (typeof width === "object") { if (typeof width === "object") {
fillStyle = width.fillStyle || "white"; fillStyle = width.fillStyle || "white";
x = width.x || wc(app)(); x = width.x || wc(app)();
y = width.y || hc(app)(); y = width.y || hc(app)();
rotation = width.rotation || 0; rotation = width.rotation || 0;
height = width.height || hc(app)() / 3; height = width.height || hc(app)() / 3;
width = width.width || hc(app)() / 3; width = width.width || hc(app)() / 3;
} }
drawTriangular(app.interface.drawings as HTMLCanvasElement, width, height, fillStyle, rotation, x, y); drawTriangular(app.interface.drawings as HTMLCanvasElement, width, height, fillStyle, rotation, x, y);
return true; return true;
}; };
export const pointy = triangular; export const pointy = triangular;
export const ball = (app: Editor) => ( export const ball = (app: Editor) => (
radius: number | ShapeObject = hc(app)() / 3, radius: number | ShapeObject = hc(app)() / 3,
fillStyle: string = "white", fillStyle: string = "white",
x: number = wc(app)(), x: number = wc(app)(),
y: number = hc(app)(), y: number = hc(app)(),
): boolean => { ): boolean => {
if (typeof radius === "object") { if (typeof radius === "object") {
fillStyle = radius.fillStyle || "white"; fillStyle = radius.fillStyle || "white";
x = radius.x || wc(app)(); x = radius.x || wc(app)();
y = radius.y || hc(app)(); y = radius.y || hc(app)();
radius = radius.radius || hc(app)() / 3; radius = radius.radius || hc(app)() / 3;
} }
drawBall(app.interface.drawings as HTMLCanvasElement, radius, fillStyle, x, y); drawBall(app.interface.drawings as HTMLCanvasElement, radius, fillStyle, x, y);
return true; return true;
}; };
export const circle = ball; export const circle = ball;
export const donut = (app: Editor) => ( export const donut = (app: Editor) => (
slices: number | ShapeObject = 3, slices: number | ShapeObject = 3,
eaten: number = 0, eaten: number = 0,
radius: number = hc(app)() / 3, radius: number = hc(app)() / 3,
hole: number = hc(app)() / 12, hole: number = hc(app)() / 12,
fillStyle: string = "white", fillStyle: string = "white",
secondary: string = "black", secondary: string = "black",
stroke: string = "black", stroke: string = "black",
rotation: number = 0, rotation: number = 0,
x: number = wc(app)(), x: number = wc(app)(),
y: number = hc(app)(), y: number = hc(app)(),
): boolean => { ): boolean => {
if (typeof slices === "object") { if (typeof slices === "object") {
fillStyle = slices.fillStyle || "white"; fillStyle = slices.fillStyle || "white";
x = slices.x || wc(app)(); x = slices.x || wc(app)();
y = slices.y || hc(app)(); y = slices.y || hc(app)();
rotation = slices.rotation || 0; rotation = slices.rotation || 0;
radius = slices.radius || hc(app)() / 3; radius = slices.radius || hc(app)() / 3;
eaten = slices.eaten || 0; eaten = slices.eaten || 0;
hole = slices.hole || hc(app)() / 12; hole = slices.hole || hc(app)() / 12;
secondary = slices.secondary || "black"; secondary = slices.secondary || "black";
stroke = slices.stroke || "black"; stroke = slices.stroke || "black";
slices = slices.slices || 3; slices = slices.slices || 3;
} }
drawDonut(app.interface.drawings as HTMLCanvasElement, slices, eaten, radius, hole, fillStyle, secondary, stroke, rotation, x, y); drawDonut(app.interface.drawings as HTMLCanvasElement, slices, eaten, radius, hole, fillStyle, secondary, stroke, rotation, x, y);
return true; return true;
}; };
export const pie = (app: Editor) => ( export const pie = (app: Editor) => (
slices: number | ShapeObject = 3, slices: number | ShapeObject = 3,
eaten: number = 0, eaten: number = 0,
radius: number = hc(app)() / 3, radius: number = hc(app)() / 3,
fillStyle: string = "white", fillStyle: string = "white",
secondary: string = "black", secondary: string = "black",
stroke: string = "black", stroke: string = "black",
rotation: number = 0, rotation: number = 0,
x: number = wc(app)(), x: number = wc(app)(),
y: number = hc(app)(), y: number = hc(app)(),
): boolean => { ): boolean => {
if (typeof slices === "object") { if (typeof slices === "object") {
fillStyle = slices.fillStyle || "white"; fillStyle = slices.fillStyle || "white";
x = slices.x || wc(app)(); x = slices.x || wc(app)();
y = slices.y || hc(app)(); y = slices.y || hc(app)();
rotation = slices.rotation || 0; rotation = slices.rotation || 0;
radius = slices.radius || hc(app)() / 3; radius = slices.radius || hc(app)() / 3;
secondary = slices.secondary || "black"; secondary = slices.secondary || "black";
stroke = slices.stroke || "black"; stroke = slices.stroke || "black";
eaten = slices.eaten || 0; eaten = slices.eaten || 0;
slices = slices.slices || 3; slices = slices.slices || 3;
} }
drawPie(app.interface.drawings as HTMLCanvasElement, slices, eaten, radius, fillStyle, secondary, stroke, rotation, x, y); drawPie(app.interface.drawings as HTMLCanvasElement, slices, eaten, radius, fillStyle, secondary, stroke, rotation, x, y);
return true; return true;
}; };
export const star = (app: Editor) => ( export const star = (app: Editor) => (
points: number | ShapeObject = 5, points: number | ShapeObject = 5,
radius: number = hc(app)() / 3, radius: number = hc(app)() / 3,
fillStyle: string = "white", fillStyle: string = "white",
rotation: number = 0, rotation: number = 0,
outerRadius: number = radius / 100, outerRadius: number = radius / 100,
x: number = wc(app)(), x: number = wc(app)(),
y: number = hc(app)(), y: number = hc(app)(),
): boolean => { ): boolean => {
if (typeof points === "object") { if (typeof points === "object") {
radius = points.radius || hc(app)() / 3; radius = points.radius || hc(app)() / 3;
fillStyle = points.fillStyle || "white"; fillStyle = points.fillStyle || "white";
x = points.x || wc(app)(); x = points.x || wc(app)();
y = points.y || hc(app)(); y = points.y || hc(app)();
rotation = points.rotation || 0; rotation = points.rotation || 0;
outerRadius = points.outerRadius || radius / 100; outerRadius = points.outerRadius || radius / 100;
points = points.points || 5; points = points.points || 5;
} }
drawStar(app.interface.drawings as HTMLCanvasElement, points, radius, fillStyle, rotation, outerRadius, x, y); drawStar(app.interface.drawings as HTMLCanvasElement, points, radius, fillStyle, rotation, outerRadius, x, y);
return true; return true;
}; };
export const stroke = (app: Editor) => ( export const stroke = (app: Editor) => (
width: number | ShapeObject = 1, width: number | ShapeObject = 1,
strokeStyle: string = "white", strokeStyle: string = "white",
rotation: number = 0, rotation: number = 0,
x1: number = wc(app)() - wc(app)() / 10, x1: number = wc(app)() - wc(app)() / 10,
y1: number = hc(app)(), y1: number = hc(app)(),
x2: number = wc(app)() + wc(app)() / 5, x2: number = wc(app)() + wc(app)() / 5,
y2: number = hc(app)(), y2: number = hc(app)(),
): boolean => { ): boolean => {
if (typeof width === "object") { if (typeof width === "object") {
strokeStyle = width.strokeStyle || "white"; strokeStyle = width.strokeStyle || "white";
x1 = width.x1 || wc(app)() - wc(app)() / 10; x1 = width.x1 || wc(app)() - wc(app)() / 10;
y1 = width.y1 || hc(app)(); y1 = width.y1 || hc(app)();
x2 = width.x2 || wc(app)() + wc(app)() / 5; x2 = width.x2 || wc(app)() + wc(app)() / 5;
y2 = width.y2 || hc(app)(); y2 = width.y2 || hc(app)();
rotation = width.rotation || 0; rotation = width.rotation || 0;
width = width.width || 1; width = width.width || 1;
} }
drawStroke(app.interface.drawings as HTMLCanvasElement, width, strokeStyle, rotation, x1, y1, x2, y2); drawStroke(app.interface.drawings as HTMLCanvasElement, width, strokeStyle, rotation, x1, y1, x2, y2);
return true; return true;
}; };
export const box = (app: Editor) => ( export const box = (app: Editor) => (
width: number | ShapeObject = wc(app)() / 4, width: number | ShapeObject = wc(app)() / 4,
height: number = wc(app)() / 4, height: number = wc(app)() / 4,
fillStyle: string = "white", fillStyle: string = "white",
rotation: number = 0, rotation: number = 0,
x: number = wc(app)() - wc(app)() / 8, x: number = wc(app)() - wc(app)() / 8,
y: number = hc(app)() - hc(app)() / 8, y: number = hc(app)() - hc(app)() / 8,
): boolean => { ): boolean => {
if (typeof width === "object") { if (typeof width === "object") {
fillStyle = width.fillStyle || "white"; fillStyle = width.fillStyle || "white";
x = width.x || wc(app)() - wc(app)() / 4; x = width.x || wc(app)() - wc(app)() / 4;
y = width.y || hc(app)() - hc(app)() / 2; y = width.y || hc(app)() - hc(app)() / 2;
rotation = width.rotation || 0; rotation = width.rotation || 0;
height = width.height || wc(app)() / 4; height = width.height || wc(app)() / 4;
width = width.width || wc(app)() / 4; width = width.width || wc(app)() / 4;
} }
drawBox(app.interface.drawings as HTMLCanvasElement, width, height, fillStyle, rotation, x, y); drawBox(app.interface.drawings as HTMLCanvasElement, width, height, fillStyle, rotation, x, y);
return true; return true;
}; };
export const smiley = (app: Editor) => ( export const smiley = (app: Editor) => (
happiness: number | ShapeObject = 0, happiness: number | ShapeObject = 0,
radius: number = hc(app)() / 3, radius: number = hc(app)() / 3,
eyeSize: number = 3.0, eyeSize: number = 3.0,
fillStyle: string = "yellow", fillStyle: string = "yellow",
rotation: number = 0, rotation: number = 0,
x: number = wc(app)(), x: number = wc(app)(),
y: number = hc(app)(), y: number = hc(app)(),
): boolean => { ): boolean => {
if (typeof happiness === "object") { if (typeof happiness === "object") {
fillStyle = happiness.fillStyle || "yellow"; fillStyle = happiness.fillStyle || "yellow";
x = happiness.x || wc(app)(); x = happiness.x || wc(app)();
y = happiness.y || hc(app)(); y = happiness.y || hc(app)();
rotation = happiness.rotation || 0; rotation = happiness.rotation || 0;
eyeSize = happiness.eyeSize || 3.0; eyeSize = happiness.eyeSize || 3.0;
radius = happiness.radius || hc(app)() / 3; radius = happiness.radius || hc(app)() / 3;
happiness = happiness.happiness || 0; happiness = happiness.happiness || 0;
} }
drawSmiley(app.interface.drawings as HTMLCanvasElement, happiness, radius, eyeSize, fillStyle, rotation, x, y); drawSmiley(app.interface.drawings as HTMLCanvasElement, happiness, radius, eyeSize, fillStyle, rotation, x, y);
return true; return true;
}; };
export const text = (app: Editor) => ( export const text = (app: Editor) => (
text: string | ShapeObject, text: string | ShapeObject,
fontSize: number = 24, fontSize: number = 24,
rotation: number = 0, rotation: number = 0,
font: string = "Arial", font: string = "Arial",
x: number = wc(app)(), x: number = wc(app)(),
y: number = hc(app)(), y: number = hc(app)(),
fillStyle: string = "white", fillStyle: string = "white",
filter: string = "none", filter: string = "none",
): boolean => { ): boolean => {
if (typeof text === "object") { if (typeof text === "object") {
fillStyle = text.fillStyle || "white"; fillStyle = text.fillStyle || "white";
x = text.x || wc(app)(); x = text.x || wc(app)();
y = text.y || hc(app)(); y = text.y || hc(app)();
rotation = text.rotation || 0; rotation = text.rotation || 0;
font = text.font || "Arial"; font = text.font || "Arial";
fontSize = text.fontSize || 24; fontSize = text.fontSize || 24;
filter = text.filter || "none"; filter = text.filter || "none";
text = text.text || ""; text = text.text || "";
} }
drawText(app.interface.drawings as HTMLCanvasElement, text, fontSize, rotation, font, x, y, fillStyle, filter); drawText(app.interface.drawings as HTMLCanvasElement, text, fontSize, rotation, font, x, y, fillStyle, filter);
return true; return true;
}; };
export const image = (app: Editor) => ( export const image = (app: Editor) => (
url: string | ShapeObject, url: string | ShapeObject,
width: number = wc(app)() / 2, width: number = wc(app)() / 2,
height: number = hc(app)() / 2, height: number = hc(app)() / 2,
rotation: number = 0, rotation: number = 0,
x: number = wc(app)(), x: number = wc(app)(),
y: number = hc(app)(), y: number = hc(app)(),
filter: string = "none", filter: string = "none",
): boolean => { ): boolean => {
if (typeof url === "object") { if (typeof url === "object") {
if (!url.url) return true; if (!url.url) return true;
x = url.x || wc(app)(); x = url.x || wc(app)();
y = url.y || hc(app)(); y = url.y || hc(app)();
rotation = url.rotation || 0; rotation = url.rotation || 0;
width = url.width || 100; width = url.width || 100;
height = url.height || 100; height = url.height || 100;
filter = url.filter || "none"; filter = url.filter || "none";
url = url.url || ""; url = url.url || "";
} }
drawImage(app.interface.drawings as HTMLCanvasElement, url, width, height, rotation, x, y, filter); drawImage(app.interface.drawings as HTMLCanvasElement, url, width, height, rotation, x, y, filter);
return true; return true;
}; };
export const randomChar = () => (length: number = 1, min: number = 0, max: number = 65536): string => { export const randomChar = () => (length: number = 1, min: number = 0, max: number = 65536): string => {
return Array.from( return Array.from(
{ length }, () => String.fromCodePoint(Math.floor(Math.random() * (max - min) + min)) { length }, () => String.fromCodePoint(Math.floor(Math.random() * (max - min) + min))
).join(''); ).join('');
}; };
export const randomFromRange = () => (min: number, max: number): string => { export const randomFromRange = () => (min: number, max: number): string => {
const codePoint = Math.floor(Math.random() * (max - min) + min); const codePoint = Math.floor(Math.random() * (max - min) + min);
return String.fromCodePoint(codePoint); return String.fromCodePoint(codePoint);
}; };
export const emoji = () => (n: number = 1): string => { export const emoji = () => (n: number = 1): string => {
return randomChar()(n, 0x1f600, 0x1f64f); return randomChar()(n, 0x1f600, 0x1f64f);
}; };
export const food = () => (n: number = 1): string => { export const food = () => (n: number = 1): string => {
return randomChar()(n, 0x1f32d, 0x1f37f); return randomChar()(n, 0x1f32d, 0x1f37f);
}; };
export const animals = () => (n: number = 1): string => { export const animals = () => (n: number = 1): string => {
return randomChar()(n, 0x1f400, 0x1f4d3); return randomChar()(n, 0x1f400, 0x1f4d3);
}; };
export const expressions = () => (n: number = 1): string => { export const expressions = () => (n: number = 1): string => {
return randomChar()(n, 0x1f910, 0x1f92f); return randomChar()(n, 0x1f910, 0x1f92f);
}; };
export const gif = (app: any) => (options: any): void => { export const gif = (app: Editor) => (options: any): void => {
const { const {
url, url,
posX = 0, posX = 0,
posY = 0, posY = 0,
opacity = 1, opacity = 1,
size = "auto", size = "auto",
center = false, center = false,
rotation = 0, rotation = 0,
filter = 'none', filter = 'none',
duration = 10 duration = 10
} = options; } = options;
let real_duration = duration * app.clock.pulse_duration * app.clock.ppqn; let real_duration = duration * app.clock.pulse_duration * app.clock.ppqn;
let fadeOutDuration = real_duration * 0.1; let fadeOutDuration = real_duration * 0.1;
let visibilityDuration = real_duration - fadeOutDuration; let visibilityDuration = real_duration - fadeOutDuration;
const gifElement = document.createElement("img"); const gifElement = document.createElement("img");
gifElement.src = url; gifElement.src = url;
gifElement.style.position = "fixed"; gifElement.style.position = "fixed";
gifElement.style.left = center ? "50%" : `${posX}px`; gifElement.style.left = center ? "50%" : `${posX}px`;
gifElement.style.top = center ? "50%" : `${posY}px`; gifElement.style.top = center ? "50%" : `${posY}px`;
gifElement.style.opacity = `${opacity}`; gifElement.style.opacity = `${opacity}`;
gifElement.style.zIndex = "1000"; // Ensure it's on top, fixed zIndex gifElement.style.zIndex = "1000"; // Ensure it's on top, fixed zIndex
if (size !== "auto") { if (size !== "auto") {
gifElement.style.width = size; gifElement.style.width = size;
gifElement.style.height = 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);
} }
const transformRules = [`rotate(${rotation}deg)`]; }, real_duration * 1000);
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 => { export const scope = (app: Editor) => (config: OscilloscopeConfig): void => {
/** /**
* Configures the oscilloscope. * Configures the oscilloscope.
* @param config - The configuration object for the oscilloscope. * @param config - The configuration object for the oscilloscope.
*/ */
app.osc = { app.osc = {
...app.osc, ...app.osc,
...config, ...config,
}; };
}; };

View File

@ -1,20 +1,22 @@
export const log = (app: any) => (message: any) => { import { type UserAPI } from "./API";
/**
* Logs a message to the console and app-specific logger. export const log = (api: UserAPI) => (message: any) => {
* @param message - The message to log. /**
*/ * Logs a message to the console and app-specific logger.
console.log(message); * @param message - The message to log.
app._logMessage(message, false); */
console.log(message);
api._logMessage(message, false);
}; };
export const logOnce = (app: any) => (message: any) => { export const logOnce = (api: UserAPI) => (message: any) => {
/** /**
* Logs a message to the console and app-specific logger, but only once. * Logs a message to the console and app-specific logger, but only once.
* @param message - The message to log. * @param message - The message to log.
*/ */
if (app.onceEvaluator) { if (api.onceEvaluator) {
console.log(message); console.log(message);
app._logMessage(message, false); api._logMessage(message, false);
app.onceEvaluator = false; api.onceEvaluator = false;
} }
}; };

View File

@ -1,40 +1,43 @@
export const once = (app: any) => (): boolean => { import { type UserAPI } from "./API";
const firstTime = app.api.onceEvaluator; import { type Editor } from "../main";
app.api.onceEvaluator = false;
return firstTime; export const once = (api: UserAPI) => (): boolean => {
const firstTime = api.onceEvaluator;
api.onceEvaluator = false;
return firstTime;
}; };
export const counter = (app: any) => (name: string | number, limit?: number, step?: number): number => { export const counter = (api: UserAPI) => (name: string | number, limit?: number, step?: number): number => {
if (!(name in app.counters)) { if (!(name in api.counters)) {
app.counters[name] = { api.counters[name] = {
value: 0, value: 0,
step: step ?? 1, step: step ?? 1,
limit, limit,
}; };
} else { } else {
if (app.counters[name].limit !== limit) { if (api.counters[name].limit !== limit) {
app.counters[name].value = 0; api.counters[name].value = 0;
app.counters[name].limit = limit; api.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; if (api.counters[name].step !== step) {
api.counters[name].step = step ?? api.counters[name].step;
}
api.counters[name].value += api.counters[name].step;
if (api.counters[name].limit !== undefined && api.counters[name].value > api.counters[name].limit) {
api.counters[name].value = 0;
}
}
return api.counters[name].value;
}; };
export const i = (app: any) => (n?: number) => { export const i = (app: Editor) => (n?: number) => {
if (n !== undefined) { if (n !== undefined) {
app.universes[app.selected_universe].global.evaluations = n; app.universes[app.selected_universe].global.evaluations = n;
return app.universes[app.selected_universe]; return app.universes[app.selected_universe];
} }
return app.universes[app.selected_universe].global.evaluations as number; return app.universes[app.selected_universe].global.evaluations as number;
}; };

View File

@ -1,37 +1,39 @@
export const drunk = (app: any) => (n?: number): number => { import { type UserAPI } from './API';
/**
* This function sets or returns the current drunk mechanism's value. export const drunk = (api: UserAPI) => (n?: number): number => {
* @param n - [optional] The value to set the drunk mechanism to /**
* @returns The current value of the drunk mechanism * This function sets or returns the current drunk mechanism's value.
*/ * @param n - [optional] The value to set the drunk mechanism to
if (n !== undefined) { * @returns The current value of the drunk mechanism
app._drunk.position = n; */
return app._drunk.getPosition(); if (n !== undefined) {
} api._drunk.position = n;
app._drunk.step(); return api._drunk.getPosition();
return app._drunk.getPosition(); }
api._drunk.step();
return api._drunk.getPosition();
}; };
export const drunk_max = (app: any) => (max: number): void => { export const drunk_max = (api: UserAPI) => (max: number): void => {
/** /**
* Sets the maximum value of the drunk mechanism. * Sets the maximum value of the drunk mechanism.
* @param max - The maximum value of the drunk mechanism * @param max - The maximum value of the drunk mechanism
*/ */
app._drunk.max = max; api._drunk.max = max;
}; };
export const drunk_min = (app: any) => (min: number): void => { export const drunk_min = (api: UserAPI) => (min: number): void => {
/** /**
* Sets the minimum value of the drunk mechanism. * Sets the minimum value of the drunk mechanism.
* @param min - The minimum value of the drunk mechanism * @param min - The minimum value of the drunk mechanism
*/ */
app._drunk.min = min; api._drunk.min = min;
}; };
export const drunk_wrap = (app: any) => (wrap: boolean): void => { export const drunk_wrap = (api: UserAPI) => (wrap: boolean): void => {
/** /**
* Sets whether the drunk mechanism should wrap around * Sets whether the drunk mechanism should wrap around
* @param wrap - Whether the drunk mechanism should wrap around * @param wrap - Whether the drunk mechanism should wrap around
*/ */
app._drunk.toggleWrap(wrap); api._drunk.toggleWrap(wrap);
}; };

View File

@ -1,203 +1,206 @@
import { type Editor } from "../main";
import { UserAPI } from "./API";
const _euclidean_cycle = ( const _euclidean_cycle = (
pulses: number, pulses: number,
length: number, length: number,
rotate: number = 0, rotate: number = 0,
): boolean[] => { ): boolean[] => {
if (pulses == length) return Array.from({ length }, () => true); if (pulses == length) return Array.from({ length }, () => true);
function startsDescent(list: number[], i: number): boolean { function startsDescent(list: number[], i: number): boolean {
const length = list.length; const length = list.length;
const nextIndex = (i + 1) % length; const nextIndex = (i + 1) % length;
return list[i] > list[nextIndex] ? true : false; 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;
} }
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> => { export const fullseq = () => (sequence: string, duration: number): boolean | Array<boolean> => {
if (sequence.split("").every((c) => c === "x" || c === "o")) { if (sequence.split("").every((c) => c === "x" || c === "o")) {
return [...sequence].map((c) => c === "x").beat(duration); return [...sequence].map((c) => c === "x").beat(duration);
} else { } else {
return false; return false;
} }
}; };
export const seq = (app: any) => (expr: string, duration: number = 0.5): boolean => { export const seq = (app: any) => (expr: string, duration: number = 0.5): boolean => {
let len = expr.length * duration; let len = expr.length * duration;
let output: number[] = []; let output: number[] = [];
for (let i = 1; i <= len + 1; i += duration) { for (let i = 1; i <= len + 1; i += duration) {
output.push(Math.floor(i * 10) / 10); output.push(Math.floor(i * 10) / 10);
} }
output.pop(); output.pop();
output = output.filter((_, idx) => { output = output.filter((_, idx) => {
const exprIdx = idx % expr.length; const exprIdx = idx % expr.length;
return expr[exprIdx] === "x"; return expr[exprIdx] === "x";
}); });
return oncount(app)(output, len); return oncount(app)(output, len);
}; };
export const beat = (app: any) => (n: number | number[] = 1, nudge: number = 0): boolean => { export const beat = (app: Editor) => (n: number | number[] = 1, nudge: number = 0): boolean => {
const nArray = Array.isArray(n) ? n : [n]; const nArray = Array.isArray(n) ? n : [n];
const results: boolean[] = nArray.map( const results: boolean[] = nArray.map(
(value) => (value) =>
(app.clock.pulses_since_origin - Math.floor(nudge * app.clock.ppqn)) % (app.clock.pulses_since_origin - Math.floor(nudge * app.clock.ppqn)) %
Math.floor(value * app.clock.ppqn) === 0, Math.floor(value * app.clock.ppqn) === 0,
); );
return results.some((value) => value === true); return results.some((value) => value === true);
}; };
export const bar = (app: any) => (n: number | number[] = 1, nudge: number = 0): boolean => { export const bar = (app: Editor) => (n: number | number[] = 1, nudge: number = 0): boolean => {
const nArray = Array.isArray(n) ? n : [n]; const nArray = Array.isArray(n) ? n : [n];
const barLength = app.clock.time_signature[1] * app.clock.ppqn; const barLength = app.clock.time_signature[1] * app.clock.ppqn;
const nudgeInPulses = Math.floor(nudge * barLength); const nudgeInPulses = Math.floor(nudge * barLength);
const results: boolean[] = nArray.map( const results: boolean[] = nArray.map(
(value) => (value) =>
(app.clock.pulses_since_origin - nudgeInPulses) % (app.clock.pulses_since_origin - nudgeInPulses) %
Math.floor(value * barLength) === 0, Math.floor(value * barLength) === 0,
); );
return results.some((value) => value === true); return results.some((value) => value === true);
}; };
export const pulse = (app: any) => (n: number | number[] = 1, nudge: number = 0): boolean => { export const pulse = (app: Editor) => (n: number | number[] = 1, nudge: number = 0): boolean => {
const nArray = Array.isArray(n) ? n : [n]; const nArray = Array.isArray(n) ? n : [n];
const results: boolean[] = nArray.map( const results: boolean[] = nArray.map(
(value) => (app.clock.pulses_since_origin - nudge) % value === 0, (value) => (app.clock.pulses_since_origin - nudge) % value === 0,
); );
return results.some((value) => value === true); return results.some((value) => value === true);
}; };
export const tick = (app: any) => (tick: number | number[], offset: number = 0): boolean => { export const tick = (app: Editor) => (tick: number | number[], offset: number = 0): boolean => {
const nArray = Array.isArray(tick) ? tick : [tick]; const nArray = Array.isArray(tick) ? tick : [tick];
const results: boolean[] = nArray.map( const results: boolean[] = nArray.map(
(value) => app.clock.time_position.pulse === value + offset, (value) => app.clock.time_position.pulse === value + offset,
); );
return results.some((value) => value === true); return results.some((value) => value === true);
}; };
export const dur = (app: any) => (n: number | number[]): boolean => { export const dur = (app: Editor) => (n: number | number[]): boolean => {
let nums: number[] = Array.isArray(n) ? n : [n]; let nums: number[] = Array.isArray(n) ? n : [n];
return beat(app)(nums.dur(...nums)); return beat(app)(nums.dur(...nums));
}; };
export const flip = (app: any) => (chunk: number, ratio: number = 50): boolean => { export const flip = (app: Editor) => (chunk: number, ratio: number = 50): boolean => {
let realChunk = chunk * 2; let realChunk = chunk * 2;
const time_pos = app.clock.pulses_since_origin; const time_pos = app.clock.pulses_since_origin;
const full_chunk = Math.floor(realChunk * app.clock.ppqn); const full_chunk = Math.floor(realChunk * app.clock.ppqn);
const threshold = Math.floor((ratio / 100) * full_chunk); const threshold = Math.floor((ratio / 100) * full_chunk);
const pos_within_chunk = time_pos % full_chunk; const pos_within_chunk = time_pos % full_chunk;
return pos_within_chunk < threshold; return pos_within_chunk < threshold;
}; };
export const flipbar = (app: any) => (chunk: number = 1): boolean => { export const flipbar = (app: Editor) => (chunk: number = 1): boolean => {
let realFlip = chunk; let realFlip = chunk;
const time_pos = app.clock.time_position.bar; const time_pos = app.clock.time_position.bar;
const current_chunk = Math.floor(time_pos / realFlip); const current_chunk = Math.floor(time_pos / realFlip);
return current_chunk % 2 === 0; return current_chunk % 2 === 0;
}; };
export const onbar = (app: any) => ( export const onbar = (app: Editor) => (
bars: number[] | number, bars: number[] | number,
n: number = app.clock.time_signature[0], n: number = app.clock.time_signature[0],
): boolean => { ): boolean => {
let current_bar = (app.clock.time_position.bar % n) + 1; let current_bar = (app.clock.time_position.bar % n) + 1;
return typeof bars === "number" return typeof bars === "number"
? bars === current_bar ? bars === current_bar
: bars.some((b) => b === current_bar); : bars.some((b) => b === current_bar);
}; };
export const onbeat = (app: any) => (...beat: number[]): boolean => { export const onbeat = (api: UserAPI) => (...beat: number[]): boolean => {
let final_pulses: boolean[] = []; let final_pulses: boolean[] = [];
beat.forEach((b) => { beat.forEach((b) => {
let beatNumber = b % app.nominator() || app.nominator(); let beatNumber = b % api.nominator() || api.nominator();
let integral_part = Math.floor(beatNumber); let integral_part = Math.floor(beatNumber);
integral_part = integral_part === 0 ? app.nominator() : integral_part; integral_part = integral_part === 0 ? api.nominator() : integral_part;
let decimal_part = Math.floor((beatNumber - integral_part) * app.clock.ppqn + 1); let decimal_part = Math.floor((beatNumber - integral_part) * api.app.clock.ppqn + 1);
if (decimal_part <= 0) if (decimal_part <= 0)
decimal_part += app.clock.ppqn * app.nominator(); decimal_part += api.app.clock.ppqn * api.nominator();
final_pulses.push( final_pulses.push(
integral_part === app.cbeat() && app.cpulse() === decimal_part, integral_part === api.cbeat() && api.cpulse() === decimal_part,
); );
}); });
return final_pulses.some((p) => p === true); return final_pulses.some((p) => p === true);
}; };
export const oncount = (app: any) => (beats: number[] | number, count: number): boolean => { export const oncount = (app: Editor) => (beats: number[] | number, count: number): boolean => {
if (typeof beats === "number") beats = [beats]; if (typeof beats === "number") beats = [beats];
const origin = app.clock.pulses_since_origin; const origin = app.clock.pulses_since_origin;
let final_pulses: boolean[] = []; let final_pulses: boolean[] = [];
beats.forEach((b) => { beats.forEach((b) => {
b = b < 1 ? 0 : b - 1; b = b < 1 ? 0 : b - 1;
const beatInTicks = Math.ceil(b * app.clock.ppqn); const beatInTicks = Math.ceil(b * app.clock.ppqn);
const meterPosition = origin % (app.clock.ppqn * count); const meterPosition = origin % (app.clock.ppqn * count);
final_pulses.push(meterPosition === beatInTicks); final_pulses.push(meterPosition === beatInTicks);
}); });
return final_pulses.some((p) => p === true); return final_pulses.some((p) => p === true);
}; };
export const oneuclid = (app: any) => (pulses: number, length: number, rotate: number = 0): boolean => { export const oneuclid = (app: Editor) => (pulses: number, length: number, rotate: number = 0): boolean => {
const cycle = _euclidean_cycle(pulses, length, rotate); const cycle = _euclidean_cycle(pulses, length, rotate);
const beats = cycle.reduce((acc: number[], x: boolean, i: number) => { const beats = cycle.reduce((acc: number[], x: boolean, i: number) => {
if (x) acc.push(i + 1); if (x) acc.push(i + 1);
return acc; return acc;
}, []); }, []);
return oncount(app)(beats, length); return oncount(app)(beats, length);
}; };
export const euclid = () => (iterator: number, pulses: number, length: number, rotate: number = 0): boolean => { export const euclid = () => (iterator: number, pulses: number, length: number, rotate: number = 0): boolean => {
/** /**
* Returns a Euclidean cycle of size length, with n pulses, rotated or not. * Returns a Euclidean cycle of size length, with n pulses, rotated or not.
*/ */
return _euclidean_cycle(pulses, length, rotate)[iterator % length]; return _euclidean_cycle(pulses, length, rotate)[iterator % length];
}; };
export const ec = euclid; export const ec = euclid;
export const rhythm = (app: any) => (div: number, pulses: number, length: number, rotate: number = 0): boolean => { export const rhythm = (app: Editor) => (div: number, pulses: number, length: number, rotate: number = 0): boolean => {
/** /**
* Returns a rhythm based on Euclidean cycle. * Returns a rhythm based on Euclidean cycle.
*/ */
return ( return (
beat(app)(div) && _euclidean_cycle(pulses, length, rotate).beat(div) beat(app)(div) && _euclidean_cycle(pulses, length, rotate).beat(div)
); );
}; };
export const ry = rhythm; export const ry = rhythm;
export const nrhythm = (app: any) => (div: number, pulses: number, length: number, rotate: number = 0): boolean => { export const nrhythm = (app: Editor) => (div: number, pulses: number, length: number, rotate: number = 0): boolean => {
/** /**
* Returns a negated rhythm based on Euclidean cycle. * Returns a negated rhythm based on Euclidean cycle.
*/ */
let rhythm = _euclidean_cycle(pulses, length, rotate).map((n: any) => !n); let rhythm = _euclidean_cycle(pulses, length, rotate).map((n: any) => !n);
return ( return (
beat(app)(div) && rhythm.beat(div) beat(app)(div) && rhythm.beat(div)
); );
}; };
export const nry = nrhythm; export const nry = nrhythm;
export const bin = () => (iterator: number, n: number): boolean => { export const bin = () => (iterator: number, n: number): boolean => {
/** /**
* Returns a binary cycle of size n. * Returns a binary cycle of size n.
*/ */
let convert: string = n.toString(2); let convert: string = n.toString(2);
let tobin: boolean[] = convert.split("").map((x: string) => x === "1"); let tobin: boolean[] = convert.split("").map((x: string) => x === "1");
return tobin[iterator % tobin.length]; return tobin[iterator % tobin.length];
}; };
export const binrhythm = (app: any) => (div: number, n: number): boolean => { export const binrhythm = (app: Editor) => (div: number, n: number): boolean => {
/** /**
* Returns a binary rhythm based on division and binary cycle. * Returns a binary rhythm based on division and binary cycle.
*/ */
let convert: string = n.toString(2); let convert: string = n.toString(2);
let tobin: boolean[] = convert.split("").map((x: string) => x === "1"); let tobin: boolean[] = convert.split("").map((x: string) => x === "1");
return beat(app)(div) && tobin.beat(div); return beat(app)(div) && tobin.beat(div);
}; };
export const bry = binrhythm; export const bry = binrhythm;

View File

@ -1,65 +1,68 @@
import { Editor } from "../main";
import { UserAPI } from "./API";
export const line = () => (start: number, end: number, step: number = 1): number[] => { export const line = () => (start: number, end: number, step: number = 1): number[] => {
const countPlaces = (num: number) => { const countPlaces = (num: number) => {
var text = num.toString(); var text = num.toString();
var index = text.indexOf("."); var index = text.indexOf(".");
return index == -1 ? 0 : (text.length - index - 1); return index == -1 ? 0 : (text.length - index - 1);
}; };
const result: number[] = []; const result: number[] = [];
if ((end > start && step > 0) || (end < start && step < 0)) { if ((end > start && step > 0) || (end < start && step < 0)) {
for (let value = start; value <= end; value += step) { for (let value = start; value <= end; value += step) {
result.push(value); 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.");
} }
} 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; return result;
}; };
export const sine = (app: any) => (freq: number = 1, phase: number = 0): number => { export const sine = (app: Editor) => (freq: number = 1, phase: number = 0): number => {
return Math.sin(2 * Math.PI * freq * (app.clock.ctx.currentTime - phase)); return Math.sin(2 * Math.PI * freq * (app.clock.ctx.currentTime - phase));
}; };
export const usine = (app: any) => (freq: number = 1, phase: number = 0): number => { export const usine = (app: Editor) => (freq: number = 1, phase: number = 0): number => {
return ((sine(app)(freq, phase) + 1) / 2); return ((sine(app)(freq, phase) + 1) / 2);
}; };
export const saw = (app: any) => (freq: number = 1, phase: number = 0): number => { export const saw = (app: Editor) => (freq: number = 1, phase: number = 0): number => {
return (((app.clock.ctx.currentTime * freq + phase) % 1) * 2 - 1); return (((app.clock.ctx.currentTime * freq + phase) % 1) * 2 - 1);
}; };
export const usaw = (app: any) => (freq: number = 1, phase: number = 0): number => { export const usaw = (app: Editor) => (freq: number = 1, phase: number = 0): number => {
return ((saw(app)(freq, phase) + 1) / 2); return ((saw(app)(freq, phase) + 1) / 2);
}; };
export const triangle = (app: any) => (freq: number = 1, phase: number = 0): number => { export const triangle = (app: Editor) => (freq: number = 1, phase: number = 0): number => {
return (Math.abs(saw(app)(freq, phase)) * 2 - 1); return (Math.abs(saw(app)(freq, phase)) * 2 - 1);
}; };
export const utriangle = (app: any) => (freq: number = 1, phase: number = 0): number => { export const utriangle = (app: Editor) => (freq: number = 1, phase: number = 0): number => {
return ((triangle(app)(freq, phase) + 1) / 2); return ((triangle(app)(freq, phase) + 1) / 2);
}; };
export const square = () => (freq: number = 1, duty: number = 0.5): number => { export const square = (app: Editor) => (freq: number = 1, duty: number = 0.5): number => {
const period = 1 / freq; const period = 1 / freq;
const t = (Date.now() / 1000) % period; const t = (app.clock.ctx.currentTime % period);
return (t / period < duty ? 1 : -1); return (t / period < duty ? 1 : -1);
}; };
export const usquare = () => (freq: number = 1, duty: number = 0.5): number => { export const usquare = (app: Editor) => (freq: number = 1, duty: number = 0.5): number => {
return ((square()(freq, duty) + 1) / 2); return ((square(app)(freq, duty) + 1) / 2);
}; };
export const noise = (app: any) => (): number => { export const noise = (api: UserAPI) => (): number => {
return (app.randomGen() * 2 - 1); // Assuming randomGen() is defined in the app context return (api.randomGen() * 2 - 1); // Assuming randomGen() is defined in the app context
}; };
export const unoise = (app: any) => (): number => { export const unoise = (api: UserAPI) => (): number => {
return ((noise(app)() + 1) / 2); return ((noise(api)() + 1) / 2);
}; };

View File

@ -4,6 +4,8 @@ import {
MidiNoteEvent, MidiNoteEvent,
} from "../IO/MidiConnection"; } from "../IO/MidiConnection";
import { MidiEvent, MidiParams } from "../Classes/MidiEvent"; import { MidiEvent, MidiParams } from "../Classes/MidiEvent";
import { UserAPI } from './API';
import { Editor } from '../main';
interface ControlChange { interface ControlChange {
channel: number; channel: number;
@ -11,20 +13,19 @@ interface ControlChange {
value: number; value: number;
} }
export const midi_outputs = (api: UserAPI) => (): void => {
export const midi_outputs = (app: any) => (): void => { api._logMessage(api.MidiConnection.listMidiOutputs(), false);
app._logMessage(app.MidiConnection.listMidiOutputs(), false);
}; };
export const midi_output = (app: any) => (outputName: string): void => { export const midi_output = (api: UserAPI) => (outputName: string): void => {
if (!outputName) { if (!outputName) {
console.log(app.MidiConnection.getCurrentMidiPort()); console.log(api.MidiConnection.getCurrentMidiPort());
} else { } else {
app.MidiConnection.switchMidiOutput(outputName); api.MidiConnection.switchMidiOutput(outputName);
} }
}; };
export const midi = (app: any) => ( export const midi = (app: Editor) => (
value: number | number[] = 60, value: number | number[] = 60,
velocity?: number | number[], velocity?: number | number[],
channel?: number | number[], channel?: number | number[],
@ -34,164 +35,164 @@ export const midi = (app: any) => (
return new MidiEvent(event, app); return new MidiEvent(event, app);
}; };
export const sysex = (app: any) => (data: Array<number>): void => { export const sysex = (api: UserAPI) => (data: Array<number>): void => {
app.MidiConnection.sendSysExMessage(data); api.MidiConnection.sendSysExMessage(data);
}; };
export const pitch_bend = (app: any) => (value: number, channel: number): void => { export const pitch_bend = (api: UserAPI) => (value: number, channel: number): void => {
app.MidiConnection.sendPitchBend(value, channel); api.MidiConnection.sendPitchBend(value, channel);
}; };
export const program_change = (app: any) => (program: number, channel: number): void => { export const program_change = (api: UserAPI) => (program: number, channel: number): void => {
app.MidiConnection.sendProgramChange(program, channel); api.MidiConnection.sendProgramChange(program, channel);
}; };
export const midi_clock = (app: any) => (): void => { export const midi_clock = (api: UserAPI) => (): void => {
app.MidiConnection.sendMidiClock(); api.MidiConnection.sendMidiClock();
}; };
export const control_change = (app: any) => ({ export const control_change = (api: UserAPI) => ({
control = 20, control = 20,
value = 0, value = 0,
channel = 0, channel = 0,
}: ControlChange): void => { }: ControlChange): void => {
app.MidiConnection.sendMidiControlChange(control, value, channel); api.MidiConnection.sendMidiControlChange(control, value, channel);
}; };
export const cc = control_change; export const cc = control_change;
export const midi_panic = (app: any) => (): void => { export const midi_panic = (api: UserAPI) => (): void => {
app.MidiConnection.panic(); api.MidiConnection.panic();
}; };
export const active_note_events = (app: any) => ( export const active_note_events = (api: UserAPI) => (
channel?: number, channel?: number,
): MidiNoteEvent[] | undefined => { ): MidiNoteEvent[] | undefined => {
let events; let events;
if (channel) { if (channel) {
events = app.MidiConnection.activeNotesFromChannel(channel); events = api.MidiConnection.activeNotesFromChannel(channel);
} else { } else {
events = app.MidiConnection.activeNotes; events = api.MidiConnection.activeNotes;
} }
if (events.length > 0) return events; if (events.length > 0) return events;
else return undefined; else return undefined;
}; };
export const transmission = (app: any) => (): boolean => { export const transmission = (api: UserAPI) => (): boolean => {
return app.MidiConnection.activeNotes.length > 0; return api.MidiConnection.activeNotes.length > 0;
}; };
export const active_notes = (app: any) => (channel?: number): number[] | undefined => { export const active_notes = (api: UserAPI) => (channel?: number): number[] | undefined => {
const events = active_note_events(app)(channel); const events = active_note_events(api)(channel);
if (events && events.length > 0) return events.map((e) => e.note); if (events && events.length > 0) return events.map((e) => e.note);
else return undefined; else return undefined;
}; };
export const kill_active_notes = (app: any) => (): void => { export const kill_active_notes = (api: UserAPI) => (): void => {
app.MidiConnection.activeNotes = []; api.MidiConnection.activeNotes = [];
}; };
export const sticky_notes = (app: any) => (channel?: number): number[] | undefined => { export const sticky_notes = (api: UserAPI) => (channel?: number): number[] | undefined => {
let notes; let notes;
if (channel) notes = app.MidiConnection.stickyNotesFromChannel(channel); if (channel) notes = api.MidiConnection.stickyNotesFromChannel(channel);
else notes = app.MidiConnection.stickyNotes; else notes = api.MidiConnection.stickyNotes;
if (notes.length > 0) return notes.map((e: any) => e.note); if (notes.length > 0) return notes.map((e: any) => e.note);
else return undefined; else return undefined;
}; };
export const kill_sticky_notes = (app: any) => (): void => { export const kill_sticky_notes = (api: UserAPI) => (): void => {
app.MidiConnection.stickyNotes = []; api.MidiConnection.stickyNotes = [];
}; };
export const buffer = (app: any) => (channel?: number): boolean => { export const buffer = (api: UserAPI) => (channel?: number): boolean => {
if (channel) if (channel)
return ( return (
app.MidiConnection.findNoteFromBufferInChannel(channel) !== undefined api.MidiConnection.findNoteFromBufferInChannel(channel) !== undefined
); );
else return app.MidiConnection.noteInputBuffer.length > 0; else return api.MidiConnection.noteInputBuffer.length > 0;
}; };
export const buffer_event = (app: any) => (channel?: number): MidiNoteEvent | undefined => { export const buffer_event = (api: UserAPI) => (channel?: number): MidiNoteEvent | undefined => {
if (channel) if (channel)
return app.MidiConnection.findNoteFromBufferInChannel(channel); return api.MidiConnection.findNoteFromBufferInChannel(channel);
else return app.MidiConnection.noteInputBuffer.shift(); else return api.MidiConnection.noteInputBuffer.shift();
}; };
export const buffer_note = (app: any) => (channel?: number): number | undefined => { export const buffer_note = (api: UserAPI) => (channel?: number): number | undefined => {
const note = buffer_event(app)(channel); const note = buffer_event(api)(channel);
return note ? note.note : undefined; return note ? note.note : undefined;
}; };
export const last_note_event = (app: any) => (channel?: number): MidiNoteEvent | undefined => { export const last_note_event = (api: UserAPI) => (channel?: number): MidiNoteEvent | undefined => {
if (channel) return app.MidiConnection.lastNoteInChannel[channel]; if (channel) return api.MidiConnection.lastNoteInChannel[channel];
else return app.MidiConnection.lastNote; else return api.MidiConnection.lastNote;
}; };
export const last_note = (app: any) => (channel?: number): number => { export const last_note = (api: UserAPI) => (channel?: number): number => {
const note = last_note_event(app)(channel); const note = last_note_event(api)(channel);
return note ? note.note : 60; return note ? note.note : 60;
}; };
export const ccIn = (app: any) => (control: number, channel?: number): number => { export const ccIn = (api: UserAPI) => (control: number, channel?: number): number => {
if (channel) { if (channel) {
if (app.MidiConnection.lastCCInChannel[channel]) { if (api.MidiConnection.lastCCInChannel[channel]) {
return app.MidiConnection.lastCCInChannel[channel][control]; return api.MidiConnection.lastCCInChannel[channel][control];
} else return 0; } else return 0;
} else return app.MidiConnection.lastCC[control] || 0; } else return api.MidiConnection.lastCC[control] || 0;
}; };
export const has_cc = (app: any) => (channel?: number): boolean => { export const has_cc = (api: UserAPI) => (channel?: number): boolean => {
if (channel) if (channel)
return ( return (
app.MidiConnection.findCCFromBufferInChannel(channel) !== undefined api.MidiConnection.findCCFromBufferInChannel(channel) !== undefined
); );
else return app.MidiConnection.ccInputBuffer.length > 0; else return api.MidiConnection.ccInputBuffer.length > 0;
}; };
export const buffer_cc = (app: any) => (channel?: number): MidiCCEvent | undefined => { export const buffer_cc = (api: UserAPI) => (channel?: number): MidiCCEvent | undefined => {
if (channel) return app.MidiConnection.findCCFromBufferInChannel(channel); if (channel) return api.MidiConnection.findCCFromBufferInChannel(channel);
else return app.MidiConnection.ccInputBuffer.shift(); else return api.MidiConnection.ccInputBuffer.shift();
}; };
export const show_scale = (app: any) => ( export const show_scale = (api: UserAPI) => (
root: number | string, root: number | string,
scale: number | string, scale: number | string,
channel: number = 0, channel: number = 0,
port: number | string = app.MidiConnection.currentOutputIndex || 0, port: number | string = api.MidiConnection.currentOutputIndex || 0,
soundOff: boolean = false, soundOff: boolean = false,
): void => { ): void => {
if (!app.scale_aid || scale !== app.scale_aid) { if (!api.scale_aid || scale !== api.scale_aid) {
hide_scale(app)(channel, port); hide_scale(api)(channel, port);
const scaleNotes = getAllScaleNotes(scale, root); const scaleNotes = getAllScaleNotes(scale, root);
scaleNotes.forEach((note) => { scaleNotes.forEach((note) => {
app.MidiConnection.sendMidiOn(note, channel, 1, port); api.MidiConnection.sendMidiOn(note, channel, 1, port);
if (soundOff) app.MidiConnection.sendAllSoundOff(channel, port); if (soundOff) api.MidiConnection.sendAllSoundOff(channel, port);
}); });
app.scale_aid = scale; api.scale_aid = scale;
} }
}; };
export const hide_scale = (app: any) => ( export const hide_scale = (api: UserAPI) => (
channel: number = 0, channel: number = 0,
port: number | string = app.MidiConnection.currentOutputIndex || 0, port: number | string = api.MidiConnection.currentOutputIndex || 0,
): void => { ): void => {
const allNotes = Array.from(Array(128).keys()); const allNotes = Array.from(Array(128).keys());
allNotes.forEach((note) => { allNotes.forEach((note) => {
app.MidiConnection.sendMidiOff(note, channel, port); api.MidiConnection.sendMidiOff(note, channel, port);
}); });
app.scale_aid = undefined; api.scale_aid = undefined;
}; };
export const midi_notes_off = (app: any) => ( export const midi_notes_off = (api: UserAPI) => (
channel: number = 0, channel: number = 0,
port: number | string = app.MidiConnection.currentOutputIndex || 0, port: number | string = api.MidiConnection.currentOutputIndex || 0,
): void => { ): void => {
app.MidiConnection.sendAllNotesOff(channel, port); api.MidiConnection.sendAllNotesOff(channel, port);
}; };
export const midi_sound_off = (app: any) => ( export const midi_sound_off = (api: UserAPI) => (
channel: number = 0, channel: number = 0,
port: number | string = app.MidiConnection.currentOutputIndex || 0, port: number | string = api.MidiConnection.currentOutputIndex || 0,
): void => { ): void => {
app.MidiConnection.sendAllSoundOff(channel, port); api.MidiConnection.sendAllSoundOff(channel, port);
}; };

View File

@ -1,33 +1,35 @@
import { Editor } from "../main";
// mouse.ts // mouse.ts
export const onmousemove = (app: any) => (e: MouseEvent): void => { export const onmousemove = (app: Editor) => (e: MouseEvent): void => {
app._mouseX = e.pageX; app._mouseX = e.pageX;
app._mouseY = e.pageY; app._mouseY = e.pageY;
}; };
export const mouseX = (app: any) => (): number => { export const mouseX = (app: Editor) => (): number => {
/** /**
* @returns The current x position of the mouse * @returns The current x position of the mouse
*/ */
return app._mouseX; return app._mouseX;
}; };
export const mouseY = (app: any) => (): number => { export const mouseY = (app: Editor) => (): number => {
/** /**
* @returns The current y position of the mouse * @returns The current y position of the mouse
*/ */
return app._mouseY; return app._mouseY;
}; };
export const noteX = (app: any) => (): number => { export const noteX = (app: Editor) => (): number => {
/** /**
* @returns The current x position scaled to 0-127 using screen width * @returns The current x position scaled to 0-127 using screen width
*/ */
return Math.floor((app._mouseX / document.body.clientWidth) * 127); return Math.floor((app._mouseX / document.body.clientWidth) * 127);
}; };
export const noteY = (app: any) => (): number => { export const noteY = (app: Editor) => (): number => {
/** /**
* @returns The current y position scaled to 0-127 using screen height * @returns The current y position scaled to 0-127 using screen height
*/ */
return Math.floor((app._mouseY / document.body.clientHeight) * 127); return Math.floor((app._mouseY / document.body.clientHeight) * 127);
}; };

View File

@ -1,27 +1,28 @@
import { sendToServer, type OSCMessage } from "../IO/OSC"; import { sendToServer, type OSCMessage } from "../IO/OSC";
import { Editor } from "../main";
import { oscMessages } from "../IO/OSC";
export const osc = (app: any) => (address: string, port: number, ...args: any[]): void => { export const osc = (app: Editor) => (address: string, port: number, ...args: any[]): void => {
/** /**
* Sends an OSC message to the server. * Sends an OSC message to the server.
*/ */
sendToServer({ sendToServer({
address: address, address: address,
port: port, port: port,
args: args, args: args,
timetag: Math.round(Date.now() + (app.clock.nudge - app.clock.deviation)), timetag: Math.round(Date.now() + (app.clock.nudge - app.clock.deviation)),
} as OSCMessage); } as OSCMessage);
}; };
export const getOSC = (app: any) => (address?: string): any[] => { export const getOSC = () => (address?: string): any[] => {
/** /**
* Retrieves incoming OSC messages. Filters by address if provided. * Retrieves incoming OSC messages. Filters by address if provided.
*/ */
let oscMessages = app.oscMessages; // Assuming `oscMessages` is stored in `app` if (address) {
if (address) { let messages = oscMessages.filter((msg: { address: string; }) => msg.address === address);
let messages = oscMessages.filter((msg: { address: string; }) => msg.address === address); messages = messages.map((msg: { data: any; }) => msg.data);
messages = messages.map((msg: { data: any; }) => msg.data); return messages;
return messages; } else {
} else { return oscMessages;
return oscMessages; }
}
}; };

View File

@ -1,53 +1,53 @@
// Probability.ts import { type UserAPI } from "./API";
export const prob = (app: any) => (p: number): boolean => { export const prob = (api: UserAPI) => (p: number): boolean => {
return app.randomGen() * 100 < p; return api.randomGen() * 100 < p;
}; };
export const toss = (app: any) => (): boolean => { export const toss = (api: UserAPI) => (): boolean => {
return app.randomGen() > 0.5; return api.randomGen() > 0.5;
}; };
export const odds = (app: any) => (n: number, beats: number = 1): boolean => { export const odds = (api: UserAPI) => (n: number, beats: number = 1): boolean => {
return app.randomGen() < (n * app.ppqn()) / (app.ppqn() * beats); return api.randomGen() < (n * api.ppqn()) / (api.ppqn() * beats);
}; };
export const never = () => (): boolean => { export const never = () => (): boolean => {
return false; return false;
}; };
export const almostNever = (app: any) => (beats: number = 1): boolean => { export const almostNever = (api: UserAPI) => (beats: number = 1): boolean => {
return app.randomGen() < (0.025 * app.ppqn()) / (app.ppqn() * beats); return api.randomGen() < (0.025 * api.ppqn()) / (api.ppqn() * beats);
}; };
export const rarely = (app: any) => (beats: number = 1): boolean => { export const rarely = (api: UserAPI) => (beats: number = 1): boolean => {
return app.randomGen() < (0.1 * app.ppqn()) / (app.ppqn() * beats); return api.randomGen() < (0.1 * api.ppqn()) / (api.ppqn() * beats);
}; };
export const scarcely = (app: any) => (beats: number = 1): boolean => { export const scarcely = (api: UserAPI) => (beats: number = 1): boolean => {
return app.randomGen() < (0.25 * app.ppqn()) / (app.ppqn() * beats); return api.randomGen() < (0.25 * api.ppqn()) / (api.ppqn() * beats);
}; };
export const sometimes = (app: any) => (beats: number = 1): boolean => { export const sometimes = (api: UserAPI) => (beats: number = 1): boolean => {
return app.randomGen() < (0.5 * app.ppqn()) / (app.ppqn() * beats); return api.randomGen() < (0.5 * api.ppqn()) / (api.ppqn() * beats);
}; };
export const often = (app: any) => (beats: number = 1): boolean => { export const often = (api: UserAPI) => (beats: number = 1): boolean => {
return app.randomGen() < (0.75 * app.ppqn()) / (app.ppqn() * beats); return api.randomGen() < (0.75 * api.ppqn()) / (api.ppqn() * beats);
}; };
export const frequently = (app: any) => (beats: number = 1): boolean => { export const frequently = (api: UserAPI) => (beats: number = 1): boolean => {
return app.randomGen() < (0.9 * app.ppqn()) / (app.ppqn() * beats); return api.randomGen() < (0.9 * api.ppqn()) / (api.ppqn() * beats);
}; };
export const almostAlways = (app: any) => (beats: number = 1): boolean => { export const almostAlways = (api: UserAPI) => (beats: number = 1): boolean => {
return app.randomGen() < (0.985 * app.ppqn()) / (app.ppqn() * beats); return api.randomGen() < (0.985 * api.ppqn()) / (api.ppqn() * beats);
}; };
export const always = () => (): boolean => { export const always = () => (): boolean => {
return true; return true;
}; };
export const dice = (app: any) => (sides: number): number => { export const dice = (api: UserAPI) => (sides: number): number => {
return Math.floor(app.randomGen() * sides) + 1; return Math.floor(api.randomGen() * sides) + 1;
}; };

View File

@ -1,33 +1,34 @@
import { seededRandom } from "zifferjs"; import { seededRandom } from "zifferjs";
import { UserAPI } from "./API";
export const randI = (app: any) => (min: number, max: number): number => { export const randI = (api: UserAPI) => (min: number, max: number): number => {
return Math.floor(app.randomGen() * (max - min + 1)) + min; return Math.floor(api.randomGen() * (max - min + 1)) + min;
}; };
export const rand = (app: any) => (min: number, max: number): number => { export const rand = (api: UserAPI) => (min: number, max: number): number => {
return app.randomGen() * (max - min) + min; return api.randomGen() * (max - min) + min;
}; };
export const seed = (app: any) => (seed: string | number): void => { export const seed = (api: UserAPI) => (seed: string | number): void => {
if (typeof seed === "number") seed = seed.toString(); if (typeof seed === "number") seed = seed.toString();
if (app.currentSeed !== seed) { if (api.currentSeed !== seed) {
app.currentSeed = seed; api.currentSeed = seed;
app.randomGen = seededRandom(seed); api.randomGen = seededRandom(seed);
} }
}; };
export const localSeededRandom = (app: any) => (seed: string | number): Function => { export const localSeededRandom = (api: UserAPI) => (seed: string | number): Function => {
if (typeof seed === "number") seed = seed.toString(); if (typeof seed === "number") seed = seed.toString();
if (app.localSeeds.has(seed)) return app.localSeeds.get(seed) as Function; if (api.localSeeds.has(seed)) return api.localSeeds.get(seed) as Function;
const newSeededRandom = seededRandom(seed); const newSeededRandom = seededRandom(seed);
app.localSeeds.set(seed, newSeededRandom); api.localSeeds.set(seed, newSeededRandom);
return newSeededRandom; return newSeededRandom;
}; };
export const clearLocalSeed = (app: any) => (seed: string | number | undefined = undefined): void => { export const clearLocalSeed = (api: UserAPI) => (seed: string | number | undefined = undefined): void => {
if (seed) { if (seed) {
app.localSeeds.delete(seed.toString()); api.localSeeds.delete(seed.toString());
} else { } else {
app.localSeeds.clear(); api.localSeeds.clear();
} }
}; };

View File

@ -1,63 +1,64 @@
import { tryEvaluate } from "../Evaluator"; import { tryEvaluate } from "../Evaluator";
import { blinkScript } from "../DOM/Visuals/Blinkers"; import { blinkScript } from "../DOM/Visuals/Blinkers";
import { template_universes } from "../Editor/FileManagement"; import { template_universes } from "../Editor/FileManagement";
import { Editor } from "../main";
export const script = (app: any) => (...args: number[]): void => { export const script = (app: Editor) => (...args: number[]): void => {
args.forEach((arg) => { args.forEach((arg) => {
if (arg >= 1 && arg <= 9) { if (arg >= 1 && arg <= 9) {
blinkScript(app, "local", arg); blinkScript(app, "local", arg);
tryEvaluate( tryEvaluate(
app, app,
app.universes[app.selected_universe].locals[arg], app.universes[app.selected_universe].locals[arg],
); );
} }
}); });
}; };
export const s = script; export const s = script;
export const delete_script = (app: any) => (script: number): void => { export const delete_script = (app: Editor) => (script: number): void => {
app.universes[app.selected_universe].locals[script] = { app.universes[app.selected_universe].locals[script] = {
candidate: "", candidate: "",
committed: "", committed: "",
evaluations: 0, evaluations: 0,
}; };
}; };
export const copy_script = (app: any) => (from: number, to: number): void => { export const copy_script = (app: Editor) => (from: number, to: number): void => {
app.universes[app.selected_universe].locals[to] = { app.universes[app.selected_universe].locals[to] = {
...app.universes[app.selected_universe].locals[from], ...app.universes[app.selected_universe].locals[from],
}; };
}; };
export const copy_universe = (app: any) => (from: string, to: string): void => { export const copy_universe = (app: Editor) => (from: string, to: string): void => {
app.universes[to] = { app.universes[to] = {
...app.universes[from], ...app.universes[from],
}; };
}; };
export const delete_universe = (app: any) => (universe: string): void => { export const delete_universe = (app: Editor) => (universe: string): void => {
if (app.selected_universe === universe) { if (app.selected_universe === universe) {
app.selected_universe = "Default"; app.selected_universe = "Default";
} }
delete app.universes[universe]; delete app.universes[universe];
app.settings.saveApplicationToLocalStorage(
app.universes,
app.settings,
);
app.updateKnownUniversesView();
};
export const big_bang = (app: Editor) => (): void => {
if (confirm("Are you sure you want to delete all universes?")) {
app.universes = {
...template_universes, // Assuming template_universes is defined elsewhere
};
app.settings.saveApplicationToLocalStorage( app.settings.saveApplicationToLocalStorage(
app.universes, app.universes,
app.settings, app.settings,
); );
app.updateKnownUniversesView(); }
}; app.selected_universe = "Default";
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();
}; };

View File

@ -1,43 +1,44 @@
import { SoundEvent } from "../Classes/SoundEvent"; import { SoundEvent } from "../Classes/SoundEvent";
import { SkipEvent } from "../Classes/SkipEvent"; import { SkipEvent } from "../Classes/SkipEvent";
import { Editor } from "../main";
export const sound = (app: any) => (sound: string | string[] | null | undefined) => { export const sound = (app: Editor) => (sound: string | string[] | null | undefined) => {
/** /**
* Creates a sound event if a sound is specified, otherwise returns a skip event. * 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. * @param sound - The sound identifier or array of identifiers to play.
* @returns SoundEvent if sound is defined, otherwise SkipEvent. * @returns SoundEvent if sound is defined, otherwise SkipEvent.
*/ */
if (sound) return new SoundEvent(sound, app); if (sound) return new SoundEvent(sound, app);
else return new SkipEvent(); else return new SkipEvent();
}; };
export const snd = sound; export const snd = sound;
export const speak = () => (text: string, lang: string = "en-US", voiceIndex: number = 0, rate: number = 1, pitch: number = 1): void => { export const speak = () => (text: string, lang: string = "en-US", voiceIndex: number = 0, rate: number = 1, pitch: number = 1): void => {
/** /**
* Speaks the given text using the browser's speech synthesis API. * Speaks the given text using the browser's speech synthesis API.
* @param text - The text to speak. * @param text - The text to speak.
* @param lang - The language code (e.g., "en-US"). * @param lang - The language code (e.g., "en-US").
* @param voiceIndex - The index of the voice to use from the speechSynthesis voice list. * @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 rate - The rate at which to speak the text.
* @param pitch - The pitch at which to speak the text. * @param pitch - The pitch at which to speak the text.
*/ */
const msg = new SpeechSynthesisUtterance(text); const msg = new SpeechSynthesisUtterance(text);
msg.lang = lang; msg.lang = lang;
msg.rate = rate; msg.rate = rate;
msg.pitch = pitch; msg.pitch = pitch;
// Set the voice using a provided index // Set the voice using a provided index
const voices = window.speechSynthesis.getVoices(); const voices = window.speechSynthesis.getVoices();
msg.voice = voices[voiceIndex] || null; msg.voice = voices[voiceIndex] || null;
window.speechSynthesis.speak(msg); window.speechSynthesis.speak(msg);
msg.onend = () => { msg.onend = () => {
console.log("Finished speaking:", text); console.log("Finished speaking:", text);
}; };
msg.onerror = (event) => { msg.onerror = (event) => {
console.error("Speech synthesis error:", event); console.error("Speech synthesis error:", event);
}; };
}; };

View File

@ -1,114 +1,117 @@
export const time = (app: any) => (): number => { import { type UserAPI } from "./API";
return app.audioContext.currentTime; import { type Editor } from "../main";
export const time = (api: UserAPI) => (): number => {
return api.app.audioContext.currentTime;
}; };
export const play = (app: any) => (): void => { export const play = (api: UserAPI) => (): void => {
app.setButtonHighlighting("play", true); api.app.setButtonHighlighting("play", true);
app.MidiConnection.sendStartMessage(); api.MidiConnection.sendStartMessage();
app.clock.start(); api.app.clock.start();
}; };
export const pause = (app: any) => (): void => { export const pause = (api: UserAPI) => (): void => {
app.setButtonHighlighting("pause", true); api.app.setButtonHighlighting("pause", true);
app.clock.pause(); api.app.clock.pause();
}; };
export const stop = (app: any) => (): void => { export const stop = (api: UserAPI) => (): void => {
app.setButtonHighlighting("stop", true); api.app.setButtonHighlighting("stop", true);
app.clock.stop(); api.app.clock.stop();
}; };
export const silence = (app: any) => (): void => { export const silence = (api: UserAPI) => (): void => {
return stop(app)(); return stop(api)();
}; };
export const tempo = (app: any) => (n?: number): number => { export const tempo = (app: Editor) => (n?: number): number => {
/** /**
* Sets or returns the current bpm. * Sets or returns the current bpm.
*/ */
if (n === undefined) return app.clock.bpm; if (n === undefined) return app.clock.bpm;
if (n >= 1 && n <= 500) { if (n >= 1 && n <= 500) {
app.clock.bpm = n; app.clock.bpm = n;
} else { } else {
console.error("BPM out of acceptable range (1-500)."); console.error("BPM out of acceptable range (1-500).");
} }
return n; return n;
}; };
export const bpb = (app: any) => (n?: number): number => { export const bpb = (app: Editor) => (n?: number): number => {
/** /**
* Sets or returns the number of beats per bar. * Sets or returns the number of beats per bar.
*/ */
if (n === undefined) return app.clock.time_signature[0]; if (n === undefined) return app.clock.time_signature[0];
if (n >= 1) { if (n >= 1) {
app.clock.time_signature[0] = n; app.clock.time_signature[0] = n;
} else { } else {
console.error("Beats per bar must be at least 1."); console.error("Beats per bar must be at least 1.");
} }
return n; return n;
}; };
export const ppqn = (app: any) => (n?: number): number => { export const ppqn = (app: Editor) => (n?: number): number => {
/** /**
* Sets or returns the number of pulses per quarter note. * Sets or returns the number of pulses per quarter note.
*/ */
if (n === undefined) return app.clock.ppqn; if (n === undefined) return app.clock.ppqn;
if (n >= 1) { if (n >= 1) {
app.clock.ppqn = n; app.clock.ppqn = n;
} else { } else {
console.error("Pulses per quarter note must be at least 1."); console.error("Pulses per quarter note must be at least 1.");
} }
return n; return n;
}; };
export const time_signature = (app: any) => (numerator: number, denominator: number): void => { export const time_signature = (app: Editor) => (numerator: number, denominator: number): void => {
/** /**
* Sets the time signature. * Sets the time signature.
*/ */
if (numerator < 1 || denominator < 1) { if (numerator < 1 || denominator < 1) {
console.error("Time signature values must be at least 1."); console.error("Time signature values must be at least 1.");
} else { } else {
app.clock.time_signature = [numerator, denominator]; app.clock.time_signature = [numerator, denominator];
} }
}; };
export const cbar = (app: any) => (): number => { export const cbar = (app: Editor) => (): number => {
return app.clock.time_position.bar + 1; return app.clock.time_position.bar + 1;
}; };
export const ctick = (app: any) => (): number => { export const ctick = (app: Editor) => (): number => {
return app.clock.tick + 1; return app.clock.tick + 1;
}; };
export const cpulse = (app: any) => (): number => { export const cpulse = (app: Editor) => (): number => {
return app.clock.time_position.pulse + 1; return app.clock.time_position.pulse + 1;
}; };
export const cbeat = (app: any) => (): number => { export const cbeat = (app: Editor) => (): number => {
return app.clock.time_position.beat + 1; return app.clock.time_position.beat + 1;
}; };
export const ebeat = (app: any) => (): number => { export const ebeat = (app: Editor) => (): number => {
return app.clock.beats_since_origin + 1; return app.clock.beats_since_origin + 1;
}; };
export const epulse = (app: any) => (): number => { export const epulse = (app: Editor) => (): number => {
return app.clock.pulses_since_origin + 1; return app.clock.pulses_since_origin + 1;
}; };
export const nominator = (app: any) => (): number => { export const nominator = (app: Editor) => (): number => {
return app.clock.time_signature[0]; return app.clock.time_signature[0];
}; };
export const meter = (app: any) => (): number => { export const meter = (app: Editor) => (): number => {
return app.clock.time_signature[1]; return app.clock.time_signature[1];
}; };
export const denominator = meter; // Alias for meter export const denominator = meter;
export const pulsesForBar = (app: any) => (): number => { export const pulsesForBar = (app: Editor) => (): number => {
return (app.clock.bpm * app.clock.ppqn * nominator(app)()) / 60; return (app.clock.bpm * app.clock.ppqn * nominator(app)()) / 60;
}; };

View File

@ -1,16 +1,18 @@
export const warp = (app: any) => (n: number): void => { import { Editor } from "../main";
/**
* Time-warp the clock by using the tick you wish to jump to. export const warp = (app: Editor) => (n: number): void => {
*/ /**
app.clock.tick = n; * Time-warp the clock by using the tick you wish to jump to.
app.clock.time_position = app.clock.convertTicksToTimeposition(n); */
app.clock.tick = n;
app.clock.time_position = app.clock.convertTicksToTimeposition(n);
}; };
export const beat_warp = (app: any) => (beat: number): void => { export const beat_warp = (app: Editor) => (beat: number): void => {
/** /**
* Time-warp the clock by using the tick you wish to jump to. * Time-warp the clock by using the tick you wish to jump to.
*/ */
const ticks = beat * app.clock.ppqn; const ticks = beat * app.clock.ppqn;
app.clock.tick = ticks; app.clock.tick = ticks;
app.clock.time_position = app.clock.convertTicksToTimeposition(ticks); app.clock.time_position = app.clock.convertTicksToTimeposition(ticks);
}; };

View File

@ -1,73 +1,72 @@
import { InputOptions, Player } from "../Classes/ZPlayer"; import { InputOptions, Player } from "../Classes/ZPlayer";
import { UserAPI } from "./API";
import { generateCacheKey, removePatternFromCache } from "./Cache" import { generateCacheKey, removePatternFromCache } from "./Cache"
export const z = (api: UserAPI) => (input: string | Generator<number>, options: InputOptions = {}, id: number | string = ""): Player => {
const zid = "z" + id.toString();
const key = id === "" ? generateCacheKey()(input, options) : zid;
// ziffersFunctions.ts const validSyntax = typeof input === "string" && !api.invalidPatterns[input]
export const z = (app: any) => (input: string | Generator<number>, options: InputOptions = {}, id: number | string = ""): Player => {
const zid = "z" + id.toString();
const key = id === "" ? generateCacheKey()(input, options) : zid;
const validSyntax = typeof input === "string" && !app.invalidPatterns[input] let player;
let replace = false;
let player; if (api.patternCache.has(key)) {
let replace = false; player = api.patternCache.get(key) as Player;
if (app.patternCache.has(key)) { if (typeof input === "string" &&
player = app.patternCache.get(key) as Player; player.input !== input &&
(player.atTheBeginning() || api.forceEvaluator)) {
if (typeof input === "string" && replace = true;
player.input !== input &&
(player.atTheBeginning() || app.forceEvaluator)) {
replace = true;
}
} }
}
if ((typeof input !== "string" || validSyntax) && (!player || replace)) { if ((typeof input !== "string" || validSyntax) && (!player || replace)) {
if (typeof input === "string" && player && app.forceEvaluator) { if (typeof input === "string" && player && api.forceEvaluator) {
if (!player.updatePattern(input, options)) { if (!player.updatePattern(input, options)) {
app.logOnce(`Invalid syntax: ${input}`); api.logOnce(`Invalid syntax: ${input}`);
}; };
app.forceEvaluator = false; api.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 { } else {
throw new Error(`Invalid syntax: ${input}`); const newPlayer = player ? new Player(input, options, api.app, zid, player.nextEndTime()) : new Player(input, options, api.app, zid);
if (newPlayer.isValid()) {
player = newPlayer;
api.patternCache.set(key, player);
} else if (typeof input === "string") {
api.invalidPatterns[input] = true;
}
} }
}
if (player) {
if (player.atTheBeginning()) {
if (typeof input === "string" && !validSyntax) api.log(`Invalid syntax: ${input}`);
}
if (player.ziffers.generator && player.ziffers.generatorDone) {
removePatternFromCache(api)(key);
}
if (typeof id === "number") player.zid = zid;
player.updateLastCallTime();
if (id !== "" && zid !== "z0") {
// Sync named patterns to z0 by default
player.sync("z0", false);
}
return player;
} else {
throw new Error(`Invalid syntax: ${input}`);
}
}; };
// Generating numbered functions dynamically // Generating numbered functions dynamically
export const generateZFunctions = (app: any) => { export const generateZFunctions = (api: UserAPI) => {
const zFunctions: { [key: string]: (input: string, opts: InputOptions) => Player } = {}; const zFunctions: { [key: string]: (input: string, opts: InputOptions) => Player } = {};
for (let i = 0; i <= 16; i++) { for (let i = 0; i <= 16; i++) {
zFunctions[`z${i}`] = (input: string, opts: InputOptions = {}) => z(app)(input, opts, i); zFunctions[`z${i}`] = (input: string, opts: InputOptions = {}) => z(api)(input, opts, i);
} }
return zFunctions; return zFunctions;
}; };

View File

@ -126,7 +126,7 @@ beat([.25,.125].beat(2))::snd('arpy')
.cutoff(usine(.5) * 5000).resonance(20).gain(0.3) .cutoff(usine(.5) * 5000).resonance(20).gain(0.3)
.end(0.8).room(0.9).size(0.9).n(3).out(); .end(0.8).room(0.9).size(0.9).n(3).out();
beat(.5) :: snd('arpy').note( beat(.5) :: snd('arpy').note(
[30, 33, 35].repeatAll(4).beat(1) - [12,0].beat(0.5)).out() [30, 33, 35].repeat(4).beat(1) - [12,0].beat(0.5)).out()
// Comment me to stop warping! // Comment me to stop warping!
beat(1) :: beat_warp([2,4,5,10,11].pick()) beat(1) :: beat_warp([2,4,5,10,11].pick())
`, `,