Running Prettier on most files
This commit is contained in:
139
src/API.ts
139
src/API.ts
@ -51,25 +51,24 @@ export class UserAPI {
|
|||||||
private errorTimeoutID: number = 0;
|
private errorTimeoutID: number = 0;
|
||||||
private printTimeoutID: number = 0;
|
private printTimeoutID: number = 0;
|
||||||
|
|
||||||
|
|
||||||
MidiConnection: MidiConnection = new MidiConnection();
|
MidiConnection: MidiConnection = new MidiConnection();
|
||||||
load: samples;
|
load: samples;
|
||||||
|
|
||||||
constructor(public app: Editor) {}
|
constructor(public app: Editor) {}
|
||||||
|
|
||||||
_loadUniverseFromInterface = (universe: string) => {
|
_loadUniverseFromInterface = (universe: string) => {
|
||||||
this.app.loadUniverse(universe as string);
|
this.app.loadUniverse(universe as string);
|
||||||
this.app.openBuffersModal();
|
this.app.openBuffersModal();
|
||||||
}
|
};
|
||||||
|
|
||||||
_deleteUniverseFromInterface = (universe: string) => {
|
_deleteUniverseFromInterface = (universe: string) => {
|
||||||
delete this.app.universes[universe];
|
delete this.app.universes[universe];
|
||||||
this.app.settings.saveApplicationToLocalStorage(
|
this.app.settings.saveApplicationToLocalStorage(
|
||||||
this.app.universes,
|
this.app.universes,
|
||||||
this.app.settings
|
this.app.settings
|
||||||
);
|
);
|
||||||
this.app.updateKnownUniversesView();
|
this.app.updateKnownUniversesView();
|
||||||
}
|
};
|
||||||
|
|
||||||
_playDocExample = (code?: string) => {
|
_playDocExample = (code?: string) => {
|
||||||
this.play();
|
this.play();
|
||||||
@ -82,7 +81,6 @@ export class UserAPI {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
_playDocExampleOnce = (code?: string) => {
|
_playDocExampleOnce = (code?: string) => {
|
||||||
this.play();
|
this.play();
|
||||||
console.log("Executing documentation example: " + this.app.selectedExample);
|
console.log("Executing documentation example: " + this.app.selectedExample);
|
||||||
@ -98,7 +96,7 @@ export class UserAPI {
|
|||||||
clearTimeout(this.errorTimeoutID);
|
clearTimeout(this.errorTimeoutID);
|
||||||
clearTimeout(this.printTimeoutID);
|
clearTimeout(this.printTimeoutID);
|
||||||
this.app.error_line.innerHTML = error as string;
|
this.app.error_line.innerHTML = error as string;
|
||||||
this.app.error_line.style.color = "color-red-800";
|
this.app.error_line.style.color = "color-red-800";
|
||||||
this.app.error_line.classList.remove("hidden");
|
this.app.error_line.classList.remove("hidden");
|
||||||
this.errorTimeoutID = setTimeout(
|
this.errorTimeoutID = setTimeout(
|
||||||
() => this.app.error_line.classList.add("hidden"),
|
() => this.app.error_line.classList.add("hidden"),
|
||||||
@ -111,7 +109,7 @@ export class UserAPI {
|
|||||||
clearTimeout(this.printTimeoutID);
|
clearTimeout(this.printTimeoutID);
|
||||||
clearTimeout(this.errorTimeoutID);
|
clearTimeout(this.errorTimeoutID);
|
||||||
this.app.error_line.innerHTML = message as string;
|
this.app.error_line.innerHTML = message as string;
|
||||||
this.app.error_line.style.color = "white";
|
this.app.error_line.style.color = "white";
|
||||||
this.app.error_line.classList.remove("hidden");
|
this.app.error_line.classList.remove("hidden");
|
||||||
this.printTimeoutID = setTimeout(
|
this.printTimeoutID = setTimeout(
|
||||||
() => this.app.error_line.classList.add("hidden"),
|
() => this.app.error_line.classList.add("hidden"),
|
||||||
@ -151,10 +149,10 @@ export class UserAPI {
|
|||||||
// Mouse functions
|
// Mouse functions
|
||||||
// =============================================================
|
// =============================================================
|
||||||
|
|
||||||
onmousemove = (e: MouseEvent) => {
|
onmousemove = (e: MouseEvent) => {
|
||||||
this.app._mouseX = e.clientX;
|
this.app._mouseX = e.clientX;
|
||||||
this.app._mouseY = e.clientY;
|
this.app._mouseY = e.clientY;
|
||||||
}
|
};
|
||||||
|
|
||||||
public mouseX = (): number => {
|
public mouseX = (): number => {
|
||||||
/**
|
/**
|
||||||
@ -359,17 +357,16 @@ export class UserAPI {
|
|||||||
public z = (
|
public z = (
|
||||||
input: string,
|
input: string,
|
||||||
options: InputOptions = {},
|
options: InputOptions = {},
|
||||||
id: number|string = ""
|
id: number | string = ""
|
||||||
) => {
|
) => {
|
||||||
|
const zid = "z" + id.toString();
|
||||||
const zid = "z"+id.toString();
|
const key = id === "" ? this.generateCacheKey(input, options) : zid;
|
||||||
const key = id==="" ? this.generateCacheKey(input, options) : zid;
|
|
||||||
|
|
||||||
let player;
|
let player;
|
||||||
|
|
||||||
if (this.app.api.patternCache.has(key)) {
|
if (this.app.api.patternCache.has(key)) {
|
||||||
player = this.app.api.patternCache.get(key) as Player;
|
player = this.app.api.patternCache.get(key) as Player;
|
||||||
if(player.input!==input) {
|
if (player.input !== input) {
|
||||||
player = undefined;
|
player = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -379,30 +376,47 @@ export class UserAPI {
|
|||||||
this.app.api.patternCache.set(key, player);
|
this.app.api.patternCache.set(key, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(typeof id === "number") player.zid = zid;
|
if (typeof id === "number") player.zid = zid;
|
||||||
|
|
||||||
player.updateLastCallTime();
|
player.updateLastCallTime();
|
||||||
|
|
||||||
return player;
|
return player;
|
||||||
};
|
};
|
||||||
|
|
||||||
public z0 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 0);
|
public z0 = (input: string, opts: InputOptions = {}) =>
|
||||||
public z1 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 1);
|
this.z(input, opts, 0);
|
||||||
public z2 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 2);
|
public z1 = (input: string, opts: InputOptions = {}) =>
|
||||||
public z3 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 3);
|
this.z(input, opts, 1);
|
||||||
public z4 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 4);
|
public z2 = (input: string, opts: InputOptions = {}) =>
|
||||||
public z5 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 5);
|
this.z(input, opts, 2);
|
||||||
public z6 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 6);
|
public z3 = (input: string, opts: InputOptions = {}) =>
|
||||||
public z7 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 7);
|
this.z(input, opts, 3);
|
||||||
public z8 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 8);
|
public z4 = (input: string, opts: InputOptions = {}) =>
|
||||||
public z9 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 9);
|
this.z(input, opts, 4);
|
||||||
public z10 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 10);
|
public z5 = (input: string, opts: InputOptions = {}) =>
|
||||||
public z11 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 11);
|
this.z(input, opts, 5);
|
||||||
public z12 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 12);
|
public z6 = (input: string, opts: InputOptions = {}) =>
|
||||||
public z13 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 13);
|
this.z(input, opts, 6);
|
||||||
public z14 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 14);
|
public z7 = (input: string, opts: InputOptions = {}) =>
|
||||||
public z15 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 15);
|
this.z(input, opts, 7);
|
||||||
public z16 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 16);
|
public z8 = (input: string, opts: InputOptions = {}) =>
|
||||||
|
this.z(input, opts, 8);
|
||||||
|
public z9 = (input: string, opts: InputOptions = {}) =>
|
||||||
|
this.z(input, opts, 9);
|
||||||
|
public z10 = (input: string, opts: InputOptions = {}) =>
|
||||||
|
this.z(input, opts, 10);
|
||||||
|
public z11 = (input: string, opts: InputOptions = {}) =>
|
||||||
|
this.z(input, opts, 11);
|
||||||
|
public z12 = (input: string, opts: InputOptions = {}) =>
|
||||||
|
this.z(input, opts, 12);
|
||||||
|
public z13 = (input: string, opts: InputOptions = {}) =>
|
||||||
|
this.z(input, opts, 13);
|
||||||
|
public z14 = (input: string, opts: InputOptions = {}) =>
|
||||||
|
this.z(input, opts, 14);
|
||||||
|
public z15 = (input: string, opts: InputOptions = {}) =>
|
||||||
|
this.z(input, opts, 15);
|
||||||
|
public z16 = (input: string, opts: InputOptions = {}) =>
|
||||||
|
this.z(input, opts, 16);
|
||||||
|
|
||||||
// =============================================================
|
// =============================================================
|
||||||
// Counter and iteration
|
// Counter and iteration
|
||||||
@ -930,12 +944,15 @@ export class UserAPI {
|
|||||||
return current_chunk % 2 === 0;
|
return current_chunk % 2 === 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
public onbar = (bars: number[] | number, n: number = this.app.clock.time_signature[0]): boolean => {
|
public onbar = (
|
||||||
let current_bar = (this.bar() % n) + 1;
|
bars: number[] | number,
|
||||||
return (typeof bars === "number")
|
n: number = this.app.clock.time_signature[0]
|
||||||
? bars === current_bar
|
): boolean => {
|
||||||
: bars.some((b) => b == current_bar)
|
let current_bar = (this.bar() % n) + 1;
|
||||||
};
|
return typeof bars === "number"
|
||||||
|
? bars === current_bar
|
||||||
|
: bars.some((b) => b == current_bar);
|
||||||
|
};
|
||||||
|
|
||||||
onbeat = (...beat: number[]): boolean => {
|
onbeat = (...beat: number[]): boolean => {
|
||||||
/**
|
/**
|
||||||
@ -949,12 +966,14 @@ export class UserAPI {
|
|||||||
*/
|
*/
|
||||||
let final_pulses: boolean[] = [];
|
let final_pulses: boolean[] = [];
|
||||||
beat.forEach((b) => {
|
beat.forEach((b) => {
|
||||||
const beat = b % this.app.clock.time_signature[0] || this.app.clock.time_signature[0];
|
const beat =
|
||||||
|
b % this.app.clock.time_signature[0] ||
|
||||||
|
this.app.clock.time_signature[0];
|
||||||
const integral_part = Math.floor(beat);
|
const integral_part = Math.floor(beat);
|
||||||
const decimal_part = ((beat - integral_part) * this.app.clock.ppqn) + 1;
|
const decimal_part = (beat - integral_part) * this.app.clock.ppqn + 1;
|
||||||
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.time_position.pulse === decimal_part
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return final_pulses.some((p) => p == true);
|
return final_pulses.some((p) => p == true);
|
||||||
@ -1013,14 +1032,16 @@ export class UserAPI {
|
|||||||
};
|
};
|
||||||
ec: Function = this.euclid;
|
ec: Function = this.euclid;
|
||||||
|
|
||||||
public rhythm = (
|
public rhythm = (
|
||||||
div: number,
|
div: number,
|
||||||
pulses: number,
|
pulses: number,
|
||||||
length: number,
|
length: number,
|
||||||
rotate: number = 0
|
rotate: number = 0
|
||||||
): boolean => {
|
): boolean => {
|
||||||
return this.mod(div) && this._euclidean_cycle(pulses, length, rotate).div(div);
|
return (
|
||||||
}
|
this.mod(div) && this._euclidean_cycle(pulses, length, rotate).div(div)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
_euclidean_cycle(
|
_euclidean_cycle(
|
||||||
pulses: number,
|
pulses: number,
|
||||||
@ -1274,9 +1295,9 @@ export class UserAPI {
|
|||||||
samples = samples;
|
samples = samples;
|
||||||
|
|
||||||
log = (message: any) => {
|
log = (message: any) => {
|
||||||
console.log(message);
|
console.log(message);
|
||||||
this._logMessage(message);
|
this._logMessage(message);
|
||||||
}
|
};
|
||||||
|
|
||||||
scale = scale;
|
scale = scale;
|
||||||
|
|
||||||
|
|||||||
@ -3,10 +3,10 @@ export {};
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Array<T> {
|
interface Array<T> {
|
||||||
add(amount: number): number[];
|
add(amount: number): number[];
|
||||||
sub(amount: number): number[];
|
sub(amount: number): number[];
|
||||||
mult(amount: number): number[];
|
mult(amount: number): number[];
|
||||||
division(amount: number): number[];
|
division(amount: number): number[];
|
||||||
palindrome(): T[];
|
palindrome(): T[];
|
||||||
random(index: number): T;
|
random(index: number): T;
|
||||||
rand(index: number): T;
|
rand(index: number): T;
|
||||||
@ -24,8 +24,8 @@ declare global {
|
|||||||
rotate(steps: number): this;
|
rotate(steps: number): this;
|
||||||
unique(): this;
|
unique(): this;
|
||||||
in(value: T): boolean;
|
in(value: T): boolean;
|
||||||
square(): number[];
|
square(): number[];
|
||||||
sqrt(): number[];
|
sqrt(): number[];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,54 +33,55 @@ export const makeArrayExtensions = (api: UserAPI) => {
|
|||||||
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
Array.prototype.square = function (): number[] {
|
|
||||||
/**
|
|
||||||
* @returns New array with squared values.
|
|
||||||
*/
|
|
||||||
return this.map((x: number) => x * x);
|
|
||||||
};
|
|
||||||
|
|
||||||
Array.prototype.sqrt = function (): number[] {
|
Array.prototype.square = function (): number[] {
|
||||||
/**
|
/**
|
||||||
* @returns New array with square roots of values. Throws if any element is negative.
|
* @returns New array with squared values.
|
||||||
*/
|
*/
|
||||||
if (this.some(x => x < 0)) throw new Error('Cannot take square root of negative number');
|
return this.map((x: number) => x * x);
|
||||||
return this.map((x: number) => Math.sqrt(x));
|
};
|
||||||
};
|
|
||||||
|
Array.prototype.sqrt = function (): number[] {
|
||||||
Array.prototype.add = function (amount: number): number[] {
|
/**
|
||||||
/**
|
* @returns New array with square roots of values. Throws if any element is negative.
|
||||||
* @param amount - The value to add to each element in the array.
|
*/
|
||||||
* @returns New array with added values.
|
if (this.some((x) => x < 0))
|
||||||
*/
|
throw new Error("Cannot take square root of negative number");
|
||||||
return this.map((x: number) => x + amount);
|
return this.map((x: number) => Math.sqrt(x));
|
||||||
};
|
};
|
||||||
|
|
||||||
Array.prototype.sub = function (amount: number): number[] {
|
Array.prototype.add = function (amount: number): number[] {
|
||||||
/**
|
/**
|
||||||
* @param amount - The value to subtract from each element in the array.
|
* @param amount - The value to add to each element in the array.
|
||||||
* @returns New array with subtracted values.
|
* @returns New array with added values.
|
||||||
*/
|
*/
|
||||||
return this.map((x: number) => x - amount);
|
return this.map((x: number) => x + amount);
|
||||||
};
|
};
|
||||||
|
|
||||||
Array.prototype.mult = function (amount: number): number[] {
|
Array.prototype.sub = function (amount: number): number[] {
|
||||||
/**
|
/**
|
||||||
* @param amount - The value to multiply with each element in the array.
|
* @param amount - The value to subtract from each element in the array.
|
||||||
* @returns New array with multiplied values.
|
* @returns New array with subtracted values.
|
||||||
*/
|
*/
|
||||||
return this.map((x: number) => x * amount);
|
return this.map((x: number) => x - amount);
|
||||||
};
|
};
|
||||||
|
|
||||||
Array.prototype.division = function (amount: number): number[] {
|
Array.prototype.mult = function (amount: number): number[] {
|
||||||
/**
|
/**
|
||||||
* @param amount - The value to divide each element in the array by.
|
* @param amount - The value to multiply with each element in the array.
|
||||||
* @returns New array with divided values. Throws if division by zero.
|
* @returns New array with multiplied values.
|
||||||
*/
|
*/
|
||||||
if (amount === 0) throw new Error('Division by zero');
|
return this.map((x: number) => x * amount);
|
||||||
return this.map((x: number) => x / amount);
|
};
|
||||||
};
|
|
||||||
|
Array.prototype.division = function (amount: number): number[] {
|
||||||
|
/**
|
||||||
|
* @param amount - The value to divide each element in the array by.
|
||||||
|
* @returns New array with divided values. Throws if division by zero.
|
||||||
|
*/
|
||||||
|
if (amount === 0) throw new Error("Division by zero");
|
||||||
|
return this.map((x: number) => x / amount);
|
||||||
|
};
|
||||||
|
|
||||||
Array.prototype.pick = function () {
|
Array.prototype.pick = function () {
|
||||||
/**
|
/**
|
||||||
|
|||||||
283
src/Clock.ts
283
src/Clock.ts
@ -1,164 +1,163 @@
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { TransportNode } from './TransportNode';
|
import { TransportNode } from "./TransportNode";
|
||||||
import TransportProcessor from './TransportProcessor?worker&url';
|
import TransportProcessor from "./TransportProcessor?worker&url";
|
||||||
import { Editor } from './main';
|
import { Editor } from "./main";
|
||||||
|
|
||||||
export interface TimePosition {
|
export interface TimePosition {
|
||||||
/**
|
/**
|
||||||
* A position in time.
|
* A position in time.
|
||||||
*
|
*
|
||||||
* @param bar - The bar number
|
* @param bar - The bar number
|
||||||
* @param beat - The beat number
|
* @param beat - The beat number
|
||||||
* @param pulse - The pulse number
|
* @param pulse - The pulse number
|
||||||
*/
|
*/
|
||||||
bar: number
|
bar: number;
|
||||||
beat: number
|
beat: number;
|
||||||
pulse: number
|
pulse: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Clock {
|
export class Clock {
|
||||||
|
/**
|
||||||
|
* The Clock Class is responsible for keeping track of the current time.
|
||||||
|
* It is also responsible for starting and stopping the Clock TransportNode.
|
||||||
|
*
|
||||||
|
* @param app - The main application instance
|
||||||
|
* @param ctx - The current AudioContext used by app
|
||||||
|
* @param transportNode - The TransportNode helper
|
||||||
|
* @param bpm - The current beats per minute value
|
||||||
|
* @param time_signature - The time signature
|
||||||
|
* @param time_position - The current time position
|
||||||
|
* @param ppqn - The pulses per quarter note
|
||||||
|
* @param tick - The current tick since origin
|
||||||
|
*/
|
||||||
|
|
||||||
|
ctx: AudioContext;
|
||||||
|
transportNode: TransportNode | null;
|
||||||
|
private _bpm: number;
|
||||||
|
time_signature: number[];
|
||||||
|
time_position: TimePosition;
|
||||||
|
private _ppqn: number;
|
||||||
|
tick: number;
|
||||||
|
|
||||||
|
constructor(public app: Editor, ctx: AudioContext) {
|
||||||
|
this.time_position = { bar: 0, beat: 0, pulse: 0 };
|
||||||
|
this.time_signature = [4, 4];
|
||||||
|
this.tick = 0;
|
||||||
|
this._bpm = 120;
|
||||||
|
this._ppqn = 48;
|
||||||
|
this.transportNode = null;
|
||||||
|
this.ctx = ctx;
|
||||||
|
ctx.audioWorklet
|
||||||
|
.addModule(TransportProcessor)
|
||||||
|
.then((e) => {
|
||||||
|
this.transportNode = new TransportNode(ctx, {}, this.app);
|
||||||
|
this.transportNode.connect(ctx.destination);
|
||||||
|
return e;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log("Error loading TransportProcessor.js:", e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get ticks_before_new_bar(): number {
|
||||||
/**
|
/**
|
||||||
* The Clock Class is responsible for keeping track of the current time.
|
* This function returns the number of ticks separating the current moment
|
||||||
* It is also responsible for starting and stopping the Clock TransportNode.
|
* from the beginning of the next bar.
|
||||||
*
|
*
|
||||||
* @param app - The main application instance
|
* @returns number of ticks until next bar
|
||||||
* @param ctx - The current AudioContext used by app
|
|
||||||
* @param transportNode - The TransportNode helper
|
|
||||||
* @param bpm - The current beats per minute value
|
|
||||||
* @param time_signature - The time signature
|
|
||||||
* @param time_position - The current time position
|
|
||||||
* @param ppqn - The pulses per quarter note
|
|
||||||
* @param tick - The current tick since origin
|
|
||||||
*/
|
*/
|
||||||
|
const ticskMissingFromBeat = this.ppqn - this.time_position.pulse;
|
||||||
|
const beatsMissingFromBar = this.beats_per_bar - this.time_position.beat;
|
||||||
|
return beatsMissingFromBar * this.ppqn + ticskMissingFromBeat;
|
||||||
|
}
|
||||||
|
|
||||||
ctx: AudioContext
|
get next_beat_in_ticks(): number {
|
||||||
transportNode: TransportNode | null
|
/**
|
||||||
private _bpm: number
|
* This function returns the number of ticks separating the current moment
|
||||||
time_signature: number[]
|
* from the beginning of the next beat.
|
||||||
time_position: TimePosition
|
*
|
||||||
private _ppqn: number
|
* @returns number of ticks until next beat
|
||||||
tick: number
|
*/
|
||||||
|
return this.app.clock.pulses_since_origin + this.time_position.pulse;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(public app: Editor, ctx: AudioContext) {
|
get beats_per_bar(): number {
|
||||||
this.time_position = { bar: 0, beat: 0, pulse: 0 }
|
/**
|
||||||
this.time_signature = [4, 4];
|
* Returns the number of beats per bar.
|
||||||
this.tick = 0;
|
*/
|
||||||
this._bpm = 120;
|
return this.time_signature[0];
|
||||||
this._ppqn = 48;
|
}
|
||||||
this.transportNode = null;
|
|
||||||
this.ctx = ctx;
|
|
||||||
ctx.audioWorklet.addModule(TransportProcessor).then((e) => {
|
|
||||||
this.transportNode = new TransportNode(ctx, {}, this.app);
|
|
||||||
this.transportNode.connect(ctx.destination);
|
|
||||||
return e
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.log('Error loading TransportProcessor.js:', e);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
get ticks_before_new_bar(): number {
|
get beats_since_origin(): number {
|
||||||
/**
|
/**
|
||||||
* This function returns the number of ticks separating the current moment
|
* Returns the number of beats since the origin.
|
||||||
* from the beginning of the next bar.
|
*
|
||||||
*
|
* @returns number of beats since origin
|
||||||
* @returns number of ticks until next bar
|
*/
|
||||||
*/
|
return Math.floor(this.tick / this.ppqn);
|
||||||
const ticskMissingFromBeat = this.ppqn - this.time_position.pulse;
|
}
|
||||||
const beatsMissingFromBar = this.beats_per_bar - this.time_position.beat;
|
|
||||||
return (beatsMissingFromBar * this.ppqn) + ticskMissingFromBeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
get next_beat_in_ticks(): number {
|
get pulses_since_origin(): number {
|
||||||
/**
|
/**
|
||||||
* This function returns the number of ticks separating the current moment
|
* Returns the number of pulses since the origin.
|
||||||
* from the beginning of the next beat.
|
*
|
||||||
*
|
* @returns number of pulses since origin
|
||||||
* @returns number of ticks until next beat
|
*/
|
||||||
*/
|
return this.tick;
|
||||||
return this.app.clock.pulses_since_origin + this.time_position.pulse;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
get beats_per_bar(): number {
|
get pulse_duration(): number {
|
||||||
/**
|
/**
|
||||||
* Returns the number of beats per bar.
|
* Returns the duration of a pulse in seconds.
|
||||||
*/
|
*/
|
||||||
return this.time_signature[0];
|
return 60 / this.bpm / this.ppqn;
|
||||||
}
|
}
|
||||||
|
|
||||||
get beats_since_origin(): number {
|
get bpm(): number {
|
||||||
/**
|
return this._bpm;
|
||||||
* Returns the number of beats since the origin.
|
}
|
||||||
*
|
|
||||||
* @returns number of beats since origin
|
|
||||||
*/
|
|
||||||
return Math.floor(this.tick / this.ppqn);
|
|
||||||
}
|
|
||||||
|
|
||||||
get pulses_since_origin(): number {
|
set bpm(bpm: number) {
|
||||||
/**
|
this._bpm = bpm;
|
||||||
* Returns the number of pulses since the origin.
|
this.transportNode?.setBPM(bpm);
|
||||||
*
|
}
|
||||||
* @returns number of pulses since origin
|
|
||||||
*/
|
|
||||||
return this.tick;
|
|
||||||
|
|
||||||
}
|
get ppqn(): number {
|
||||||
|
return this._ppqn;
|
||||||
|
}
|
||||||
|
|
||||||
get pulse_duration(): number {
|
set ppqn(ppqn: number) {
|
||||||
/**
|
this._ppqn = ppqn;
|
||||||
* Returns the duration of a pulse in seconds.
|
this.transportNode?.setPPQN(ppqn);
|
||||||
*/
|
}
|
||||||
return 60 / this.bpm / this.ppqn;
|
|
||||||
}
|
|
||||||
|
|
||||||
get bpm(): number {
|
public convertPulseToSecond(n: number): number {
|
||||||
return this._bpm;
|
/**
|
||||||
}
|
* Converts a pulse to a second.
|
||||||
|
*/
|
||||||
set bpm(bpm: number) {
|
return n * this.pulse_duration;
|
||||||
this._bpm = bpm;
|
}
|
||||||
this.transportNode?.setBPM(bpm);
|
|
||||||
}
|
|
||||||
|
|
||||||
get ppqn(): number {
|
public start(): void {
|
||||||
return this._ppqn;
|
/**
|
||||||
}
|
* Starts the TransportNode (starts the clock).
|
||||||
|
*/
|
||||||
|
this.app.audioContext.resume();
|
||||||
|
this.transportNode?.start();
|
||||||
|
}
|
||||||
|
|
||||||
set ppqn(ppqn: number) {
|
public pause(): void {
|
||||||
this._ppqn = ppqn;
|
/**
|
||||||
this.transportNode?.setPPQN(ppqn);
|
* Pauses the TransportNode (pauses the clock).
|
||||||
}
|
*/
|
||||||
|
this.transportNode?.pause();
|
||||||
|
}
|
||||||
|
|
||||||
public convertPulseToSecond(n: number): number {
|
public stop(): void {
|
||||||
/**
|
/**
|
||||||
* Converts a pulse to a second.
|
* Stops the TransportNode (stops the clock).
|
||||||
*/
|
*/
|
||||||
return n * this.pulse_duration
|
this.app.clock.tick = 0;
|
||||||
}
|
this.transportNode?.stop();
|
||||||
|
}
|
||||||
public start(): void {
|
}
|
||||||
/**
|
|
||||||
* Starts the TransportNode (starts the clock).
|
|
||||||
*/
|
|
||||||
this.app.audioContext.resume();
|
|
||||||
this.transportNode?.start();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public pause(): void {
|
|
||||||
/**
|
|
||||||
* Pauses the TransportNode (pauses the clock).
|
|
||||||
*/
|
|
||||||
this.transportNode?.pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
public stop(): void {
|
|
||||||
/**
|
|
||||||
* Stops the TransportNode (stops the clock).
|
|
||||||
*/
|
|
||||||
this.app.clock.tick = 0;
|
|
||||||
this.transportNode?.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -100,12 +100,12 @@ export const evaluateOnce = async (
|
|||||||
application: Editor,
|
application: Editor,
|
||||||
code: string
|
code: string
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
/**
|
/**
|
||||||
* Evaluates the code once without any caching or error-handling mechanisms besides the tryCatchWrapper.
|
* Evaluates the code once without any caching or error-handling mechanisms besides the tryCatchWrapper.
|
||||||
*
|
*
|
||||||
* @param application - The application object that contains the Editor API.
|
* @param application - The application object that contains the Editor API.
|
||||||
* @param code - The code to be evaluated.
|
* @param code - The code to be evaluated.
|
||||||
* @returns A promise that resolves when the code has been evaluated.
|
* @returns A promise that resolves when the code has been evaluated.
|
||||||
*/
|
*/
|
||||||
await tryCatchWrapper(application, code);
|
await tryCatchWrapper(application, code);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,264 +1,292 @@
|
|||||||
export class MidiConnection{
|
export class MidiConnection {
|
||||||
|
/**
|
||||||
|
* Wrapper class for Web MIDI API. Provides methods for sending MIDI messages.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param midiAccess - Web MIDI API access object
|
||||||
|
* @param midiOutputs - Array of MIDI output objects
|
||||||
|
* @param currentOutputIndex - Index of the currently selected MIDI output
|
||||||
|
* @param scheduledNotes - Object containing scheduled notes. Keys are note numbers and values are timeout IDs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private midiAccess: MIDIAccess | null = null;
|
||||||
|
public midiOutputs: MIDIOutput[] = [];
|
||||||
|
private currentOutputIndex: number = 0;
|
||||||
|
private scheduledNotes: { [noteNumber: number]: number } = {}; // { noteNumber: timeoutId }
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.initializeMidiAccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async initializeMidiAccess(): Promise<void> {
|
||||||
/**
|
/**
|
||||||
* Wrapper class for Web MIDI API. Provides methods for sending MIDI messages.
|
* Initializes Web MIDI API access and populates the list of MIDI outputs.
|
||||||
*
|
*
|
||||||
*
|
* @returns Promise
|
||||||
* @param midiAccess - Web MIDI API access object
|
|
||||||
* @param midiOutputs - Array of MIDI output objects
|
|
||||||
* @param currentOutputIndex - Index of the currently selected MIDI output
|
|
||||||
* @param scheduledNotes - Object containing scheduled notes. Keys are note numbers and values are timeout IDs.
|
|
||||||
*/
|
*/
|
||||||
|
try {
|
||||||
|
this.midiAccess = await navigator.requestMIDIAccess();
|
||||||
|
this.midiOutputs = Array.from(this.midiAccess.outputs.values());
|
||||||
|
if (this.midiOutputs.length === 0) {
|
||||||
|
console.warn("No MIDI outputs available.");
|
||||||
|
this.currentOutputIndex = -1;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to initialize MIDI:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private midiAccess: MIDIAccess | null = null;
|
public getCurrentMidiPort(): string | null {
|
||||||
public midiOutputs: MIDIOutput[] = [];
|
/**
|
||||||
private currentOutputIndex: number = 0;
|
* Returns the name of the currently selected MIDI output.
|
||||||
private scheduledNotes: { [noteNumber: number]: number } = {}; // { noteNumber: timeoutId }
|
*
|
||||||
|
* @returns Name of the currently selected MIDI output or null if no MIDI output is selected or available.
|
||||||
constructor() {
|
*/
|
||||||
this.initializeMidiAccess();
|
if (
|
||||||
}
|
this.midiOutputs.length > 0 &&
|
||||||
|
this.currentOutputIndex >= 0 &&
|
||||||
private async initializeMidiAccess(): Promise<void> {
|
this.currentOutputIndex < this.midiOutputs.length
|
||||||
/**
|
) {
|
||||||
* Initializes Web MIDI API access and populates the list of MIDI outputs.
|
return this.midiOutputs[this.currentOutputIndex].name;
|
||||||
*
|
} else {
|
||||||
* @returns Promise
|
console.error("No MIDI output selected or available.");
|
||||||
*/
|
return null;
|
||||||
try {
|
|
||||||
this.midiAccess = await navigator.requestMIDIAccess();
|
|
||||||
this.midiOutputs = Array.from(this.midiAccess.outputs.values());
|
|
||||||
if (this.midiOutputs.length === 0) {
|
|
||||||
console.warn('No MIDI outputs available.');
|
|
||||||
this.currentOutputIndex = -1;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to initialize MIDI:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCurrentMidiPort(): string | null {
|
|
||||||
/**
|
|
||||||
* Returns the name of the currently selected MIDI output.
|
|
||||||
*
|
|
||||||
* @returns Name of the currently selected MIDI output or null if no MIDI output is selected or available.
|
|
||||||
*/
|
|
||||||
if (this.midiOutputs.length > 0 && this.currentOutputIndex >= 0 && this.currentOutputIndex < this.midiOutputs.length) {
|
|
||||||
return this.midiOutputs[this.currentOutputIndex].name;
|
|
||||||
} else {
|
|
||||||
console.error('No MIDI output selected or available.');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public getCurrentMidiPortIndex(): number {
|
public getCurrentMidiPortIndex(): number {
|
||||||
/**
|
/**
|
||||||
* Returns the index of the currently selected MIDI output.
|
* Returns the index of the currently selected MIDI output.
|
||||||
*
|
*
|
||||||
* @returns Index of the currently selected MIDI output or -1 if no MIDI output is selected or available.
|
* @returns Index of the currently selected MIDI output or -1 if no MIDI output is selected or available.
|
||||||
*/
|
*/
|
||||||
if(this.midiOutputs.length > 0 && this.currentOutputIndex >= 0 && this.currentOutputIndex < this.midiOutputs.length) {
|
if (
|
||||||
return this.currentOutputIndex;
|
this.midiOutputs.length > 0 &&
|
||||||
} else {
|
this.currentOutputIndex >= 0 &&
|
||||||
console.error('No MIDI output selected or available.');
|
this.currentOutputIndex < this.midiOutputs.length
|
||||||
return -1;
|
) {
|
||||||
}
|
return this.currentOutputIndex;
|
||||||
|
} else {
|
||||||
|
console.error("No MIDI output selected or available.");
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public sendMidiClock(): void {
|
public sendMidiClock(): void {
|
||||||
/**
|
/**
|
||||||
* Sends a single MIDI clock message to the currently selected MIDI output.
|
* Sends a single MIDI clock message to the currently selected MIDI output.
|
||||||
*/
|
*/
|
||||||
const output = this.midiOutputs[this.currentOutputIndex];
|
const output = this.midiOutputs[this.currentOutputIndex];
|
||||||
if (output) {
|
if (output) {
|
||||||
output.send([0xF8]); // Send a single MIDI clock message
|
output.send([0xf8]); // Send a single MIDI clock message
|
||||||
} else {
|
} else {
|
||||||
console.error('MIDI output not available.');
|
console.error("MIDI output not available.");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public switchMidiOutput(outputName: string): boolean {
|
|
||||||
/**
|
public switchMidiOutput(outputName: string): boolean {
|
||||||
* Switches the currently selected MIDI output.
|
/**
|
||||||
*
|
* Switches the currently selected MIDI output.
|
||||||
* @param outputName Name of the MIDI output to switch to
|
*
|
||||||
* @returns True if the MIDI output was found and switched to, false otherwise
|
* @param outputName Name of the MIDI output to switch to
|
||||||
*/
|
* @returns True if the MIDI output was found and switched to, false otherwise
|
||||||
const index = this.getMidiOutputIndex(outputName);
|
*/
|
||||||
|
const index = this.getMidiOutputIndex(outputName);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.currentOutputIndex = index;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getMidiOutputIndex(output: string | number): number {
|
||||||
|
/**
|
||||||
|
* Returns the index of the MIDI output with the specified name.
|
||||||
|
*
|
||||||
|
* @param outputName Name of the MIDI output
|
||||||
|
* @returns Index of the new MIDI output or current output if new is not valid
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
if (typeof output === "number") {
|
||||||
|
if (output < 0 || output >= this.midiOutputs.length) {
|
||||||
|
console.error(
|
||||||
|
`Invalid MIDI output index. Index must be in the range 0-${
|
||||||
|
this.midiOutputs.length - 1
|
||||||
|
}.`
|
||||||
|
);
|
||||||
|
return this.currentOutputIndex;
|
||||||
|
} else {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const index = this.midiOutputs.findIndex((o) => o.name === output);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
this.currentOutputIndex = index;
|
return index;
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
return false;
|
console.error(`MIDI output "${output}" not found.`);
|
||||||
}
|
return this.currentOutputIndex;
|
||||||
}
|
|
||||||
|
|
||||||
public getMidiOutputIndex(output: string|number): number {
|
|
||||||
/**
|
|
||||||
* Returns the index of the MIDI output with the specified name.
|
|
||||||
*
|
|
||||||
* @param outputName Name of the MIDI output
|
|
||||||
* @returns Index of the new MIDI output or current output if new is not valid
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
if(typeof output === 'number') {
|
|
||||||
if (output < 0 || output >= this.midiOutputs.length) {
|
|
||||||
console.error(`Invalid MIDI output index. Index must be in the range 0-${this.midiOutputs.length - 1}.`);
|
|
||||||
return this.currentOutputIndex;
|
|
||||||
} else {
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const index = this.midiOutputs.findIndex((o) => o.name === output);
|
|
||||||
if (index !== -1) {
|
|
||||||
return index;
|
|
||||||
} else {
|
|
||||||
console.error(`MIDI output "${output}" not found.`);
|
|
||||||
return this.currentOutputIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public listMidiOutputs(): string {
|
|
||||||
/**
|
|
||||||
* Lists all available MIDI outputs to the console.
|
|
||||||
*/
|
|
||||||
let final_string = 'Available MIDI Outputs: ';
|
|
||||||
this.midiOutputs.forEach((output, index) => {
|
|
||||||
final_string += `(${index + 1}) ${output.name} `;
|
|
||||||
});
|
|
||||||
return final_string;
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendMidiNote(noteNumber: number, channel: number, velocity: number, duration: number, port: number|string = this.currentOutputIndex, bend: number|undefined = undefined): void {
|
|
||||||
/**
|
|
||||||
* Sending a MIDI Note on/off message with the same note number and channel. Automatically manages
|
|
||||||
* the note off message after the specified duration.
|
|
||||||
*
|
|
||||||
* @param noteNumber MIDI note number (0-127)
|
|
||||||
* @param channel MIDI channel (0-15)
|
|
||||||
* @param velocity MIDI velocity (0-127)
|
|
||||||
* @param duration Duration in milliseconds
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
if(typeof port === 'string') port = this.getMidiOutputIndex(port);
|
|
||||||
const output = this.midiOutputs[port];
|
|
||||||
noteNumber = Math.min(Math.max(noteNumber, 0), 127);
|
|
||||||
if (output) {
|
|
||||||
const noteOnMessage = [0x90 + channel, noteNumber, velocity];
|
|
||||||
const noteOffMessage = [0x80 + channel, noteNumber, 0];
|
|
||||||
|
|
||||||
// Send Note On
|
|
||||||
output.send(noteOnMessage);
|
|
||||||
|
|
||||||
if(bend) this.sendPitchBend(bend, channel, port);
|
|
||||||
|
|
||||||
// Schedule Note Off
|
|
||||||
const timeoutId = setTimeout(() => {
|
|
||||||
output.send(noteOffMessage);
|
|
||||||
if(bend) this.sendPitchBend(8192, channel, port);
|
|
||||||
delete this.scheduledNotes[noteNumber];
|
|
||||||
}, (duration - 0.02) * 1000);
|
|
||||||
|
|
||||||
this.scheduledNotes[noteNumber] = timeoutId;
|
|
||||||
} else {
|
|
||||||
console.error('MIDI output not available.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendSysExMessage(message: number[]): void {
|
|
||||||
/**
|
|
||||||
* Sends a SysEx message to the currently selected MIDI output.
|
|
||||||
*
|
|
||||||
* @param message Array of SysEx message bytes
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* // Send a SysEx message to set the pitch bend range to 12 semitones
|
|
||||||
* sendSysExMessage([0xF0, 0x43, 0x10, 0x4C, 0x08, 0x00, 0x01, 0x00, 0x02, 0xF7]);
|
|
||||||
*/
|
|
||||||
const output = this.midiOutputs[this.currentOutputIndex];
|
|
||||||
if (output) {
|
|
||||||
output.send(message);
|
|
||||||
} else {
|
|
||||||
console.error('MIDI output not available.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendPitchBend(value: number, channel: number, port: number|string = this.currentOutputIndex): void {
|
|
||||||
/**
|
|
||||||
* Sends a MIDI Pitch Bend message to the currently selected MIDI output.
|
|
||||||
*
|
|
||||||
* @param value MIDI pitch bend value (0-16383)
|
|
||||||
* @param channel MIDI channel (0-15)
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
if (value < 0 || value > 16383) {
|
|
||||||
console.error('Invalid pitch bend value. Value must be in the range 0-16383.');
|
|
||||||
}
|
|
||||||
if (channel < 0 || channel > 15) {
|
|
||||||
console.error('Invalid MIDI channel. Channel must be in the range 0-15.');
|
|
||||||
}
|
|
||||||
if(typeof port === 'string') port = this.getMidiOutputIndex(port);
|
|
||||||
const output = this.midiOutputs[port];
|
|
||||||
if (output) {
|
|
||||||
const lsb = value & 0x7F;
|
|
||||||
const msb = (value >> 7) & 0x7F;
|
|
||||||
output.send([0xE0 | channel, lsb, msb]);
|
|
||||||
} else {
|
|
||||||
console.error('MIDI output not available.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendProgramChange(programNumber: number, channel: number): void {
|
|
||||||
/**
|
|
||||||
* Sends a MIDI Program Change message to the currently selected MIDI output.
|
|
||||||
*
|
|
||||||
* @param programNumber MIDI program number (0-127)
|
|
||||||
* @param channel MIDI channel (0-15)
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* // Send a Program Change message to select program 1 on channel 1
|
|
||||||
* sendProgramChange(0, 0);
|
|
||||||
*/
|
|
||||||
const output = this.midiOutputs[this.currentOutputIndex];
|
|
||||||
if (output) {
|
|
||||||
output.send([0xC0 + channel, programNumber]); // Program Change
|
|
||||||
} else {
|
|
||||||
console.error('MIDI output not available.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendMidiControlChange(controlNumber: number, value: number, channel: number): void {
|
|
||||||
/**
|
|
||||||
* Sends a MIDI Control Change message to the currently selected MIDI output.
|
|
||||||
*
|
|
||||||
* @param controlNumber MIDI control number (0-127)
|
|
||||||
* @param value MIDI control value (0-127)
|
|
||||||
* @param channel MIDI channel (0-15)
|
|
||||||
*/
|
|
||||||
const output = this.midiOutputs[this.currentOutputIndex];
|
|
||||||
if (output) {
|
|
||||||
output.send([0xB0 + channel, controlNumber, value]); // Control Change
|
|
||||||
} else {
|
|
||||||
console.error('MIDI output not available.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public panic(): void {
|
|
||||||
/**
|
|
||||||
* Sends a Note Off message for all scheduled notes.
|
|
||||||
*/
|
|
||||||
const output = this.midiOutputs[this.currentOutputIndex];
|
|
||||||
if (output) {
|
|
||||||
for (const noteNumber in this.scheduledNotes) {
|
|
||||||
const timeoutId = this.scheduledNotes[noteNumber];
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
output.send([0x80, parseInt(noteNumber), 0]); // Note Off
|
|
||||||
}
|
|
||||||
this.scheduledNotes = {};
|
|
||||||
} else {
|
|
||||||
console.error('MIDI output not available.');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public listMidiOutputs(): string {
|
||||||
|
/**
|
||||||
|
* Lists all available MIDI outputs to the console.
|
||||||
|
*/
|
||||||
|
let final_string = "Available MIDI Outputs: ";
|
||||||
|
this.midiOutputs.forEach((output, index) => {
|
||||||
|
final_string += `(${index + 1}) ${output.name} `;
|
||||||
|
});
|
||||||
|
return final_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sendMidiNote(
|
||||||
|
noteNumber: number,
|
||||||
|
channel: number,
|
||||||
|
velocity: number,
|
||||||
|
duration: number,
|
||||||
|
port: number | string = this.currentOutputIndex,
|
||||||
|
bend: number | undefined = undefined
|
||||||
|
): void {
|
||||||
|
/**
|
||||||
|
* Sending a MIDI Note on/off message with the same note number and channel. Automatically manages
|
||||||
|
* the note off message after the specified duration.
|
||||||
|
*
|
||||||
|
* @param noteNumber MIDI note number (0-127)
|
||||||
|
* @param channel MIDI channel (0-15)
|
||||||
|
* @param velocity MIDI velocity (0-127)
|
||||||
|
* @param duration Duration in milliseconds
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (typeof port === "string") port = this.getMidiOutputIndex(port);
|
||||||
|
const output = this.midiOutputs[port];
|
||||||
|
noteNumber = Math.min(Math.max(noteNumber, 0), 127);
|
||||||
|
if (output) {
|
||||||
|
const noteOnMessage = [0x90 + channel, noteNumber, velocity];
|
||||||
|
const noteOffMessage = [0x80 + channel, noteNumber, 0];
|
||||||
|
|
||||||
|
// Send Note On
|
||||||
|
output.send(noteOnMessage);
|
||||||
|
|
||||||
|
if (bend) this.sendPitchBend(bend, channel, port);
|
||||||
|
|
||||||
|
// Schedule Note Off
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
output.send(noteOffMessage);
|
||||||
|
if (bend) this.sendPitchBend(8192, channel, port);
|
||||||
|
delete this.scheduledNotes[noteNumber];
|
||||||
|
}, (duration - 0.02) * 1000);
|
||||||
|
|
||||||
|
this.scheduledNotes[noteNumber] = timeoutId;
|
||||||
|
} else {
|
||||||
|
console.error("MIDI output not available.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sendSysExMessage(message: number[]): void {
|
||||||
|
/**
|
||||||
|
* Sends a SysEx message to the currently selected MIDI output.
|
||||||
|
*
|
||||||
|
* @param message Array of SysEx message bytes
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Send a SysEx message to set the pitch bend range to 12 semitones
|
||||||
|
* sendSysExMessage([0xF0, 0x43, 0x10, 0x4C, 0x08, 0x00, 0x01, 0x00, 0x02, 0xF7]);
|
||||||
|
*/
|
||||||
|
const output = this.midiOutputs[this.currentOutputIndex];
|
||||||
|
if (output) {
|
||||||
|
output.send(message);
|
||||||
|
} else {
|
||||||
|
console.error("MIDI output not available.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sendPitchBend(
|
||||||
|
value: number,
|
||||||
|
channel: number,
|
||||||
|
port: number | string = this.currentOutputIndex
|
||||||
|
): void {
|
||||||
|
/**
|
||||||
|
* Sends a MIDI Pitch Bend message to the currently selected MIDI output.
|
||||||
|
*
|
||||||
|
* @param value MIDI pitch bend value (0-16383)
|
||||||
|
* @param channel MIDI channel (0-15)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
if (value < 0 || value > 16383) {
|
||||||
|
console.error(
|
||||||
|
"Invalid pitch bend value. Value must be in the range 0-16383."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (channel < 0 || channel > 15) {
|
||||||
|
console.error("Invalid MIDI channel. Channel must be in the range 0-15.");
|
||||||
|
}
|
||||||
|
if (typeof port === "string") port = this.getMidiOutputIndex(port);
|
||||||
|
const output = this.midiOutputs[port];
|
||||||
|
if (output) {
|
||||||
|
const lsb = value & 0x7f;
|
||||||
|
const msb = (value >> 7) & 0x7f;
|
||||||
|
output.send([0xe0 | channel, lsb, msb]);
|
||||||
|
} else {
|
||||||
|
console.error("MIDI output not available.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sendProgramChange(programNumber: number, channel: number): void {
|
||||||
|
/**
|
||||||
|
* Sends a MIDI Program Change message to the currently selected MIDI output.
|
||||||
|
*
|
||||||
|
* @param programNumber MIDI program number (0-127)
|
||||||
|
* @param channel MIDI channel (0-15)
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Send a Program Change message to select program 1 on channel 1
|
||||||
|
* sendProgramChange(0, 0);
|
||||||
|
*/
|
||||||
|
const output = this.midiOutputs[this.currentOutputIndex];
|
||||||
|
if (output) {
|
||||||
|
output.send([0xc0 + channel, programNumber]); // Program Change
|
||||||
|
} else {
|
||||||
|
console.error("MIDI output not available.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sendMidiControlChange(
|
||||||
|
controlNumber: number,
|
||||||
|
value: number,
|
||||||
|
channel: number
|
||||||
|
): void {
|
||||||
|
/**
|
||||||
|
* Sends a MIDI Control Change message to the currently selected MIDI output.
|
||||||
|
*
|
||||||
|
* @param controlNumber MIDI control number (0-127)
|
||||||
|
* @param value MIDI control value (0-127)
|
||||||
|
* @param channel MIDI channel (0-15)
|
||||||
|
*/
|
||||||
|
const output = this.midiOutputs[this.currentOutputIndex];
|
||||||
|
if (output) {
|
||||||
|
output.send([0xb0 + channel, controlNumber, value]); // Control Change
|
||||||
|
} else {
|
||||||
|
console.error("MIDI output not available.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public panic(): void {
|
||||||
|
/**
|
||||||
|
* Sends a Note Off message for all scheduled notes.
|
||||||
|
*/
|
||||||
|
const output = this.midiOutputs[this.currentOutputIndex];
|
||||||
|
if (output) {
|
||||||
|
for (const noteNumber in this.scheduledNotes) {
|
||||||
|
const timeoutId = this.scheduledNotes[noteNumber];
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
output.send([0x80, parseInt(noteNumber), 0]); // Note Off
|
||||||
|
}
|
||||||
|
this.scheduledNotes = {};
|
||||||
|
} else {
|
||||||
|
console.error("MIDI output not available.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -50,25 +50,29 @@ const SCALES: Record<string, number[]> = {
|
|||||||
hindustan: [0, 2, 4, 5, 7, 8, 10],
|
hindustan: [0, 2, 4, 5, 7, 8, 10],
|
||||||
persian: [0, 1, 4, 5, 6, 8, 11],
|
persian: [0, 1, 4, 5, 6, 8, 11],
|
||||||
eastIndianPurvi: [0, 1, 4, 6, 7, 8, 11],
|
eastIndianPurvi: [0, 1, 4, 6, 7, 8, 11],
|
||||||
orientalA: [0, 1, 4, 5, 6, 9, 10]
|
orientalA: [0, 1, 4, 5, 6, 9, 10],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function scale(n: number, scaleName: string = 'major', octave: number = 4): number {
|
export function scale(
|
||||||
/**
|
n: number,
|
||||||
* Returns the MIDI note number for the given scale degree in the given scale.
|
scaleName: string = "major",
|
||||||
* @param {number} n - The scale degree, where 0 is the tonic.
|
octave: number = 4
|
||||||
* @param {string} scaleName - The name of the scale.
|
): number {
|
||||||
* @param {number} octave - The octave number.
|
/**
|
||||||
* @returns {number} The MIDI note number.
|
* Returns the MIDI note number for the given scale degree in the given scale.
|
||||||
*/
|
* @param {number} n - The scale degree, where 0 is the tonic.
|
||||||
const scale = SCALES[scaleName];
|
* @param {string} scaleName - The name of the scale.
|
||||||
|
* @param {number} octave - The octave number.
|
||||||
if (!scale) {
|
* @returns {number} The MIDI note number.
|
||||||
throw new Error(`Unknown scale ${scaleName}`);
|
*/
|
||||||
}
|
const scale = SCALES[scaleName];
|
||||||
|
|
||||||
let index = n % scale.length;
|
if (!scale) {
|
||||||
if (index < 0) index += scale.length; // adjust for negative indexes
|
throw new Error(`Unknown scale ${scaleName}`);
|
||||||
let additionalOctaves = Math.floor(n / scale.length);
|
|
||||||
return 60 + (octave + additionalOctaves) * 12 + scale[index];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let index = n % scale.length;
|
||||||
|
if (index < 0) index += scale.length; // adjust for negative indexes
|
||||||
|
let additionalOctaves = Math.floor(n / scale.length);
|
||||||
|
return 60 + (octave + additionalOctaves) * 12 + scale[index];
|
||||||
|
}
|
||||||
|
|||||||
@ -1,65 +1,63 @@
|
|||||||
export class DrunkWalk {
|
export class DrunkWalk {
|
||||||
|
/**
|
||||||
|
* A class that implements a "drunk walk" algorithm. This is useful for generating random
|
||||||
|
* numbers in a constrained range. The "drunk" starts at a position, and then makes a step
|
||||||
|
* of +1, 0, or -1. The "drunk" can be constrained to a range, and can wrap around the range.
|
||||||
|
*
|
||||||
|
* @param min - The minimum value of the range
|
||||||
|
* @param max - The maximum value of the range
|
||||||
|
* @param wrap - Whether or not the "drunk" should wrap around the range
|
||||||
|
* @param position - The starting/current position of the "drunk"
|
||||||
|
*/
|
||||||
|
|
||||||
|
public min: number;
|
||||||
|
public max: number;
|
||||||
|
private wrap: boolean;
|
||||||
|
public position: number;
|
||||||
|
|
||||||
|
constructor(min: number, max: number, wrap: boolean) {
|
||||||
|
this.min = min;
|
||||||
|
this.max = max;
|
||||||
|
this.wrap = wrap;
|
||||||
|
this.position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
step(): void {
|
||||||
/**
|
/**
|
||||||
* A class that implements a "drunk walk" algorithm. This is useful for generating random
|
* Makes a step in the "drunk walk" algorithm. This is a random step of +1, 0, or -1.
|
||||||
* numbers in a constrained range. The "drunk" starts at a position, and then makes a step
|
|
||||||
* of +1, 0, or -1. The "drunk" can be constrained to a range, and can wrap around the range.
|
|
||||||
*
|
|
||||||
* @param min - The minimum value of the range
|
|
||||||
* @param max - The maximum value of the range
|
|
||||||
* @param wrap - Whether or not the "drunk" should wrap around the range
|
|
||||||
* @param position - The starting/current position of the "drunk"
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public min: number;
|
const stepSize: number = Math.floor(Math.random() * 3) - 1;
|
||||||
public max: number;
|
this.position += stepSize;
|
||||||
private wrap: boolean;
|
|
||||||
public position: number;
|
|
||||||
|
|
||||||
constructor(min: number, max: number, wrap: boolean) {
|
if (this.wrap) {
|
||||||
this.min = min;
|
if (this.position > this.max) {
|
||||||
this.max = max;
|
this.position = this.min;
|
||||||
this.wrap = wrap;
|
} else if (this.position < this.min) {
|
||||||
this.position = 0;
|
this.position = this.max;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.position < this.min) {
|
||||||
|
this.position = this.min;
|
||||||
|
} else if (this.position > this.max) {
|
||||||
|
this.position = this.max;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
step(): void {
|
getPosition(): number {
|
||||||
|
/**
|
||||||
|
* @returns The current position of the "drunk"
|
||||||
|
*/
|
||||||
|
return this.position;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
toggleWrap(b: boolean): void {
|
||||||
* Makes a step in the "drunk walk" algorithm. This is a random step of +1, 0, or -1.
|
/**
|
||||||
*/
|
* Whether or not the "drunk" should wrap around the range
|
||||||
|
*
|
||||||
const stepSize: number = Math.floor(Math.random() * 3) - 1;
|
* @param b - Whether or not the "drunk" should wrap around the range
|
||||||
this.position += stepSize;
|
*/
|
||||||
|
this.wrap = b;
|
||||||
if (this.wrap) {
|
}
|
||||||
if (this.position > this.max) {
|
}
|
||||||
this.position = this.min;
|
|
||||||
} else if (this.position < this.min) {
|
|
||||||
this.position = this.max;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.position < this.min) {
|
|
||||||
this.position = this.min;
|
|
||||||
} else if (this.position > this.max) {
|
|
||||||
this.position = this.max;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getPosition(): number {
|
|
||||||
/**
|
|
||||||
* @returns The current position of the "drunk"
|
|
||||||
*/
|
|
||||||
return this.position;
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleWrap(b: boolean): void {
|
|
||||||
/**
|
|
||||||
* Whether or not the "drunk" should wrap around the range
|
|
||||||
*
|
|
||||||
* @param b - Whether or not the "drunk" should wrap around the range
|
|
||||||
*/
|
|
||||||
this.wrap = b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,122 +1,126 @@
|
|||||||
import { type Editor } from '../main';
|
import { type Editor } from "../main";
|
||||||
import { freqToMidi, resolvePitchBend, getScale, isScale, parseScala } from 'zifferjs';
|
import {
|
||||||
|
freqToMidi,
|
||||||
|
resolvePitchBend,
|
||||||
|
getScale,
|
||||||
|
isScale,
|
||||||
|
parseScala,
|
||||||
|
} from "zifferjs";
|
||||||
|
|
||||||
export abstract class Event {
|
export abstract class Event {
|
||||||
seedValue: string|undefined = undefined;
|
seedValue: string | undefined = undefined;
|
||||||
randomGen: Function = Math.random;
|
randomGen: Function = Math.random;
|
||||||
app: Editor;
|
app: Editor;
|
||||||
values: { [key: string]: any } = {};
|
values: { [key: string]: any } = {};
|
||||||
|
|
||||||
constructor(app: Editor) {
|
constructor(app: Editor) {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
if(this.app.api.currentSeed) {
|
if (this.app.api.currentSeed) {
|
||||||
this.randomGen = this.app.api.randomGen;
|
this.randomGen = this.app.api.randomGen;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
odds = (probability: number, func: Function): Event => {
|
odds = (probability: number, func: Function): Event => {
|
||||||
if(this.randomGen() < probability) {
|
if (this.randomGen() < probability) {
|
||||||
return this.modify(func);
|
return this.modify(func);
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
almostNever = (func: Function): Event => {
|
almostNever = (func: Function): Event => {
|
||||||
return this.odds(0.025, func);
|
return this.odds(0.025, func);
|
||||||
}
|
};
|
||||||
|
|
||||||
rarely = (func: Function): Event => {
|
rarely = (func: Function): Event => {
|
||||||
return this.odds(0.1, func);
|
return this.odds(0.1, func);
|
||||||
}
|
};
|
||||||
|
|
||||||
scarcely = (func: Function): Event => {
|
scarcely = (func: Function): Event => {
|
||||||
return this.odds(0.25, func);
|
return this.odds(0.25, func);
|
||||||
}
|
};
|
||||||
|
|
||||||
sometimes = (func: Function): Event => {
|
sometimes = (func: Function): Event => {
|
||||||
return this.odds(0.5, func);
|
return this.odds(0.5, func);
|
||||||
}
|
};
|
||||||
|
|
||||||
often = (func: Function): Event => {
|
often = (func: Function): Event => {
|
||||||
return this.odds(0.75, func);
|
return this.odds(0.75, func);
|
||||||
}
|
};
|
||||||
|
|
||||||
frequently = (func: Function): Event => {
|
frequently = (func: Function): Event => {
|
||||||
return this.odds(0.9, func);
|
return this.odds(0.9, func);
|
||||||
}
|
};
|
||||||
|
|
||||||
almostAlways = (func: Function): Event => {
|
almostAlways = (func: Function): Event => {
|
||||||
return this.odds(0.985, func);
|
return this.odds(0.985, func);
|
||||||
}
|
};
|
||||||
|
|
||||||
modify = (func: Function): Event => {
|
modify = (func: Function): Event => {
|
||||||
return func(this);
|
return func(this);
|
||||||
}
|
};
|
||||||
|
|
||||||
seed = (value: string|number): Event => {
|
seed = (value: string | number): Event => {
|
||||||
this.seedValue = value.toString();
|
this.seedValue = value.toString();
|
||||||
this.randomGen = this.app.api.localSeededRandom(this.seedValue);
|
this.randomGen = this.app.api.localSeededRandom(this.seedValue);
|
||||||
return this;
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
clear = (): Event => {
|
clear = (): Event => {
|
||||||
this.app.api.clearLocalSeed(this.seedValue);
|
this.app.api.clearLocalSeed(this.seedValue);
|
||||||
return this;
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
apply = (func: Function): Event => {
|
apply = (func: Function): Event => {
|
||||||
return this.modify(func);
|
return this.modify(func);
|
||||||
}
|
};
|
||||||
|
|
||||||
duration = (value: number): Event => {
|
|
||||||
this.values['duration'] = value;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
duration = (value: number): Event => {
|
||||||
|
this.values["duration"] = value;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class AudibleEvent extends Event {
|
export abstract class AudibleEvent extends Event {
|
||||||
constructor(app: Editor) {
|
constructor(app: Editor) {
|
||||||
super(app);
|
super(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
octave = (value: number): this => {
|
octave = (value: number): this => {
|
||||||
this.values['octave'] = value;
|
this.values["octave"] = value;
|
||||||
this.update();
|
this.update();
|
||||||
return this;
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
key = (value: string): this => {
|
key = (value: string): this => {
|
||||||
this.values['key'] = value;
|
this.values["key"] = value;
|
||||||
this.update();
|
this.update();
|
||||||
return this;
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
scale = (value: string): this => {
|
scale = (value: string): this => {
|
||||||
if(!isScale(value)) {
|
if (!isScale(value)) {
|
||||||
this.values.parsedScale = parseScala(value) as number[];
|
this.values.parsedScale = parseScala(value) as number[];
|
||||||
} else {
|
} else {
|
||||||
this.values.scaleName = value;
|
this.values.scaleName = value;
|
||||||
this.values.parsedScale = getScale(value) as number[];
|
this.values.parsedScale = getScale(value) as number[];
|
||||||
}
|
|
||||||
this.update();
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
this.update();
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
freq = (value: number): this => {
|
freq = (value: number): this => {
|
||||||
this.values['freq'] = value;
|
this.values["freq"] = value;
|
||||||
const midiNote = freqToMidi(value);
|
const midiNote = freqToMidi(value);
|
||||||
if(midiNote % 1 !== 0) {
|
if (midiNote % 1 !== 0) {
|
||||||
this.values['note'] = Math.floor(midiNote);
|
this.values["note"] = Math.floor(midiNote);
|
||||||
this.values['bend'] = resolvePitchBend(midiNote)[1];
|
this.values["bend"] = resolvePitchBend(midiNote)[1];
|
||||||
} else {
|
} else {
|
||||||
this.values['note'] = midiNote;
|
this.values["note"] = midiNote;
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
update = (): void => {
|
update = (): void => {
|
||||||
// Overwrite in subclasses
|
// Overwrite in subclasses
|
||||||
}
|
};
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@ -1,95 +1,102 @@
|
|||||||
import { AudibleEvent } from './AbstractEvents';
|
import { AudibleEvent } from "./AbstractEvents";
|
||||||
import { type Editor } from '../main';
|
import { type Editor } from "../main";
|
||||||
import { MidiConnection } from "../IO/MidiConnection";
|
import { MidiConnection } from "../IO/MidiConnection";
|
||||||
import { midiToFreq, noteFromPc } from 'zifferjs';
|
import { midiToFreq, noteFromPc } from "zifferjs";
|
||||||
|
|
||||||
export class NoteEvent extends AudibleEvent {
|
export class NoteEvent extends AudibleEvent {
|
||||||
midiConnection: MidiConnection;
|
midiConnection: MidiConnection;
|
||||||
|
|
||||||
constructor(input: number|object, public app: Editor) {
|
constructor(input: number | object, public app: Editor) {
|
||||||
super(app);
|
super(app);
|
||||||
if(typeof input === 'number') this.values['note'] = input;
|
if (typeof input === "number") this.values["note"] = input;
|
||||||
else this.values = input;
|
else this.values = input;
|
||||||
this.midiConnection = app.api.MidiConnection
|
this.midiConnection = app.api.MidiConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
note = (value: number): this => {
|
note = (value: number): this => {
|
||||||
this.values['note'] = value;
|
this.values["note"] = value;
|
||||||
return this;
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
sustain = (value: number): this => {
|
sustain = (value: number): this => {
|
||||||
this.values['sustain'] = value;
|
this.values["sustain"] = value;
|
||||||
return this;
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
channel = (value: number): this => {
|
channel = (value: number): this => {
|
||||||
this.values['channel'] = value;
|
this.values["channel"] = value;
|
||||||
return this;
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
port = (value: number|string): this => {
|
port = (value: number | string): this => {
|
||||||
this.values['port'] = this.midiConnection.getMidiOutputIndex(value);
|
this.values["port"] = this.midiConnection.getMidiOutputIndex(value);
|
||||||
return this;
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
add = (value: number): this => {
|
add = (value: number): this => {
|
||||||
this.values.note += value;
|
this.values.note += value;
|
||||||
return this;
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
modify = (func: Function): this => {
|
modify = (func: Function): this => {
|
||||||
const funcResult = func(this);
|
const funcResult = func(this);
|
||||||
if(funcResult instanceof Object) {
|
if (funcResult instanceof Object) {
|
||||||
return funcResult;
|
return funcResult;
|
||||||
}
|
} else {
|
||||||
else {
|
func(this.values);
|
||||||
func(this.values);
|
this.update();
|
||||||
this.update();
|
return this;
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
bend = (value: number): this => {
|
bend = (value: number): this => {
|
||||||
this.values['bend'] = value;
|
this.values["bend"] = value;
|
||||||
return this;
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
random = (min: number = 0, max: number = 127): this => {
|
|
||||||
min = Math.min(Math.max(min, 0), 127);
|
|
||||||
max = Math.min(Math.max(max, 0), 127);
|
|
||||||
this.values['note'] = Math.floor(this.randomGen() * (max - min + 1)) + min;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
update = (): void => {
|
random = (min: number = 0, max: number = 127): this => {
|
||||||
const [note, bend] = noteFromPc(
|
min = Math.min(Math.max(min, 0), 127);
|
||||||
this.values.key || "C4",
|
max = Math.min(Math.max(max, 0), 127);
|
||||||
this.values.pitch || 0,
|
this.values["note"] = Math.floor(this.randomGen() * (max - min + 1)) + min;
|
||||||
this.values.parsedScale || "MAJOR",
|
return this;
|
||||||
this.values.octave || 0
|
};
|
||||||
);
|
|
||||||
this.values.note = note;
|
|
||||||
this.values.freq = midiToFreq(note);
|
|
||||||
if(bend) this.values.bend = bend;
|
|
||||||
}
|
|
||||||
|
|
||||||
out = (): void => {
|
update = (): void => {
|
||||||
const note = this.values.note ? this.values.note : 60;
|
const [note, bend] = noteFromPc(
|
||||||
const channel = this.values.channel ? this.values.channel : 0;
|
this.values.key || "C4",
|
||||||
const velocity = this.values.velocity ? this.values.velocity : 100;
|
this.values.pitch || 0,
|
||||||
|
this.values.parsedScale || "MAJOR",
|
||||||
const sustain = this.values.sustain ?
|
this.values.octave || 0
|
||||||
this.values.sustain * this.app.clock.pulse_duration * this.app.api.ppqn() :
|
);
|
||||||
this.app.clock.pulse_duration * this.app.api.ppqn();
|
this.values.note = note;
|
||||||
|
this.values.freq = midiToFreq(note);
|
||||||
const bend = this.values.bend ? this.values.bend : undefined;
|
if (bend) this.values.bend = bend;
|
||||||
|
};
|
||||||
const port = this.values.port ?
|
|
||||||
this.midiConnection.getMidiOutputIndex(this.values.port) :
|
|
||||||
this.midiConnection.getCurrentMidiPortIndex();
|
|
||||||
|
|
||||||
this.midiConnection.sendMidiNote(note, channel, velocity, sustain, port, bend);
|
out = (): void => {
|
||||||
}
|
const note = this.values.note ? this.values.note : 60;
|
||||||
|
const channel = this.values.channel ? this.values.channel : 0;
|
||||||
}
|
const velocity = this.values.velocity ? this.values.velocity : 100;
|
||||||
|
|
||||||
|
const sustain = this.values.sustain
|
||||||
|
? this.values.sustain *
|
||||||
|
this.app.clock.pulse_duration *
|
||||||
|
this.app.api.ppqn()
|
||||||
|
: this.app.clock.pulse_duration * this.app.api.ppqn();
|
||||||
|
|
||||||
|
const bend = this.values.bend ? this.values.bend : undefined;
|
||||||
|
|
||||||
|
const port = this.values.port
|
||||||
|
? this.midiConnection.getMidiOutputIndex(this.values.port)
|
||||||
|
: this.midiConnection.getCurrentMidiPortIndex();
|
||||||
|
|
||||||
|
this.midiConnection.sendMidiNote(
|
||||||
|
note,
|
||||||
|
channel,
|
||||||
|
velocity,
|
||||||
|
sustain,
|
||||||
|
port,
|
||||||
|
bend
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -1,37 +1,39 @@
|
|||||||
import { type Editor } from '../main';
|
import { type Editor } from "../main";
|
||||||
import { Event } from "./AbstractEvents";
|
import { Event } from "./AbstractEvents";
|
||||||
|
|
||||||
export class RestEvent extends Event {
|
export class RestEvent extends Event {
|
||||||
constructor(duration: number, app: Editor) {
|
constructor(duration: number, app: Editor) {
|
||||||
super(app);
|
super(app);
|
||||||
this.values["duration"] = duration;
|
this.values["duration"] = duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
_fallbackMethod = (): Event => {
|
_fallbackMethod = (): Event => {
|
||||||
return RestEvent.createRestProxy(this.values["duration"], this.app);
|
return RestEvent.createRestProxy(this.values["duration"], this.app);
|
||||||
}
|
};
|
||||||
|
|
||||||
public static createRestProxy = (duration: number, app: Editor): RestEvent => {
|
public static createRestProxy = (
|
||||||
const instance = new RestEvent(duration, app);
|
duration: number,
|
||||||
return new Proxy(instance, {
|
app: Editor
|
||||||
// @ts-ignore
|
): RestEvent => {
|
||||||
get(target, propKey, receiver) {
|
const instance = new RestEvent(duration, app);
|
||||||
// @ts-ignore
|
return new Proxy(instance, {
|
||||||
if (typeof target[propKey] === 'undefined') {
|
// @ts-ignore
|
||||||
return target._fallbackMethod;
|
get(target, propKey, receiver) {
|
||||||
}
|
// @ts-ignore
|
||||||
// @ts-ignore
|
if (typeof target[propKey] === "undefined") {
|
||||||
return target[propKey];
|
return target._fallbackMethod;
|
||||||
},
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
set(target, propKey, value, receiver) {
|
return target[propKey];
|
||||||
return false;
|
},
|
||||||
}
|
// @ts-ignore
|
||||||
});
|
set(target, propKey, value, receiver) {
|
||||||
}
|
return false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
out = (): void => {
|
out = (): void => {
|
||||||
// TODO?
|
// TODO?
|
||||||
}
|
};
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@ -1,28 +1,26 @@
|
|||||||
export class SkipEvent {
|
export class SkipEvent {
|
||||||
|
_fallbackMethod = (): SkipEvent => {
|
||||||
|
return SkipEvent.createSkipProxy();
|
||||||
|
};
|
||||||
|
|
||||||
_fallbackMethod = (): SkipEvent => {
|
public static createSkipProxy = (): SkipEvent => {
|
||||||
return SkipEvent.createSkipProxy();
|
const instance = new SkipEvent();
|
||||||
}
|
return new Proxy(instance, {
|
||||||
|
// @ts-ignore
|
||||||
public static createSkipProxy = (): SkipEvent => {
|
get(target, propKey, receiver) {
|
||||||
const instance = new SkipEvent();
|
// @ts-ignore
|
||||||
return new Proxy(instance, {
|
if (typeof target[propKey] === "undefined") {
|
||||||
// @ts-ignore
|
return target._fallbackMethod;
|
||||||
get(target, propKey, receiver) {
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (typeof target[propKey] === 'undefined') {
|
return target[propKey];
|
||||||
return target._fallbackMethod;
|
},
|
||||||
}
|
// @ts-ignore
|
||||||
// @ts-ignore
|
set(target, propKey, value, receiver) {
|
||||||
return target[propKey];
|
return false;
|
||||||
},
|
},
|
||||||
// @ts-ignore
|
});
|
||||||
set(target, propKey, value, receiver) {
|
};
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
out = (): void => {}
|
out = (): void => {};
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@ -238,13 +238,13 @@ export class SoundEvent extends AudibleEvent {
|
|||||||
sus = this.sustain;
|
sus = this.sustain;
|
||||||
|
|
||||||
update = (): void => {
|
update = (): void => {
|
||||||
const [note, _] = noteFromPc(
|
const [note, _] = noteFromPc(
|
||||||
this.values.key || "C4",
|
this.values.key || "C4",
|
||||||
this.values.pitch || 0,
|
this.values.pitch || 0,
|
||||||
this.values.parsedScale || "MAJOR",
|
this.values.parsedScale || "MAJOR",
|
||||||
this.values.octave || 0
|
this.values.octave || 0
|
||||||
);
|
);
|
||||||
this.values.freq = midiToFreq(note);
|
this.values.freq = midiToFreq(note);
|
||||||
};
|
};
|
||||||
|
|
||||||
out = (): object => {
|
out = (): object => {
|
||||||
|
|||||||
@ -9,194 +9,204 @@ import { RestEvent } from "./RestEvent";
|
|||||||
export type InputOptions = { [key: string]: string | number };
|
export type InputOptions = { [key: string]: string | number };
|
||||||
|
|
||||||
export class Player extends Event {
|
export class Player extends Event {
|
||||||
input: string;
|
input: string;
|
||||||
ziffers: Ziffers;
|
ziffers: Ziffers;
|
||||||
initCallTime: number = 1;
|
initCallTime: number = 1;
|
||||||
startCallTime: number = 1;
|
startCallTime: number = 1;
|
||||||
lastCallTime: number = 1;
|
lastCallTime: number = 1;
|
||||||
waitTime = 0;
|
waitTime = 0;
|
||||||
startBeat: number = 0;
|
startBeat: number = 0;
|
||||||
played: boolean = false;
|
played: boolean = false;
|
||||||
current!: Pitch|Chord|ZRest;
|
current!: Pitch | Chord | ZRest;
|
||||||
retro: boolean = false;
|
retro: boolean = false;
|
||||||
index: number = -1;
|
index: number = -1;
|
||||||
zid: string = "";
|
zid: string = "";
|
||||||
options: InputOptions = {};
|
options: InputOptions = {};
|
||||||
skipIndex = 0;
|
skipIndex = 0;
|
||||||
endTime = 0;
|
endTime = 0;
|
||||||
|
|
||||||
constructor(input: string, options: InputOptions, public app: Editor) {
|
constructor(input: string, options: InputOptions, public app: Editor) {
|
||||||
super(app);
|
super(app);
|
||||||
this.input = input;
|
this.input = input;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.ziffers = new Ziffers(input, options);
|
this.ziffers = new Ziffers(input, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
get ticks(): number {
|
||||||
|
const dur = this.ziffers.duration;
|
||||||
|
return dur * 4 * this.app.clock.ppqn;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextEndTime(): number {
|
||||||
|
return this.startCallTime + this.ticks;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLastCallTime(): void {
|
||||||
|
if (this.notStarted() || this.played) {
|
||||||
|
this.lastCallTime = this.app.clock.pulses_since_origin;
|
||||||
|
this.played = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notStarted(): boolean {
|
||||||
|
return this.ziffers.notStarted();
|
||||||
|
}
|
||||||
|
|
||||||
|
next = (): Pitch | Chord | ZRest => {
|
||||||
|
this.current = this.ziffers.next() as Pitch | Chord | ZRest;
|
||||||
|
this.played = true;
|
||||||
|
return this.current;
|
||||||
|
};
|
||||||
|
|
||||||
|
pulseToSecond = (pulse: number): number => {
|
||||||
|
return this.app.clock.convertPulseToSecond(pulse);
|
||||||
|
};
|
||||||
|
|
||||||
|
firstRun = (): boolean => {
|
||||||
|
return this.notStarted();
|
||||||
|
};
|
||||||
|
|
||||||
|
atTheBeginning = (): boolean => {
|
||||||
|
return this.skipIndex === 0 && this.ziffers.index <= 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
origin = (): number => {
|
||||||
|
return this.app.clock.pulses_since_origin;
|
||||||
|
};
|
||||||
|
|
||||||
|
pulse = (): number => {
|
||||||
|
return this.app.clock.time_position.pulse;
|
||||||
|
};
|
||||||
|
|
||||||
|
beat = (): number => {
|
||||||
|
return this.app.clock.time_position.beat;
|
||||||
|
};
|
||||||
|
|
||||||
|
nextBeat = (): number => {
|
||||||
|
return this.app.clock.next_beat_in_ticks;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if it's time to play the event
|
||||||
|
areWeThereYet = (): boolean => {
|
||||||
|
// If clock has stopped
|
||||||
|
if (this.app.clock.pulses_since_origin < this.lastCallTime) {
|
||||||
|
this.lastCallTime = 1;
|
||||||
|
this.startCallTime = 1;
|
||||||
|
this.index = 0;
|
||||||
|
this.waitTime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
get ticks(): number {
|
// Main logic
|
||||||
const dur = this.ziffers.duration;
|
const howAboutNow =
|
||||||
return dur * 4 * this.app.clock.ppqn;
|
// If pattern is just starting
|
||||||
|
(this.notStarted() &&
|
||||||
|
(this.app.clock.time_position.pulse === 1 ||
|
||||||
|
this.app.clock.pulses_since_origin >=
|
||||||
|
this.app.clock.next_beat_in_ticks) &&
|
||||||
|
this.app.clock.pulses_since_origin >= this.waitTime) || // If pattern is already playing
|
||||||
|
(this.current &&
|
||||||
|
this.pulseToSecond(this.app.clock.pulses_since_origin) >=
|
||||||
|
this.pulseToSecond(this.lastCallTime) +
|
||||||
|
this.current.duration *
|
||||||
|
4 *
|
||||||
|
this.pulseToSecond(this.app.api.ppqn()) &&
|
||||||
|
this.app.clock.pulses_since_origin >= this.waitTime);
|
||||||
|
|
||||||
|
// Increment index of how many times call is skipped
|
||||||
|
this.skipIndex = howAboutNow ? 0 : this.skipIndex + 1;
|
||||||
|
|
||||||
|
// Increment index of how many times sound/midi have been called
|
||||||
|
this.index = howAboutNow ? this.index + 1 : this.index;
|
||||||
|
|
||||||
|
if (howAboutNow && this.notStarted()) {
|
||||||
|
this.initCallTime = this.app.clock.pulses_since_origin;
|
||||||
}
|
}
|
||||||
|
|
||||||
nextEndTime(): number {
|
if (this.atTheBeginning()) {
|
||||||
return this.startCallTime + this.ticks;
|
this.startCallTime = this.app.clock.pulses_since_origin;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLastCallTime(): void {
|
return howAboutNow;
|
||||||
if (this.notStarted() || this.played) {
|
};
|
||||||
this.lastCallTime = this.app.clock.pulses_since_origin;
|
|
||||||
this.played = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
notStarted(): boolean {
|
sound(name: string) {
|
||||||
return this.ziffers.notStarted();
|
if (this.areWeThereYet()) {
|
||||||
}
|
const event = this.next() as Pitch | Chord | ZRest;
|
||||||
|
if (event instanceof Pitch) {
|
||||||
next = (): Pitch|Chord|ZRest => {
|
const obj = event.getExisting(
|
||||||
this.current = this.ziffers.next() as Pitch|Chord|ZRest;
|
"freq",
|
||||||
this.played = true;
|
"pitch",
|
||||||
return this.current;
|
"key",
|
||||||
}
|
"scale",
|
||||||
|
"octave",
|
||||||
pulseToSecond = (pulse: number): number => {
|
"parsedScale"
|
||||||
return this.app.clock.convertPulseToSecond(pulse);
|
|
||||||
}
|
|
||||||
|
|
||||||
firstRun = (): boolean => {
|
|
||||||
return this.notStarted();
|
|
||||||
}
|
|
||||||
|
|
||||||
atTheBeginning = (): boolean => {
|
|
||||||
return this.skipIndex === 0 && this.ziffers.index<=0;
|
|
||||||
}
|
|
||||||
|
|
||||||
origin = (): number => {
|
|
||||||
return this.app.clock.pulses_since_origin;
|
|
||||||
}
|
|
||||||
|
|
||||||
pulse = (): number => {
|
|
||||||
return this.app.clock.time_position.pulse;
|
|
||||||
}
|
|
||||||
|
|
||||||
beat = (): number => {
|
|
||||||
return this.app.clock.time_position.beat;
|
|
||||||
}
|
|
||||||
|
|
||||||
nextBeat = (): number => {
|
|
||||||
return this.app.clock.next_beat_in_ticks;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if it's time to play the event
|
|
||||||
areWeThereYet = (): boolean => {
|
|
||||||
|
|
||||||
// If clock has stopped
|
|
||||||
if(this.app.clock.pulses_since_origin<this.lastCallTime) {
|
|
||||||
this.lastCallTime = 1;
|
|
||||||
this.startCallTime = 1;
|
|
||||||
this.index = 0;
|
|
||||||
this.waitTime = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main logic
|
|
||||||
const howAboutNow = (
|
|
||||||
( // If pattern is just starting
|
|
||||||
this.notStarted() &&
|
|
||||||
(this.app.clock.time_position.pulse === 1 ||
|
|
||||||
this.app.clock.pulses_since_origin >= this.app.clock.next_beat_in_ticks) &&
|
|
||||||
(this.app.clock.pulses_since_origin >= this.waitTime)
|
|
||||||
)
|
|
||||||
||
|
|
||||||
( // If pattern is already playing
|
|
||||||
this.current &&
|
|
||||||
(this.pulseToSecond(this.app.clock.pulses_since_origin) >=
|
|
||||||
this.pulseToSecond(this.lastCallTime) +
|
|
||||||
(this.current.duration*4) * this.pulseToSecond(this.app.api.ppqn())) &&
|
|
||||||
(this.app.clock.pulses_since_origin >= this.waitTime)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
return new SoundEvent(obj, this.app).sound(name);
|
||||||
// Increment index of how many times call is skipped
|
} else if (event instanceof ZRest) {
|
||||||
this.skipIndex = howAboutNow ? 0 : this.skipIndex+1;
|
return RestEvent.createRestProxy(event.duration, this.app);
|
||||||
|
}
|
||||||
// Increment index of how many times sound/midi have been called
|
} else {
|
||||||
this.index = howAboutNow ? this.index+1 : this.index;
|
return SkipEvent.createSkipProxy();
|
||||||
|
|
||||||
|
|
||||||
if(howAboutNow && this.notStarted()) {
|
|
||||||
this.initCallTime = this.app.clock.pulses_since_origin;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.atTheBeginning()) {
|
|
||||||
this.startCallTime = this.app.clock.pulses_since_origin;
|
|
||||||
}
|
|
||||||
|
|
||||||
return howAboutNow;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sound(name: string) {
|
midi(value: number | undefined = undefined) {
|
||||||
if(this.areWeThereYet()) {
|
if (this.areWeThereYet()) {
|
||||||
const event = this.next() as Pitch|Chord|ZRest;
|
const event = this.next() as Pitch | Chord | ZRest;
|
||||||
if(event instanceof Pitch) {
|
if (event instanceof Pitch) {
|
||||||
const obj = event.getExisting("freq","pitch","key","scale","octave","parsedScale");
|
const obj = event.getExisting(
|
||||||
return new SoundEvent(obj, this.app).sound(name);
|
"note",
|
||||||
} else if(event instanceof ZRest) {
|
"pitch",
|
||||||
return RestEvent.createRestProxy(event.duration, this.app);
|
"bend",
|
||||||
}
|
"key",
|
||||||
} else {
|
"scale",
|
||||||
return SkipEvent.createSkipProxy();
|
"octave",
|
||||||
}
|
"parsedScale"
|
||||||
|
);
|
||||||
|
const note = new NoteEvent(obj, this.app);
|
||||||
|
return value ? note.note(value) : note;
|
||||||
|
} else if (event instanceof ZRest) {
|
||||||
|
return RestEvent.createRestProxy(event.duration, this.app);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return SkipEvent.createSkipProxy();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
midi(value: number|undefined = undefined) {
|
scale(name: string) {
|
||||||
if(this.areWeThereYet()) {
|
if (this.atTheBeginning()) this.ziffers.scale(name);
|
||||||
const event = this.next() as Pitch|Chord|ZRest;
|
return this;
|
||||||
if(event instanceof Pitch) {
|
}
|
||||||
const obj = event.getExisting("note","pitch","bend","key","scale","octave","parsedScale");
|
|
||||||
const note = new NoteEvent(obj, this.app);
|
|
||||||
return value ? note.note(value) : note;
|
|
||||||
} else if(event instanceof ZRest) {
|
|
||||||
return RestEvent.createRestProxy(event.duration, this.app);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return SkipEvent.createSkipProxy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scale(name: string) {
|
key(name: string) {
|
||||||
if(this.atTheBeginning()) this.ziffers.scale(name);
|
if (this.atTheBeginning()) this.ziffers.key(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
octave(value: number) {
|
||||||
|
if (this.atTheBeginning()) this.ziffers.octave(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
retrograde() {
|
||||||
|
if (this.atTheBeginning()) this.ziffers.retrograde();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
wait(value: number | Function) {
|
||||||
|
if (this.atTheBeginning()) {
|
||||||
|
if (typeof value === "function") {
|
||||||
|
const refPat = this.app.api.patternCache.get(value.name) as Player;
|
||||||
|
if (refPat) this.waitTime = refPat.nextEndTime();
|
||||||
return this;
|
return this;
|
||||||
|
}
|
||||||
|
this.waitTime =
|
||||||
|
this.origin() + Math.ceil(value * 4 * this.app.clock.ppqn);
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
key(name: string) {
|
out = (): void => {
|
||||||
if(this.atTheBeginning()) this.ziffers.key(name);
|
// TODO?
|
||||||
return this;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
octave(value: number) {
|
|
||||||
if(this.atTheBeginning()) this.ziffers.octave(value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
retrograde() {
|
|
||||||
if(this.atTheBeginning()) this.ziffers.retrograde();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
wait(value: number|Function) {
|
|
||||||
if(this.atTheBeginning()) {
|
|
||||||
if(typeof value === "function") {
|
|
||||||
const refPat = this.app.api.patternCache.get(value.name) as Player;
|
|
||||||
if(refPat) this.waitTime = refPat.nextEndTime();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
this.waitTime = this.origin() + Math.ceil(value*4*this.app.clock.ppqn);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
out = (): void => {
|
|
||||||
// TODO?
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
222
src/main.ts
222
src/main.ts
@ -1,8 +1,4 @@
|
|||||||
import {
|
import { uniqueNamesGenerator, colors, animals } from "unique-names-generator";
|
||||||
uniqueNamesGenerator,
|
|
||||||
colors,
|
|
||||||
animals,
|
|
||||||
} from "unique-names-generator";
|
|
||||||
import { examples } from "./examples/excerpts";
|
import { examples } from "./examples/excerpts";
|
||||||
import { EditorState, Compartment } from "@codemirror/state";
|
import { EditorState, Compartment } from "@codemirror/state";
|
||||||
import { ViewUpdate, lineNumbers, keymap } from "@codemirror/view";
|
import { ViewUpdate, lineNumbers, keymap } from "@codemirror/view";
|
||||||
@ -28,7 +24,7 @@ import {
|
|||||||
} from "./AppSettings";
|
} from "./AppSettings";
|
||||||
import { tryEvaluate } from "./Evaluator";
|
import { tryEvaluate } from "./Evaluator";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { gzipSync, decompressSync, strFromU8 } from 'fflate';
|
import { gzipSync, decompressSync, strFromU8 } from "fflate";
|
||||||
|
|
||||||
// Importing showdown and setting up the markdown converter
|
// Importing showdown and setting up the markdown converter
|
||||||
import showdown from "showdown";
|
import showdown from "showdown";
|
||||||
@ -47,7 +43,7 @@ const classMap = {
|
|||||||
blockquote: "text-neutral-200 border-l-4 border-neutral-500 pl-4 my-4 mx-4",
|
blockquote: "text-neutral-200 border-l-4 border-neutral-500 pl-4 my-4 mx-4",
|
||||||
details:
|
details:
|
||||||
"lg:mx-12 py-2 px-6 lg:text-2xl text-white rounded-lg bg-neutral-600",
|
"lg:mx-12 py-2 px-6 lg:text-2xl text-white rounded-lg bg-neutral-600",
|
||||||
summary: "font-semibold text-xl",
|
summary: "font-semibold text-xl",
|
||||||
table:
|
table:
|
||||||
"justify-center lg:my-8 my-2 lg:mx-8 mx-2 lg:text-2xl text-base w-full text-left text-white border-collapse",
|
"justify-center lg:my-8 my-2 lg:mx-8 mx-2 lg:text-2xl text-base w-full text-left text-white border-collapse",
|
||||||
thead:
|
thead:
|
||||||
@ -87,14 +83,16 @@ export class Editor {
|
|||||||
view: EditorView;
|
view: EditorView;
|
||||||
clock: Clock;
|
clock: Clock;
|
||||||
manualPlay: boolean = false;
|
manualPlay: boolean = false;
|
||||||
isPlaying: boolean = false;
|
isPlaying: boolean = false;
|
||||||
|
|
||||||
// Mouse position
|
// Mouse position
|
||||||
public _mouseX: number = 0;
|
public _mouseX: number = 0;
|
||||||
public _mouseY: number = 0;
|
public _mouseY: number = 0;
|
||||||
|
|
||||||
// Topos Logo
|
// Topos Logo
|
||||||
topos_logo: HTMLElement = document.getElementById('topos-logo') as HTMLElement;
|
topos_logo: HTMLElement = document.getElementById(
|
||||||
|
"topos-logo"
|
||||||
|
) as HTMLElement;
|
||||||
|
|
||||||
// Transport elements
|
// Transport elements
|
||||||
play_buttons: HTMLButtonElement[] = [
|
play_buttons: HTMLButtonElement[] = [
|
||||||
@ -108,7 +106,9 @@ export class Editor {
|
|||||||
document.getElementById("clear-button-1") as HTMLButtonElement,
|
document.getElementById("clear-button-1") as HTMLButtonElement,
|
||||||
//document.getElementById("clear-button-2") as HTMLButtonElement,
|
//document.getElementById("clear-button-2") as HTMLButtonElement,
|
||||||
];
|
];
|
||||||
load_universe_button: HTMLButtonElement = document.getElementById("load-universe-button") as HTMLButtonElement;
|
load_universe_button: HTMLButtonElement = document.getElementById(
|
||||||
|
"load-universe-button"
|
||||||
|
) as HTMLButtonElement;
|
||||||
|
|
||||||
documentation_button: HTMLButtonElement = document.getElementById(
|
documentation_button: HTMLButtonElement = document.getElementById(
|
||||||
"doc-button-1"
|
"doc-button-1"
|
||||||
@ -201,10 +201,10 @@ export class Editor {
|
|||||||
this.selected_universe = "Welcome";
|
this.selected_universe = "Welcome";
|
||||||
this.universe_viewer.innerHTML = `Topos: ${this.selected_universe}`;
|
this.universe_viewer.innerHTML = `Topos: ${this.selected_universe}`;
|
||||||
|
|
||||||
// Picking a random example to populate the landing page
|
// Picking a random example to populate the landing page
|
||||||
let random_example = examples[Math.floor(Math.random() * examples.length)];
|
let random_example = examples[Math.floor(Math.random() * examples.length)];
|
||||||
this.universes[this.selected_universe].global.committed = random_example;
|
this.universes[this.selected_universe].global.committed = random_example;
|
||||||
this.universes[this.selected_universe].global.candidate = random_example;
|
this.universes[this.selected_universe].global.candidate = random_example;
|
||||||
|
|
||||||
// ================================================================================
|
// ================================================================================
|
||||||
// Audio context and clock
|
// Audio context and clock
|
||||||
@ -255,9 +255,11 @@ export class Editor {
|
|||||||
|
|
||||||
// ================================================================================
|
// ================================================================================
|
||||||
// Building the documentation
|
// Building the documentation
|
||||||
let pre_loading = async () => { await loadSamples(); };
|
let pre_loading = async () => {
|
||||||
pre_loading();
|
await loadSamples();
|
||||||
this.docs = documentation_factory(this);
|
};
|
||||||
|
pre_loading();
|
||||||
|
this.docs = documentation_factory(this);
|
||||||
// ================================================================================
|
// ================================================================================
|
||||||
|
|
||||||
// ================================================================================
|
// ================================================================================
|
||||||
@ -265,7 +267,6 @@ export class Editor {
|
|||||||
// ================================================================================
|
// ================================================================================
|
||||||
|
|
||||||
window.addEventListener("keydown", (event: KeyboardEvent) => {
|
window.addEventListener("keydown", (event: KeyboardEvent) => {
|
||||||
|
|
||||||
if (event.key === "Tab") {
|
if (event.key === "Tab") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
@ -278,15 +279,15 @@ export class Editor {
|
|||||||
|
|
||||||
if (event.ctrlKey && event.key === "p") {
|
if (event.ctrlKey && event.key === "p") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (this.isPlaying) {
|
if (this.isPlaying) {
|
||||||
this.isPlaying = false;
|
this.isPlaying = false;
|
||||||
this.setButtonHighlighting("pause", true);
|
this.setButtonHighlighting("pause", true);
|
||||||
this.clock.pause();
|
this.clock.pause();
|
||||||
} else {
|
} else {
|
||||||
this.isPlaying = true;
|
this.isPlaying = true;
|
||||||
this.setButtonHighlighting("play", true);
|
this.setButtonHighlighting("play", true);
|
||||||
this.clock.start();
|
this.clock.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl + Shift + V: Vim Mode
|
// Ctrl + Shift + V: Vim Mode
|
||||||
@ -324,7 +325,7 @@ export class Editor {
|
|||||||
if (event.ctrlKey && event.key === "b") {
|
if (event.ctrlKey && event.key === "b") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.hideDocumentation();
|
this.hideDocumentation();
|
||||||
this.updateKnownUniversesView();
|
this.updateKnownUniversesView();
|
||||||
this.openBuffersModal();
|
this.openBuffersModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,10 +373,10 @@ export class Editor {
|
|||||||
if (event.keyCode === keycode) {
|
if (event.keyCode === keycode) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (event.ctrlKey) {
|
if (event.ctrlKey) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.api.script(keycode - 111);
|
this.api.script(keycode - 111);
|
||||||
} else {
|
} else {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.changeModeFromInterface("local");
|
this.changeModeFromInterface("local");
|
||||||
this.changeToLocalBuffer(index);
|
this.changeToLocalBuffer(index);
|
||||||
this.hideDocumentation();
|
this.hideDocumentation();
|
||||||
@ -385,12 +386,12 @@ export class Editor {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (event.keyCode == 121) {
|
if (event.keyCode == 121) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.changeModeFromInterface("global");
|
this.changeModeFromInterface("global");
|
||||||
this.hideDocumentation();
|
this.hideDocumentation();
|
||||||
}
|
}
|
||||||
if (event.keyCode == 122) {
|
if (event.keyCode == 122) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.changeModeFromInterface("init");
|
this.changeModeFromInterface("init");
|
||||||
this.hideDocumentation();
|
this.hideDocumentation();
|
||||||
}
|
}
|
||||||
@ -418,24 +419,24 @@ export class Editor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.topos_logo.addEventListener("click", () => {
|
this.topos_logo.addEventListener("click", () => {
|
||||||
this.hideDocumentation();
|
this.hideDocumentation();
|
||||||
this.updateKnownUniversesView();
|
this.updateKnownUniversesView();
|
||||||
this.openBuffersModal();
|
this.openBuffersModal();
|
||||||
})
|
});
|
||||||
|
|
||||||
this.play_buttons.forEach((button) => {
|
this.play_buttons.forEach((button) => {
|
||||||
button.addEventListener("click", () => {
|
button.addEventListener("click", () => {
|
||||||
if (this.isPlaying) {
|
if (this.isPlaying) {
|
||||||
this.setButtonHighlighting("pause", true);
|
this.setButtonHighlighting("pause", true);
|
||||||
this.isPlaying = !this.isPlaying;
|
this.isPlaying = !this.isPlaying;
|
||||||
this.clock.pause();
|
this.clock.pause();
|
||||||
} else {
|
} else {
|
||||||
this.setButtonHighlighting("play", true);
|
this.setButtonHighlighting("play", true);
|
||||||
this.isPlaying = !this.isPlaying;
|
this.isPlaying = !this.isPlaying;
|
||||||
this.clock.start();
|
this.clock.start();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.clear_buttons.forEach((button) => {
|
this.clear_buttons.forEach((button) => {
|
||||||
@ -452,17 +453,16 @@ export class Editor {
|
|||||||
this.showDocumentation();
|
this.showDocumentation();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
this.load_universe_button.addEventListener("click", () => {
|
this.load_universe_button.addEventListener("click", () => {
|
||||||
let query = this.buffer_search.value;
|
let query = this.buffer_search.value;
|
||||||
if (query.length > 2 && query.length < 20 && !query.includes(" ")) {
|
if (query.length > 2 && query.length < 20 && !query.includes(" ")) {
|
||||||
this.loadUniverse(query);
|
this.loadUniverse(query);
|
||||||
this.settings.selected_universe = query;
|
this.settings.selected_universe = query;
|
||||||
this.buffer_search.value = "";
|
this.buffer_search.value = "";
|
||||||
this.closeBuffersModal();
|
this.closeBuffersModal();
|
||||||
this.view.focus();
|
this.view.focus();
|
||||||
this.emptyUrl();
|
this.emptyUrl();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.eval_button.addEventListener("click", () => {
|
this.eval_button.addEventListener("click", () => {
|
||||||
@ -470,7 +470,6 @@ export class Editor {
|
|||||||
this.flashBackground("#2d313d", 200);
|
this.flashBackground("#2d313d", 200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
this.stop_buttons.forEach((button) => {
|
this.stop_buttons.forEach((button) => {
|
||||||
button.addEventListener("click", () => {
|
button.addEventListener("click", () => {
|
||||||
this.setButtonHighlighting("stop", true);
|
this.setButtonHighlighting("stop", true);
|
||||||
@ -514,7 +513,7 @@ export class Editor {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.close_universes_button.addEventListener("click", () => {
|
this.close_universes_button.addEventListener("click", () => {
|
||||||
this.openBuffersModal();
|
this.openBuffersModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.font_size_slider.addEventListener("input", () => {
|
this.font_size_slider.addEventListener("input", () => {
|
||||||
@ -565,13 +564,12 @@ export class Editor {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.universe_creator.addEventListener("submit", (event) => {
|
this.universe_creator.addEventListener("submit", (event) => {
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
let data = new FormData(this.universe_creator);
|
let data = new FormData(this.universe_creator);
|
||||||
let universeName = data.get("universe") as string|null;
|
let universeName = data.get("universe") as string | null;
|
||||||
|
|
||||||
if(universeName){
|
if (universeName) {
|
||||||
if (universeName.length > 2 && universeName.length < 20) {
|
if (universeName.length > 2 && universeName.length < 20) {
|
||||||
this.loadUniverse(universeName);
|
this.loadUniverse(universeName);
|
||||||
this.settings.selected_universe = universeName;
|
this.settings.selected_universe = universeName;
|
||||||
@ -603,17 +601,17 @@ export class Editor {
|
|||||||
].forEach((e) => {
|
].forEach((e) => {
|
||||||
let name = `docs_` + e;
|
let name = `docs_` + e;
|
||||||
document.getElementById(name)!.addEventListener("click", async () => {
|
document.getElementById(name)!.addEventListener("click", async () => {
|
||||||
if (name !== "docs_samples") {
|
if (name !== "docs_samples") {
|
||||||
this.currentDocumentationPane = e;
|
this.currentDocumentationPane = e;
|
||||||
this.updateDocumentationContent();
|
this.updateDocumentationContent();
|
||||||
} else {
|
} else {
|
||||||
console.log('Loading samples!');
|
console.log("Loading samples!");
|
||||||
await loadSamples().then(() => {
|
await loadSamples().then(() => {
|
||||||
this.docs = documentation_factory(this)
|
this.docs = documentation_factory(this);
|
||||||
this.currentDocumentationPane = e;
|
this.currentDocumentationPane = e;
|
||||||
this.updateDocumentationContent();
|
this.updateDocumentationContent();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -656,14 +654,18 @@ export class Editor {
|
|||||||
if (url !== null) {
|
if (url !== null) {
|
||||||
const universeParam = url.get("universe");
|
const universeParam = url.get("universe");
|
||||||
if (universeParam !== null) {
|
if (universeParam !== null) {
|
||||||
let data = Uint8Array.from(atob(universeParam), c => c.charCodeAt(0))
|
let data = Uint8Array.from(atob(universeParam), (c) =>
|
||||||
|
c.charCodeAt(0)
|
||||||
|
);
|
||||||
new_universe = JSON.parse(strFromU8(decompressSync(data)));
|
new_universe = JSON.parse(strFromU8(decompressSync(data)));
|
||||||
const randomName: string = uniqueNamesGenerator({
|
const randomName: string = uniqueNamesGenerator({
|
||||||
length: 2, separator: '_',
|
length: 2,
|
||||||
dictionaries: [colors, animals],
|
separator: "_",
|
||||||
|
dictionaries: [colors, animals],
|
||||||
});
|
});
|
||||||
this.loadUniverse(randomName, new_universe["universe"]);
|
this.loadUniverse(randomName, new_universe["universe"]);
|
||||||
this.emptyUrl(); this.emptyUrl();
|
this.emptyUrl();
|
||||||
|
this.emptyUrl();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -695,30 +697,30 @@ export class Editor {
|
|||||||
return JSON.parse(hash);
|
return JSON.parse(hash);
|
||||||
};
|
};
|
||||||
|
|
||||||
updateKnownUniversesView = () => {
|
updateKnownUniversesView = () => {
|
||||||
let existing_universes = document.getElementById("existing-universes");
|
let existing_universes = document.getElementById("existing-universes");
|
||||||
let known_universes = Object.keys(this.universes);
|
let known_universes = Object.keys(this.universes);
|
||||||
let final_html = "<ul class='lg:h-80 lg:w-80 lg:pb-2 lg:pt-2 overflow-y-scroll text-white lg:mb-4 border rounded-lg bg-gray-800'>";
|
let final_html =
|
||||||
known_universes.forEach((name) => {
|
"<ul class='lg:h-80 lg:w-80 lg:pb-2 lg:pt-2 overflow-y-scroll text-white lg:mb-4 border rounded-lg bg-gray-800'>";
|
||||||
final_html += `
|
known_universes.forEach((name) => {
|
||||||
|
final_html += `
|
||||||
<li onclick="_loadUniverseFromInterface('${name}')" class="hover:fill-black hover:bg-white py-2 hover:text-black flex justify-between px-4">
|
<li onclick="_loadUniverseFromInterface('${name}')" class="hover:fill-black hover:bg-white py-2 hover:text-black flex justify-between px-4">
|
||||||
<p >${name}</p>
|
<p >${name}</p>
|
||||||
<button onclick=_deleteUniverseFromInterface('${name}')>🗑</button>
|
<button onclick=_deleteUniverseFromInterface('${name}')>🗑</button>
|
||||||
</li>`;
|
</li>`;
|
||||||
});
|
});
|
||||||
final_html = final_html + "</ul>";
|
final_html = final_html + "</ul>";
|
||||||
existing_universes!.innerHTML = final_html;
|
existing_universes!.innerHTML = final_html;
|
||||||
}
|
};
|
||||||
|
|
||||||
async share() {
|
async share() {
|
||||||
|
async function bufferToBase64(buffer: Uint8Array) {
|
||||||
async function bufferToBase64(buffer:Uint8Array) {
|
const base64url: string = await new Promise((r) => {
|
||||||
const base64url: string = await new Promise(r => {
|
const reader = new FileReader();
|
||||||
const reader = new FileReader()
|
reader.onload = () => r(reader.result as string);
|
||||||
reader.onload = () => r(reader.result as string)
|
reader.readAsDataURL(new Blob([buffer]));
|
||||||
reader.readAsDataURL(new Blob([buffer]))
|
|
||||||
});
|
});
|
||||||
return base64url.slice(base64url.indexOf(',') + 1);
|
return base64url.slice(base64url.indexOf(",") + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = JSON.stringify({
|
let data = JSON.stringify({
|
||||||
@ -852,22 +854,22 @@ export class Editor {
|
|||||||
button: "play" | "pause" | "stop" | "clear",
|
button: "play" | "pause" | "stop" | "clear",
|
||||||
highlight: boolean
|
highlight: boolean
|
||||||
) {
|
) {
|
||||||
document.getElementById('play-label')!.textContent = button !== "pause" ? "Pause" : "Play";
|
document.getElementById("play-label")!.textContent =
|
||||||
if (button !== "pause") {
|
button !== "pause" ? "Pause" : "Play";
|
||||||
document.getElementById('pause-icon')!.classList.remove('hidden');
|
if (button !== "pause") {
|
||||||
document.getElementById('play-icon')!.classList.add('hidden');
|
document.getElementById("pause-icon")!.classList.remove("hidden");
|
||||||
} else {
|
document.getElementById("play-icon")!.classList.add("hidden");
|
||||||
document.getElementById('pause-icon')!.classList.add('hidden');
|
} else {
|
||||||
document.getElementById('play-icon')!.classList.remove('hidden');
|
document.getElementById("pause-icon")!.classList.add("hidden");
|
||||||
}
|
document.getElementById("play-icon")!.classList.remove("hidden");
|
||||||
|
}
|
||||||
if (button === "stop") {
|
|
||||||
this.isPlaying == false;
|
|
||||||
document.getElementById('play-label')!.textContent = "Play";
|
|
||||||
document.getElementById('pause-icon')!.classList.add('hidden');
|
|
||||||
document.getElementById('play-icon')!.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (button === "stop") {
|
||||||
|
this.isPlaying == false;
|
||||||
|
document.getElementById("play-label")!.textContent = "Play";
|
||||||
|
document.getElementById("pause-icon")!.classList.add("hidden");
|
||||||
|
document.getElementById("play-icon")!.classList.remove("hidden");
|
||||||
|
}
|
||||||
|
|
||||||
this.flashBackground("#2d313d", 200);
|
this.flashBackground("#2d313d", 200);
|
||||||
const possible_selectors = [
|
const possible_selectors = [
|
||||||
|
|||||||
@ -1,116 +1,125 @@
|
|||||||
import { EditorView } from '@codemirror/view'
|
import { EditorView } from "@codemirror/view";
|
||||||
import { Extension } from '@codemirror/state'
|
import { Extension } from "@codemirror/state";
|
||||||
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language'
|
import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
|
||||||
import { tags as t } from '@lezer/highlight'
|
import { tags as t } from "@lezer/highlight";
|
||||||
|
|
||||||
const base00 = '#171717', base01 = '#505d64',
|
const base00 = "#171717",
|
||||||
base02 = 'white', base03 = '#707d8b',
|
base01 = "#505d64",
|
||||||
base04 = '#a0a4ae', base05 = '#bdbdbd',
|
base02 = "white",
|
||||||
base06 = '#e0e0e0', base07 = '#fdf6e3',
|
base03 = "#707d8b",
|
||||||
base_red = '#ff5f52', base_deeporange = '#ff6e40',
|
base04 = "#a0a4ae",
|
||||||
base_pink = '#fa5788', base_yellow = '#facf4e',
|
base05 = "#bdbdbd",
|
||||||
base_orange = '#ffad42', base_cyan = '#1E6AE1',
|
base06 = "#e0e0e0",
|
||||||
base_indigo = '#7186f0', base_purple = '#D09EBF',
|
base07 = "#fdf6e3",
|
||||||
base_green = '#82d47c', base_lightgreen = '#82d47c',
|
base_red = "#ff5f52",
|
||||||
base_teal = '#4ebaaa'
|
base_deeporange = "#ff6e40",
|
||||||
|
base_pink = "#fa5788",
|
||||||
|
base_yellow = "#facf4e",
|
||||||
|
base_orange = "#ffad42",
|
||||||
|
base_cyan = "#1E6AE1",
|
||||||
|
base_indigo = "#7186f0",
|
||||||
|
base_purple = "#D09EBF",
|
||||||
|
base_green = "#82d47c",
|
||||||
|
base_lightgreen = "#82d47c",
|
||||||
|
base_teal = "#4ebaaa";
|
||||||
|
|
||||||
const invalid = base_red,
|
const invalid = base_red,
|
||||||
darkBackground = '#fdf6e3',
|
darkBackground = "#fdf6e3",
|
||||||
highlightBackground = '#545b61',
|
highlightBackground = "#545b61",
|
||||||
background = base00,
|
background = base00,
|
||||||
tooltipBackground = base01,
|
tooltipBackground = base01,
|
||||||
selection = base07,
|
selection = base07,
|
||||||
cursor = base04
|
cursor = base04;
|
||||||
|
|
||||||
/// The editor theme styles for Material Dark.
|
/// The editor theme styles for Material Dark.
|
||||||
export const materialDarkTheme = EditorView.theme(
|
export const materialDarkTheme = EditorView.theme(
|
||||||
{
|
{
|
||||||
'&': {
|
"&": {
|
||||||
color: base05,
|
color: base05,
|
||||||
backgroundColor: background,
|
backgroundColor: background,
|
||||||
fontSize: '24px',
|
fontSize: "24px",
|
||||||
},
|
},
|
||||||
|
|
||||||
'.cm-content': {
|
".cm-content": {
|
||||||
caretColor: cursor
|
caretColor: cursor,
|
||||||
},
|
},
|
||||||
|
|
||||||
'.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor },
|
".cm-cursor, .cm-dropCursor": { borderLeftColor: cursor },
|
||||||
'&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection':
|
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":
|
||||||
{ backgroundColor: selection, border: `0.5px solid ${base_teal}` },
|
{ backgroundColor: selection, border: `0.5px solid ${base_teal}` },
|
||||||
|
|
||||||
'.cm-panels': { backgroundColor: darkBackground, color: base03 },
|
".cm-panels": { backgroundColor: darkBackground, color: base03 },
|
||||||
'.cm-panels.cm-panels-top': { borderBottom: '2px solid black' },
|
".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
|
||||||
'.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' },
|
".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" },
|
||||||
|
|
||||||
'.cm-searchMatch': {
|
".cm-searchMatch": {
|
||||||
outline: `1px solid ${base_yellow}`,
|
outline: `1px solid ${base_yellow}`,
|
||||||
backgroundColor: 'transparent'
|
backgroundColor: "transparent",
|
||||||
},
|
},
|
||||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
".cm-searchMatch.cm-searchMatch-selected": {
|
||||||
backgroundColor: highlightBackground
|
backgroundColor: highlightBackground,
|
||||||
},
|
},
|
||||||
|
|
||||||
'.cm-activeLine': { backgroundColor: highlightBackground },
|
".cm-activeLine": { backgroundColor: highlightBackground },
|
||||||
'.cm-selectionMatch': {
|
".cm-selectionMatch": {
|
||||||
backgroundColor: darkBackground,
|
backgroundColor: darkBackground,
|
||||||
outline: `1px solid ${base_teal}`
|
outline: `1px solid ${base_teal}`,
|
||||||
},
|
},
|
||||||
|
|
||||||
'&.cm-focused .cm-matchingBracket': {
|
"&.cm-focused .cm-matchingBracket": {
|
||||||
color: base06,
|
color: base06,
|
||||||
outline: `1px solid ${base_teal}`
|
outline: `1px solid ${base_teal}`,
|
||||||
},
|
},
|
||||||
|
|
||||||
'&.cm-focused .cm-nonmatchingBracket': {
|
"&.cm-focused .cm-nonmatchingBracket": {
|
||||||
color: base_red
|
color: base_red,
|
||||||
},
|
},
|
||||||
|
|
||||||
'.cm-gutters': {
|
".cm-gutters": {
|
||||||
backgroundColor: base00,
|
backgroundColor: base00,
|
||||||
borderRight: `1px solid ${base07}`,
|
borderRight: `1px solid ${base07}`,
|
||||||
color: base02
|
color: base02,
|
||||||
},
|
},
|
||||||
|
|
||||||
'.cm-activeLineGutter': {
|
".cm-activeLineGutter": {
|
||||||
backgroundColor: highlightBackground,
|
backgroundColor: highlightBackground,
|
||||||
color: base07
|
color: base07,
|
||||||
},
|
},
|
||||||
|
|
||||||
'.cm-foldPlaceholder': {
|
".cm-foldPlaceholder": {
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: "transparent",
|
||||||
border: 'none',
|
border: "none",
|
||||||
color: `${base07}`,
|
color: `${base07}`,
|
||||||
},
|
},
|
||||||
|
|
||||||
'.cm-tooltip': {
|
".cm-tooltip": {
|
||||||
border: 'none',
|
border: "none",
|
||||||
backgroundColor: tooltipBackground
|
backgroundColor: tooltipBackground,
|
||||||
},
|
},
|
||||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
".cm-tooltip .cm-tooltip-arrow:before": {
|
||||||
borderTopColor: 'transparent',
|
borderTopColor: "transparent",
|
||||||
borderBottomColor: 'transparent'
|
borderBottomColor: "transparent",
|
||||||
},
|
},
|
||||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
".cm-tooltip .cm-tooltip-arrow:after": {
|
||||||
borderTopColor: tooltipBackground,
|
borderTopColor: tooltipBackground,
|
||||||
borderBottomColor: tooltipBackground
|
borderBottomColor: tooltipBackground,
|
||||||
},
|
},
|
||||||
'.cm-tooltip-autocomplete': {
|
".cm-tooltip-autocomplete": {
|
||||||
'& > ul > li[aria-selected]': {
|
"& > ul > li[aria-selected]": {
|
||||||
backgroundColor: highlightBackground,
|
backgroundColor: highlightBackground,
|
||||||
color: base03
|
color: base03,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{ dark: true }
|
{ dark: true }
|
||||||
)
|
);
|
||||||
|
|
||||||
/// The highlighting style for code in the Material Dark theme.
|
/// The highlighting style for code in the Material Dark theme.
|
||||||
export const materialDarkHighlightStyle = HighlightStyle.define([
|
export const materialDarkHighlightStyle = HighlightStyle.define([
|
||||||
{ tag: t.keyword, color: base_purple },
|
{ tag: t.keyword, color: base_purple },
|
||||||
{
|
{
|
||||||
tag: [t.name, t.deleted, t.character, t.macroName],
|
tag: [t.name, t.deleted, t.character, t.macroName],
|
||||||
color: base_cyan
|
color: base_cyan,
|
||||||
},
|
},
|
||||||
{ tag: [t.propertyName], color: base_yellow },
|
{ tag: [t.propertyName], color: base_yellow },
|
||||||
{ tag: [t.variableName], color: base05 },
|
{ tag: [t.variableName], color: base05 },
|
||||||
@ -118,93 +127,93 @@ export const materialDarkHighlightStyle = HighlightStyle.define([
|
|||||||
{ tag: [t.labelName], color: base_purple },
|
{ tag: [t.labelName], color: base_purple },
|
||||||
{
|
{
|
||||||
tag: [t.color, t.constant(t.name), t.standard(t.name)],
|
tag: [t.color, t.constant(t.name), t.standard(t.name)],
|
||||||
color: base_yellow
|
color: base_yellow,
|
||||||
},
|
},
|
||||||
{ tag: [t.definition(t.name), t.separator], color: base_pink },
|
{ tag: [t.definition(t.name), t.separator], color: base_pink },
|
||||||
{ tag: [t.brace], color: base_purple },
|
{ tag: [t.brace], color: base_purple },
|
||||||
{
|
{
|
||||||
tag: [t.annotation],
|
tag: [t.annotation],
|
||||||
color: invalid
|
color: invalid,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
|
tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
|
||||||
color: base_orange
|
color: base_orange,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tag: [t.typeName, t.className],
|
tag: [t.typeName, t.className],
|
||||||
color: base_orange
|
color: base_orange,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tag: [t.operator, t.operatorKeyword],
|
tag: [t.operator, t.operatorKeyword],
|
||||||
color: base_indigo
|
color: base_indigo,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tag: [t.tagName],
|
tag: [t.tagName],
|
||||||
color: base_deeporange
|
color: base_deeporange,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tag: [t.squareBracket],
|
tag: [t.squareBracket],
|
||||||
color: base_red
|
color: base_red,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tag: [t.angleBracket],
|
tag: [t.angleBracket],
|
||||||
color: base02
|
color: base02,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tag: [t.attributeName],
|
tag: [t.attributeName],
|
||||||
color: base05
|
color: base05,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tag: [t.regexp],
|
tag: [t.regexp],
|
||||||
color: invalid
|
color: invalid,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tag: [t.quote],
|
tag: [t.quote],
|
||||||
color: base_green
|
color: base_green,
|
||||||
},
|
},
|
||||||
{ tag: [t.string], color: base_lightgreen },
|
{ tag: [t.string], color: base_lightgreen },
|
||||||
{
|
{
|
||||||
tag: t.link,
|
tag: t.link,
|
||||||
color: base_cyan,
|
color: base_cyan,
|
||||||
textDecoration: 'underline',
|
textDecoration: "underline",
|
||||||
textUnderlinePosition: 'under'
|
textUnderlinePosition: "under",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tag: [t.url, t.escape, t.special(t.string)],
|
tag: [t.url, t.escape, t.special(t.string)],
|
||||||
color: base_yellow
|
color: base_yellow,
|
||||||
},
|
},
|
||||||
{ tag: [t.meta], color: base03 },
|
{ tag: [t.meta], color: base03 },
|
||||||
{ tag: [t.comment], color: base02, fontStyle: 'italic' },
|
{ tag: [t.comment], color: base02, fontStyle: "italic" },
|
||||||
{ tag: t.monospace, color: base05 },
|
{ tag: t.monospace, color: base05 },
|
||||||
{ tag: t.strong, fontWeight: 'bold', color: base_red },
|
{ tag: t.strong, fontWeight: "bold", color: base_red },
|
||||||
{ tag: t.emphasis, fontStyle: 'italic', color: base_lightgreen },
|
{ tag: t.emphasis, fontStyle: "italic", color: base_lightgreen },
|
||||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
{ tag: t.strikethrough, textDecoration: "line-through" },
|
||||||
{ tag: t.heading, fontWeight: 'bold', color: base_yellow },
|
{ tag: t.heading, fontWeight: "bold", color: base_yellow },
|
||||||
{ tag: t.heading1, fontWeight: 'bold', color: base_yellow },
|
{ tag: t.heading1, fontWeight: "bold", color: base_yellow },
|
||||||
{
|
{
|
||||||
tag: [t.heading2, t.heading3, t.heading4],
|
tag: [t.heading2, t.heading3, t.heading4],
|
||||||
fontWeight: 'bold',
|
fontWeight: "bold",
|
||||||
color: base_yellow
|
color: base_yellow,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tag: [t.heading5, t.heading6],
|
tag: [t.heading5, t.heading6],
|
||||||
color: base_yellow
|
color: base_yellow,
|
||||||
},
|
},
|
||||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: base_cyan },
|
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: base_cyan },
|
||||||
{
|
{
|
||||||
tag: [t.processingInstruction, t.inserted],
|
tag: [t.processingInstruction, t.inserted],
|
||||||
color: base_red
|
color: base_red,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tag: [t.contentSeparator],
|
tag: [t.contentSeparator],
|
||||||
color: base_cyan
|
color: base_cyan,
|
||||||
},
|
},
|
||||||
{ tag: t.invalid, color: base02, borderBottom: `1px dotted ${base_red}` }
|
{ tag: t.invalid, color: base02, borderBottom: `1px dotted ${base_red}` },
|
||||||
])
|
]);
|
||||||
|
|
||||||
/// Extension to enable the Material Dark theme (both the editor theme and
|
/// Extension to enable the Material Dark theme (both the editor theme and
|
||||||
/// the highlight style).
|
/// the highlight style).
|
||||||
export const materialDark: Extension = [
|
export const materialDark: Extension = [
|
||||||
materialDarkTheme,
|
materialDarkTheme,
|
||||||
syntaxHighlighting(materialDarkHighlightStyle)
|
syntaxHighlighting(materialDarkHighlightStyle),
|
||||||
]
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user