Merging zn

This commit is contained in:
2023-08-13 20:45:09 +03:00
4 changed files with 991 additions and 946 deletions

View File

@ -62,6 +62,8 @@ To build a standalone browser application using [Tauri](https://tauri.app/), you
- [ ] Give more information the local context of execution of every script - [ ] Give more information the local context of execution of every script
- print/display the internal iterator used in each script - print/display the internal iterator used in each script
- animate code to show which line is currently being executed - animate code to show which line is currently being executed
- [ ] More rhythmic generators
- [ ] Rendering static files (MIDI, MOD, etc...)
## Scheduler ## Scheduler

View File

@ -1,14 +1,16 @@
import { Editor } from "./main"; import { Editor } from "./main";
import { scale } from './Scales'; import { scale } from "./Scales";
import { tryEvaluate } from "./Evaluator"; import { tryEvaluate } from "./Evaluator";
import { MidiConnection } from "./IO/MidiConnection"; import { MidiConnection } from "./IO/MidiConnection";
import { DrunkWalk } from './Utils/Drunk'; import { DrunkWalk } from "./Utils/Drunk";
import { Pitch, Chord, Rest, Event, Start, cachedStart } from "zifferjs"; import { Pitch, Chord, Rest, Event, Start, cachedStart } from "zifferjs";
import { import {
superdough, samples, superdough,
samples,
initAudioOnFirstClick, initAudioOnFirstClick,
registerSynthSounds registerSynthSounds,
} from 'superdough'; // @ts-ignore
} from "superdough";
/** /**
* We are overriding the includes method which is rather * We are overriding the includes method which is rather
@ -19,20 +21,17 @@ declare global {
in(value: T): boolean; in(value: T): boolean;
} }
} }
Array.prototype.in = function<T>(this: T[], value: T): boolean { Array.prototype.in = function <T>(this: T[], value: T): boolean {
return this.includes(value); return this.includes(value);
}; };
Promise.all([
const init = Promise.all([
initAudioOnFirstClick(), initAudioOnFirstClick(),
samples('github:tidalcycles/Dirt-Samples/master'), samples("github:tidalcycles/Dirt-Samples/master"),
samples('github:kindohm/expedition/tree/master/samples'),
registerSynthSounds(), registerSynthSounds(),
]); ]);
export class UserAPI { export class UserAPI {
/** /**
* The UserAPI class is the interface between the user's code and the backend. It provides * The UserAPI class is the interface between the user's code and the backend. It provides
* access to the AudioContext, to the MIDI Interface, to internal variables, mouse position, * access to the AudioContext, to the MIDI Interface, to internal variables, mouse position,
@ -40,14 +39,14 @@ export class UserAPI {
* function destined to the user should be placed here. * function destined to the user should be placed here.
*/ */
private variables: { [key: string]: any } = {} private variables: { [key: string]: any } = {};
private iterators: { [key: string]: any } = {} private iterators: { [key: string]: any } = {};
private _drunk: DrunkWalk = new DrunkWalk(-100, 100, false); private _drunk: DrunkWalk = new DrunkWalk(-100, 100, false);
MidiConnection: MidiConnection = new MidiConnection() MidiConnection: MidiConnection = new MidiConnection();
load: samples load: samples;
constructor (public app: Editor) { constructor(public app: Editor) {
this.load = samples("github:tidalcycles/Dirt-Samples/master"); this.load = samples("github:tidalcycles/Dirt-Samples/master");
} }
@ -59,7 +58,7 @@ export class UserAPI {
/** /**
* @returns The current time for the AudioContext * @returns The current time for the AudioContext
*/ */
return this.app.audioContext.currentTime return this.app.audioContext.currentTime;
} }
// ============================================================= // =============================================================
@ -70,23 +69,23 @@ export class UserAPI {
/** /**
* @returns The current x position of the mouse * @returns The current x position of the mouse
*/ */
return this.app._mouseX return this.app._mouseX;
} }
get mouseY(): number { get mouseY(): number {
/** /**
* @returns The current y position of the mouse * @returns The current y position of the mouse
*/ */
return this.app._mouseY return this.app._mouseY;
} }
// ============================================================= // =============================================================
// Utility functions // Utility functions
// ============================================================= // =============================================================
log = console.log log = console.log;
scale = scale scale = scale;
rate(rate: number): void { rate(rate: number): void {
rate = rate; rate = rate;
@ -102,14 +101,14 @@ export class UserAPI {
* @param args - The scripts to evaluate * @param args - The scripts to evaluate
* @returns The result of the evaluation * @returns The result of the evaluation
*/ */
args.forEach(arg => { args.forEach((arg) => {
tryEvaluate( tryEvaluate(
this.app, this.app,
this.app.universes[this.app.selected_universe].locals[arg], this.app.universes[this.app.selected_universe].locals[arg]
) );
}) });
} }
s = this.script s = this.script;
clearscript(script: number): void { clearscript(script: number): void {
/** /**
@ -118,10 +117,12 @@ export class UserAPI {
* @param script - The script to clear * @param script - The script to clear
*/ */
this.app.universes[this.app.selected_universe].locals[script] = { this.app.universes[this.app.selected_universe].locals[script] = {
candidate: '', committed: '', evaluations: 0 candidate: "",
committed: "",
evaluations: 0,
};
} }
} cs = this.clearscript;
cs = this.clearscript
copyscript(from: number, to: number): void { copyscript(from: number, to: number): void {
/** /**
@ -131,9 +132,9 @@ export class UserAPI {
* @param to - The script to copy to * @param to - The script to copy to
*/ */
this.app.universes[this.app.selected_universe].locals[to] = this.app.universes[this.app.selected_universe].locals[to] =
this.app.universes[this.app.selected_universe].locals[from] this.app.universes[this.app.selected_universe].locals[from];
} }
cps = this.copyscript cps = this.copyscript;
// ============================================================= // =============================================================
// MIDI related functions // MIDI related functions
@ -156,13 +157,13 @@ export class UserAPI {
* @param outputName - The name of the MIDI output to switch to * @param outputName - The name of the MIDI output to switch to
*/ */
if (!outputName) { if (!outputName) {
console.log(this.MidiConnection.getCurrentMidiPort()) console.log(this.MidiConnection.getCurrentMidiPort());
} else { } else {
this.MidiConnection.switchMidiOutput(outputName) this.MidiConnection.switchMidiOutput(outputName);
} }
} }
public note(note: number, options: {[key: string]: number} = {}): void { public note(note: number, options: { [key: string]: number } = {}): void {
/** /**
* Sends a MIDI note to the current MIDI output. * Sends a MIDI note to the current MIDI output.
* *
@ -172,12 +173,13 @@ export class UserAPI {
*/ */
const channel = options.channel ? options.channel : 0; const channel = options.channel ? options.channel : 0;
const velocity = options.velocity ? options.velocity : 100; const velocity = options.velocity ? options.velocity : 100;
const duration = options.duration ? options.duration: 0.5; const duration = options.duration ? options.duration : 0.5;
this.MidiConnection.sendMidiNote(note, channel, velocity, duration) this.MidiConnection.sendMidiNote(note, channel, velocity, duration);
} }
public zn(input: string, options: {[key: string]: string|number} = {}): Event { public zn(input: string,
let event = cachedStart(input, options); options: {[key: string]: string|number} = {}): Event {
const event = cachedStart(input, options);
if(event instanceof Start) { if(event instanceof Start) {
// do nothing for now ... // do nothing for now ...
} else { } else {
@ -210,7 +212,7 @@ export class UserAPI {
* *
* @param data - The sysex data to send * @param data - The sysex data to send
*/ */
this.MidiConnection.sendSysExMessage(data) this.MidiConnection.sendSysExMessage(data);
} }
public pitch_bend(value: number, channel: number): void { public pitch_bend(value: number, channel: number): void {
@ -222,7 +224,7 @@ export class UserAPI {
* *
* @returns The value of the pitch bend * @returns The value of the pitch bend
*/ */
this.MidiConnection.sendPitchBend(value, channel) this.MidiConnection.sendPitchBend(value, channel);
} }
public program_change(program: number, channel: number): void { public program_change(program: number, channel: number): void {
@ -232,14 +234,14 @@ export class UserAPI {
* @param program - The MIDI program to send * @param program - The MIDI program to send
* @param channel - The MIDI channel to send the program change on * @param channel - The MIDI channel to send the program change on
*/ */
this.MidiConnection.sendProgramChange(program, channel) this.MidiConnection.sendProgramChange(program, channel);
} }
public midi_clock(): void { public midi_clock(): void {
/** /**
* Sends a MIDI clock to the current MIDI output. * Sends a MIDI clock to the current MIDI output.
*/ */
this.MidiConnection.sendMidiClock() this.MidiConnection.sendMidiClock();
} }
public cc(control: number, value: number): void { public cc(control: number, value: number): void {
@ -249,14 +251,14 @@ export class UserAPI {
* @param control - The MIDI control to send * @param control - The MIDI control to send
* @param value - The value of the control * @param value - The value of the control
*/ */
this.MidiConnection.sendMidiControlChange(control, value) this.MidiConnection.sendMidiControlChange(control, value);
} }
public midi_panic(): void { public midi_panic(): void {
/** /**
* Sends a MIDI panic message to the current MIDI output. * Sends a MIDI panic message to the current MIDI output.
*/ */
this.MidiConnection.panic() this.MidiConnection.panic();
} }
// ============================================================= // =============================================================
@ -278,7 +280,7 @@ export class UserAPI {
this.iterators[name] = { this.iterators[name] = {
value: 0, value: 0,
step: step ?? 1, step: step ?? 1,
limit limit,
}; };
} else { } else {
// Check if limit has changed // Check if limit has changed
@ -298,8 +300,10 @@ export class UserAPI {
this.iterators[name].value += this.iterators[name].step; this.iterators[name].value += this.iterators[name].step;
// Check for limit overshoot // Check for limit overshoot
if (this.iterators[name].limit !== undefined && if (
this.iterators[name].value > this.iterators[name].limit) { this.iterators[name].limit !== undefined &&
this.iterators[name].value > this.iterators[name].limit
) {
this.iterators[name].value = 0; this.iterators[name].value = 0;
} }
} }
@ -307,7 +311,7 @@ export class UserAPI {
// Return current iterator value // Return current iterator value
return this.iterators[name].value; return this.iterators[name].value;
} }
$ = this.iterator $ = this.iterator;
// ============================================================= // =============================================================
// Drunk mechanism // Drunk mechanism
@ -373,14 +377,14 @@ export class UserAPI {
* @param b - [optional] The value to set the variable to * @param b - [optional] The value to set the variable to
* @returns The value of the variable * @returns The value of the variable
*/ */
if (typeof a === 'string' && b === undefined) { if (typeof a === "string" && b === undefined) {
return this.variables[a] return this.variables[a];
} else { } else {
this.variables[a] = b this.variables[a] = b;
return this.variables[a] return this.variables[a];
} }
} }
v = this.variable v = this.variable;
public delete_variable(name: string): void { public delete_variable(name: string): void {
/** /**
@ -388,9 +392,9 @@ export class UserAPI {
* *
* @param name - The name of the variable to delete * @param name - The name of the variable to delete
*/ */
delete this.variables[name] delete this.variables[name];
} }
dv = this.delete_variable dv = this.delete_variable;
public clear_variables(): void { public clear_variables(): void {
/** /**
@ -400,9 +404,9 @@ export class UserAPI {
* This function will delete all variables without warning. * This function will delete all variables without warning.
* Use with caution. * Use with caution.
*/ */
this.variables = {} this.variables = {};
} }
cv = this.clear_variables cv = this.clear_variables;
// ============================================================= // =============================================================
// Small algorithmic functions // Small algorithmic functions
@ -414,7 +418,7 @@ export class UserAPI {
* *
* @param array - The array of values to pick from * @param array - The array of values to pick from
*/ */
return array[Math.floor(Math.random() * array.length)] return array[Math.floor(Math.random() * array.length)];
} }
seqbeat<T>(...array: T[]): T { seqbeat<T>(...array: T[]): T {
@ -423,7 +427,7 @@ export class UserAPI {
* *
* @param array - The array of values to pick from * @param array - The array of values to pick from
*/ */
return array[this.app.clock.time_position.beat % array.length] return array[this.ebeat % array.length];
} }
mel<T>(iterator: number, array: T[]): T { mel<T>(iterator: number, array: T[]): T {
@ -433,7 +437,7 @@ export class UserAPI {
* @param iterator - The name of the iterator * @param iterator - The name of the iterator
* @param array - The array of values to pick from * @param array - The array of values to pick from
*/ */
return array[iterator % array.length] return array[iterator % array.length];
} }
seqbar<T>(...array: T[]): T { seqbar<T>(...array: T[]): T {
@ -442,7 +446,7 @@ export class UserAPI {
* *
* @param array - The array of values to pick from * @param array - The array of values to pick from
*/ */
return array[this.app.clock.time_position.bar % array.length] return array[(this.app.clock.time_position.bar + 1) % array.length];
} }
seqpulse<T>(...array: T[]): T { seqpulse<T>(...array: T[]): T {
@ -451,7 +455,7 @@ export class UserAPI {
* *
* @param array - The array of values to pick from * @param array - The array of values to pick from
*/ */
return array[this.app.clock.time_position.pulse % array.length] return array[this.app.clock.time_position.pulse % array.length];
} }
// ============================================================= // =============================================================
@ -466,7 +470,7 @@ export class UserAPI {
* @param max - The maximum value of the random number * @param max - The maximum value of the random number
* @returns A random integer between min and max * @returns A random integer between min and max
*/ */
return Math.floor(Math.random() * (max - min + 1)) + min return Math.floor(Math.random() * (max - min + 1)) + min;
} }
rand(min: number, max: number): number { rand(min: number, max: number): number {
@ -477,9 +481,10 @@ export class UserAPI {
* @param max - The maximum value of the random number * @param max - The maximum value of the random number
* @returns A random float between min and max * @returns A random float between min and max
*/ */
return Math.random() * (max - min) + min return Math.random() * (max - min) + min;
} }
rI = this.randI; r = this.rand rI = this.randI;
r = this.rand;
// ============================================================= // =============================================================
// Quantification functions // Quantification functions
@ -493,14 +498,18 @@ export class UserAPI {
* @param quantization - The array of values to quantize to * @param quantization - The array of values to quantize to
* @returns The closest value in the array to the given value * @returns The closest value in the array to the given value
*/ */
if (quantization.length === 0) { return value } if (quantization.length === 0) {
let closest = quantization[0] return value;
quantization.forEach(q => {
if (Math.abs(q - value) < Math.abs(closest - value)) { closest = q }
})
return closest
} }
quant = this.quantize let closest = quantization[0];
quantization.forEach((q) => {
if (Math.abs(q - value) < Math.abs(closest - value)) {
closest = q;
}
});
return closest;
}
quant = this.quantize;
public clamp(value: number, min: number, max: number): number { public clamp(value: number, min: number, max: number): number {
/** /**
@ -511,9 +520,9 @@ export class UserAPI {
* @param max - The maximum value of the clamped value * @param max - The maximum value of the clamped value
* @returns A value clamped between min and max * @returns A value clamped between min and max
*/ */
return Math.min(Math.max(value, min), max) return Math.min(Math.max(value, min), max);
} }
cmp = this.clamp cmp = this.clamp;
// ============================================================= // =============================================================
// Transport functions // Transport functions
@ -526,15 +535,13 @@ export class UserAPI {
* @param bpm - [optional] The bpm to set * @param bpm - [optional] The bpm to set
* @returns The current bpm * @returns The current bpm
*/ */
if (n === undefined) if (n === undefined) return this.app.clock.bpm;
return this.app.clock.bpm
if (n < 1 || n > 500) if (n < 1 || n > 500) console.log(`Setting bpm to ${n}`);
console.log(`Setting bpm to ${n}`) this.app.clock.bpm = n;
this.app.clock.bpm = n return n;
return n
} }
tempo = this.bpm tempo = this.bpm;
time_signature(numerator: number, denominator: number): void { time_signature(numerator: number, denominator: number): void {
/** /**
@ -544,20 +551,20 @@ export class UserAPI {
* @param denominator - The denominator of the time signature * @param denominator - The denominator of the time signature
* @returns The current time signature * @returns The current time signature
*/ */
this.app.clock.time_signature = [numerator, denominator] this.app.clock.time_signature = [numerator, denominator];
} }
// ============================================================= // =============================================================
// Probability functions // Probability functions
// ============================================================= // =============================================================
public almostNever():boolean { public almostNever(): boolean {
/** /**
* Returns true 10% of the time. * Returns true 10% of the time.
* *
* @returns True 10% of the time * @returns True 10% of the time
*/ */
return Math.random() > 0.9 return Math.random() > 0.9;
} }
public sometimes(): boolean { public sometimes(): boolean {
@ -566,16 +573,16 @@ export class UserAPI {
* *
* @returns True 50% of the time * @returns True 50% of the time
*/ */
return Math.random() > 0.5 return Math.random() > 0.5;
} }
public rarely():boolean { public rarely(): boolean {
/** /**
* Returns true 25% of the time. * Returns true 25% of the time.
* *
* @returns True 25% of the time * @returns True 25% of the time
*/ */
return Math.random() > 0.75 return Math.random() > 0.75;
} }
public often(): boolean { public often(): boolean {
@ -584,26 +591,26 @@ export class UserAPI {
* *
* @returns True 75% of the time * @returns True 75% of the time
*/ */
return Math.random() > 0.25 return Math.random() > 0.25;
} }
public almostAlways():boolean { public almostAlways(): boolean {
/** /**
* Returns true 90% of the time. * Returns true 90% of the time.
* *
* @returns True 90% of the time * @returns True 90% of the time
*/ */
return Math.random() > 0.1 return Math.random() > 0.1;
} }
public dice(sides: number):number { public dice(sides: number): number {
/** /**
* Returns the value of a dice roll with n sides. * Returns the value of a dice roll with n sides.
* *
* @param sides - The number of sides on the dice * @param sides - The number of sides on the dice
* @returns The value of a dice roll with n sides * @returns The value of a dice roll with n sides
*/ */
return Math.floor(Math.random() * sides) + 1 return Math.floor(Math.random() * sides) + 1;
} }
// ============================================================= // =============================================================
@ -616,7 +623,8 @@ export class UserAPI {
* *
* @returns The current iteration of global file * @returns The current iteration of global file
*/ */
return this.app.universes[this.app.selected_universe].global.evaluations as number; return this.app.universes[this.app.selected_universe].global
.evaluations as number;
} }
set i(n: number) { set i(n: number) {
@ -633,7 +641,7 @@ export class UserAPI {
* *
* @returns The current bar number * @returns The current bar number
*/ */
return this.app.clock.time_position.bar return this.app.clock.time_position.bar;
} }
get tick(): number { get tick(): number {
@ -642,7 +650,7 @@ export class UserAPI {
* *
* @returns The current tick number * @returns The current tick number
*/ */
return this.app.clock.tick return this.app.clock.tick;
} }
get pulse(): number { get pulse(): number {
@ -651,7 +659,7 @@ export class UserAPI {
* *
* @returns The current pulse number * @returns The current pulse number
*/ */
return this.app.clock.time_position.pulse return this.app.clock.time_position.pulse;
} }
get beat(): number { get beat(): number {
@ -660,7 +668,7 @@ export class UserAPI {
* *
* @returns The current beat number * @returns The current beat number
*/ */
return this.app.clock.time_position.beat return this.app.clock.time_position.beat;
} }
get ebeat(): number { get ebeat(): number {
@ -668,15 +676,14 @@ export class UserAPI {
* Returns the current beat number since the origin of time * Returns the current beat number since the origin of time
* TODO: fix! Why is this not working? * TODO: fix! Why is this not working?
*/ */
return this.app.clock.beats_since_origin return this.app.clock.beats_since_origin;
} }
onbar(n: number, ...bar: number[]): boolean { onbar(n: number, ...bar: number[]): boolean {
// n is acting as a modulo on the bar number // n is acting as a modulo on the bar number
const bar_list = [...Array(n).keys()].map(i => i + 1); const bar_list = [...Array(n).keys()].map((i) => i + 1);
console.log(bar.some(b => bar_list.includes(b % n))) console.log(bar.some((b) => bar_list.includes(b % n)));
return bar.some(b => bar_list.includes(b % n)) return bar.some((b) => bar_list.includes(b % n));
} }
onbeat(...beat: number[]): boolean { onbeat(...beat: number[]): boolean {
@ -689,17 +696,18 @@ export class UserAPI {
* @param beat - The beats to check * @param beat - The beats to check
* @returns True if the current beat is in the given list of beats * @returns True if the current beat is in the given list of beats
*/ */
let final_pulses: boolean[] = [] let final_pulses: boolean[] = [];
beat.forEach(b => { beat.forEach((b) => {
b = 1 + (b % this.app.clock.time_signature[0]) b = 1 + (b % this.app.clock.time_signature[0]);
let integral_part = Math.floor(b); let integral_part = Math.floor(b);
let decimal_part = b - integral_part; let decimal_part = b - integral_part;
final_pulses.push( final_pulses.push(
integral_part === this.app.clock.time_position.beat && integral_part === this.app.clock.time_position.beat &&
this.app.clock.time_position.pulse === decimal_part * this.app.clock.ppqn this.app.clock.time_position.pulse ===
) decimal_part * this.app.clock.ppqn
);
}); });
return final_pulses.some(p => p == true) return final_pulses.some((p) => p == true);
} }
stop(): void { stop(): void {
@ -709,11 +717,11 @@ export class UserAPI {
* @see silence * @see silence
* @see hush * @see hush
*/ */
this.app.clock.pause() this.app.clock.pause();
this.app.setButtonHighlighting("pause", true); this.app.setButtonHighlighting("pause", true);
} }
silence = this.stop silence = this.stop;
hush = this.stop hush = this.stop;
prob(p: number): boolean { prob(p: number): boolean {
/** /**
@ -722,7 +730,7 @@ export class UserAPI {
* @param p - The probability of returning true * @param p - The probability of returning true
* @returns True p% of the time * @returns True p% of the time
*/ */
return Math.random() * 100 < p return Math.random() * 100 < p;
} }
toss(): boolean { toss(): boolean {
@ -736,7 +744,7 @@ export class UserAPI {
* @see almostAlways * @see almostAlways
* @see almostNever * @see almostNever
*/ */
return Math.random() > 0.5 return Math.random() > 0.5;
} }
min(...values: number[]): number { min(...values: number[]): number {
@ -746,7 +754,7 @@ export class UserAPI {
* @param values - The list of numbers * @param values - The list of numbers
* @returns The minimum value of the list of numbers * @returns The minimum value of the list of numbers
*/ */
return Math.min(...values) return Math.min(...values);
} }
max(...values: number[]): number { max(...values: number[]): number {
@ -756,7 +764,7 @@ export class UserAPI {
* @param values - The list of numbers * @param values - The list of numbers
* @returns The maximum value of the list of numbers * @returns The maximum value of the list of numbers
*/ */
return Math.max(...values) return Math.max(...values);
} }
limit(value: number, min: number, max: number): number { limit(value: number, min: number, max: number): number {
@ -768,10 +776,9 @@ export class UserAPI {
* @param max - The maximum value * @param max - The maximum value
* @returns The limited value * @returns The limited value
*/ */
return Math.min(Math.max(value, min), max) return Math.min(Math.max(value, min), max);
} }
delay(ms: number, func: Function): void { delay(ms: number, func: Function): void {
/** /**
* Delays the execution of a function by a given number of milliseconds. * Delays the execution of a function by a given number of milliseconds.
@ -780,7 +787,7 @@ export class UserAPI {
* @param func - The function to execute * @param func - The function to execute
* @returns The current time signature * @returns The current time signature
*/ */
setTimeout(func, ms) setTimeout(func, ms);
} }
delayr(ms: number, nb: number, func: Function): void { delayr(ms: number, nb: number, func: Function): void {
@ -792,9 +799,9 @@ export class UserAPI {
* @param func - The function to execute * @param func - The function to execute
* @returns The current time signature * @returns The current time signature
*/ */
const list = [...Array(nb).keys()].map(i => ms * i); const list = [...Array(nb).keys()].map((i) => ms * i);
list.forEach((ms, _) => { list.forEach((ms, _) => {
setTimeout(func, ms) setTimeout(func, ms);
}); });
} }
@ -805,7 +812,7 @@ export class UserAPI {
* @param pulse - The pulse to check for * @param pulse - The pulse to check for
* @returns True if the current pulse is a modulo of any of the given pulses * @returns True if the current pulse is a modulo of any of the given pulses
*/ */
return pulse.some(p => this.app.clock.time_position.pulse % p === 0) return pulse.some((p) => this.app.clock.time_position.pulse % p === 0);
} }
modbar(...bar: number[]): boolean { modbar(...bar: number[]): boolean {
@ -816,10 +823,15 @@ export class UserAPI {
* @returns True if the current bar is a modulo of any of the given bars * @returns True if the current bar is a modulo of any of the given bars
* *
*/ */
return bar.some(b => this.app.clock.time_position.bar % b === 0) return bar.some((b) => this.app.clock.time_position.bar % b === 0);
} }
euclid(iterator: number, pulses: number, length: number, rotate: number=0): boolean { 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.
* *
@ -832,16 +844,23 @@ export class UserAPI {
return this._euclidean_cycle(pulses, length, rotate)[iterator % length]; return this._euclidean_cycle(pulses, length, rotate)[iterator % length];
} }
_euclidean_cycle(pulses: number, length: number, rotate: number = 0): boolean[] { _euclidean_cycle(
pulses: number,
length: number,
rotate: number = 0
): boolean[] {
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]; if (pulses >= length) return [true];
const resList = Array.from({length}, (_, i) => ((pulses * (i - 1)) % length + length) % length); const resList = Array.from(
{ length },
(_, i) => (((pulses * (i - 1)) % length) + length) % length
);
let cycle = resList.map((_, i) => startsDescent(resList, i)); let cycle = resList.map((_, i) => startsDescent(resList, i));
if(rotate!=0) { if (rotate != 0) {
cycle = cycle.slice(rotate).concat(cycle.slice(0, rotate)); cycle = cycle.slice(rotate).concat(cycle.slice(0, rotate));
} }
return cycle; return cycle;
@ -860,6 +879,14 @@ export class UserAPI {
return tobin[iterator % tobin.length]; return tobin[iterator % tobin.length];
} }
gold() {
/**
* Essayer de générer des séquences tirées du truc de Puckette
* Faire ça avec des lazy lists, ça ne devrait pas être trop difficle.
*
*/
}
// ============================================================= // =============================================================
// Low Frequency Oscillators // Low Frequency Oscillators
// ============================================================= // =============================================================
@ -886,7 +913,7 @@ export class UserAPI {
return result; return result;
} }
sine(freq: number = 1, offset: number=0): number { sine(freq: number = 1, offset: number = 0): number {
/** /**
* Returns a sine wave between -1 and 1. * Returns a sine wave between -1 and 1.
* *
@ -894,10 +921,12 @@ export class UserAPI {
* @param offset - The offset of the sine wave * @param offset - The offset of the sine wave
* @returns A sine wave between -1 and 1 * @returns A sine wave between -1 and 1
*/ */
return Math.sin(this.app.clock.ctx.currentTime * Math.PI * 2 * freq) + offset return (
Math.sin(this.app.clock.ctx.currentTime * Math.PI * 2 * freq) + offset
);
} }
saw(freq: number = 1, offset: number=0): number { saw(freq: number = 1, offset: number = 0): number {
/** /**
* Returns a saw wave between -1 and 1. * Returns a saw wave between -1 and 1.
* *
@ -909,10 +938,10 @@ export class UserAPI {
* @see sine * @see sine
* @see noise * @see noise
*/ */
return (this.app.clock.ctx.currentTime * freq) % 1 * 2 - 1 + offset return ((this.app.clock.ctx.currentTime * freq) % 1) * 2 - 1 + offset;
} }
triangle(freq: number = 1, offset: number=0): number { triangle(freq: number = 1, offset: number = 0): number {
/** /**
* Returns a triangle wave between -1 and 1. * Returns a triangle wave between -1 and 1.
* *
@ -922,10 +951,10 @@ export class UserAPI {
* @see sine * @see sine
* @see noise * @see noise
*/ */
return Math.abs(this.saw(freq, offset)) * 2 - 1 return Math.abs(this.saw(freq, offset)) * 2 - 1;
} }
square(freq: number = 1, offset: number=0): number { square(freq: number = 1, offset: number = 0): number {
/** /**
* Returns a square wave between -1 and 1. * Returns a square wave between -1 and 1.
* *
@ -935,7 +964,7 @@ export class UserAPI {
* @see sine * @see sine
* @see noise * @see noise
*/ */
return this.saw(freq, offset) > 0 ? 1 : -1 return this.saw(freq, offset) > 0 ? 1 : -1;
} }
noise(): number { noise(): number {
@ -949,21 +978,23 @@ export class UserAPI {
* @see sine * @see sine
* @see noise * @see noise
*/ */
return Math.random() * 2 - 1 return Math.random() * 2 - 1;
} }
// ============================================================= // =============================================================
// Math functions // Math functions
// ============================================================= // =============================================================
abs = Math.abs abs = Math.abs;
// ============================================================= // =============================================================
// Trivial functions // Trivial functions
// ============================================================= // =============================================================
sound = async (values: object, delay: number = 0.00) => { sound = async (values: object, delay: number = 0.0) => {
superdough(values, delay) superdough(values, delay);
} };
d = this.sound d = this.sound;
samples = samples;
} }

View File

@ -1,18 +1,15 @@
import type { Editor } from './main'; import type { Editor } from "./main";
import type { File } from './AppSettings'; import type { File } from "./AppSettings";
function codeInterceptor(code: string): string { const delay = (ms: number) =>
return code new Promise((_, reject) =>
.replace(/->/g, "&&") setTimeout(() => reject(new Error("Operation took too long")), ms)
.replace(/t\[(\d+),(\d+)\]/g, 'mod($1,$2)') );
.replace(/b\[(\d+),(\d+)\]/g, '[$1,$2].includes(beat)')
.replace(/eb\[(\d+),(\d+)\]/g, '[$1,$2].includes(ebeat)')
.replace(/m\[(\d+),(\d+)\]/g, '[$1,$2].includes(bar)');
}
const delay = (ms: number) => new Promise((_, reject) => setTimeout(() => reject(new Error('Operation took too long')), ms)); const tryCatchWrapper = (
application: Editor,
const tryCatchWrapper = (application: Editor, code: string): Promise<boolean> => { code: string
): Promise<boolean> => {
/** /**
* This function wraps a string of code in a try/catch block and returns a promise * This function wraps a string of code in a try/catch block and returns a promise
* that resolves to true if the code is valid and false if the code is invalid after * that resolves to true if the code is valid and false if the code is invalid after
@ -24,14 +21,16 @@ const tryCatchWrapper = (application: Editor, code: string): Promise<boolean> =>
*/ */
return new Promise((resolve, _) => { return new Promise((resolve, _) => {
try { try {
Function(`with (this) {try{${codeInterceptor(code)}} catch (e) {console.log(e)}};`).call(application.api); Function(`with (this) {try{${code}} catch (e) {console.log(e)}};`).call(
application.api
);
resolve(true); resolve(true);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
resolve(false); resolve(false);
} }
}); });
} };
export const tryEvaluate = async ( export const tryEvaluate = async (
/** /**
@ -47,13 +46,16 @@ export const tryEvaluate = async (
application: Editor, application: Editor,
code: File, code: File,
timeout = 5000 timeout = 5000
): Promise<void> => { ): Promise<void> => {
try { try {
code.evaluations!++; code.evaluations!++;
const isCodeValid = await Promise.race([tryCatchWrapper( const isCodeValid = await Promise.race([
tryCatchWrapper(
application, application,
`let i = ${code.evaluations};` + codeInterceptor(code.candidate as string), (`let i = ${code.evaluations};` + code.candidate) as string
), delay(timeout)]); ),
delay(timeout),
]);
if (isCodeValid) { if (isCodeValid) {
code.committed = code.candidate; code.committed = code.candidate;
@ -63,9 +65,13 @@ export const tryEvaluate = async (
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
} };
export const evaluate = async (application: Editor, code: File, timeout = 1000): Promise<void> => { export const evaluate = async (
application: Editor,
code: File,
timeout = 1000
): Promise<void> => {
/** /**
* This function evaluates a string of code in the context of user API. * This function evaluates a string of code in the context of user API.
* *
@ -75,10 +81,12 @@ export const evaluate = async (application: Editor, code: File, timeout = 1000):
* @returns A promise that resolves to void * @returns A promise that resolves to void
*/ */
try { try {
await Promise.race([tryCatchWrapper(application, codeInterceptor(code.committed as string)), delay(timeout)]); await Promise.race([
if (code.evaluations) tryCatchWrapper(application, code.committed as string),
code.evaluations++; delay(timeout),
]);
if (code.evaluations) code.evaluations++;
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
} };

View File

@ -1160,7 +1160,11 @@ yaml@^2.1.1:
"zifferjs@https://github.com/amiika/zifferjs.git": "zifferjs@https://github.com/amiika/zifferjs.git":
version "0.0.0" version "0.0.0"
<<<<<<< HEAD
resolved "https://github.com/amiika/zifferjs.git#374ee4edb0b07d9968b635f899f9f88d79ddaa07" resolved "https://github.com/amiika/zifferjs.git#374ee4edb0b07d9968b635f899f9f88d79ddaa07"
=======
resolved "https://github.com/amiika/zifferjs.git#0254770c19db8772a9c3cb6b0571f2b5007593e6"
>>>>>>> 3effb4f456592922e3e97e1e7b16f6f8929a90b4
dependencies: dependencies:
"@types/seedrandom" "^3.0.5" "@types/seedrandom" "^3.0.5"
lru-cache "^10.0.0" lru-cache "^10.0.0"