Merge branch 'main' of https://github.com/Bubobubobubobubo/Topos into logicaltime
This commit is contained in:
15
index.html
15
index.html
@ -65,7 +65,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a title="Eval button" id="eval-button-1" class="mr-2 hover:text-gray-900 hover:bg-gray-800 px-2 py-2 rounded-lg">
|
<a title="Eval button" id="eval-button-1" class="mr-2 hover:text-gray-900 hover:bg-gray-800 px-2 py-2 rounded-lg">
|
||||||
<svg class="w-8 h-8 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 20">
|
<svg class="w-8 h-8 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 20">
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 1v5h-5M2 19v-5h5m10-4a8 8 0 0 1-14.947 3.97M1 10a8 8 0 0 1 14.947-3.97"/>
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 1v5h-5M2 19v-5h5m10-4a8 8 0 0 1-14.947 3.97M1 10a8 8 0 0 1 14.947-3.97"/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
@ -76,7 +76,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a title="Open Documentation" id="doc-button-1" class="mr-2 hover:text-gray-900 hover:bg-gray-800 px-2 py-2 rounded-lg">
|
<a title="Open Documentation" id="doc-button-1" class="mr-2 hover:text-gray-900 hover:bg-gray-800 px-2 py-2 rounded-lg">
|
||||||
<svg class="w-8 h-8 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 18">
|
<svg class="w-8 h-8 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 18">
|
||||||
<path d="M9 1.334C7.06.594 1.646-.84.293.653a1.158 1.158 0 0 0-.293.77v13.973c0 .193.046.383.134.55.088.167.214.306.366.403a.932.932 0 0 0 .5.147c.176 0 .348-.05.5-.147 1.059-.32 6.265.851 7.5 1.65V1.334ZM19.707.653C18.353-.84 12.94.593 11 1.333V18c1.234-.799 6.436-1.968 7.5-1.65a.931.931 0 0 0 .5.147.931.931 0 0 0 .5-.148c.152-.096.279-.235.366-.403.088-.167.134-.357.134-.55V1.423a1.158 1.158 0 0 0-.293-.77Z"/>
|
<path d="M9 1.334C7.06.594 1.646-.84.293.653a1.158 1.158 0 0 0-.293.77v13.973c0 .193.046.383.134.55.088.167.214.306.366.403a.932.932 0 0 0 .5.147c.176 0 .348-.05.5-.147 1.059-.32 6.265.851 7.5 1.65V1.334ZM19.707.653C18.353-.84 12.94.593 11 1.333V18c1.234-.799 6.436-1.968 7.5-1.65a.931.931 0 0 0 .5.147.931.931 0 0 0 .5-.148c.152-.096.279-.235.366-.403.088-.167.134-.357.134-.55V1.423a1.158 1.158 0 0 0-.293-.77Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
@ -214,8 +214,8 @@
|
|||||||
<!-- This is a lateral bar that will inherit the header buttons if the window is too small. -->
|
<!-- This is a lateral bar that will inherit the header buttons if the window is too small. -->
|
||||||
<aside class="
|
<aside class="
|
||||||
flex flex-col items-center w-14
|
flex flex-col items-center w-14
|
||||||
h-screen py-2 overflow-y-hidden
|
h-screen py-2 border-r
|
||||||
border-r rtl:border-l max-h-fit
|
rtl:border-l max-h-fit
|
||||||
rtl:border-r-0 bg-neutral-900
|
rtl:border-r-0 bg-neutral-900
|
||||||
dark:border-neutral-700 border-none"
|
dark:border-neutral-700 border-none"
|
||||||
>
|
>
|
||||||
@ -255,6 +255,7 @@
|
|||||||
|
|
||||||
<!-- Other buttons -->
|
<!-- Other buttons -->
|
||||||
|
|
||||||
|
<!--
|
||||||
<a title="Play button" id="play-button-2" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 hover:bg-gray-800 sm:landscape:hidden">
|
<a title="Play button" id="play-button-2" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 hover:bg-gray-800 sm:landscape:hidden">
|
||||||
<svg class="w-8 h-8 text-white block xl:hidden" fill="currentColor" viewBox="0 0 14 16">
|
<svg class="w-8 h-8 text-white block xl:hidden" fill="currentColor" viewBox="0 0 14 16">
|
||||||
<path d="M0 .984v14.032a1 1 0 0 0 1.506.845l12.006-7.016a.974.974 0 0 0 0-1.69L1.506.139A1 1 0 0 0 0 .984Z"/>
|
<path d="M0 .984v14.032a1 1 0 0 0 1.506.845l12.006-7.016a.974.974 0 0 0 0-1.69L1.506.139A1 1 0 0 0 0 .984Z"/>
|
||||||
@ -273,11 +274,13 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a title="Clear button" id="clear-button-2" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 hover:bg-gray-800">
|
<a title="Clear button" id="clear-button-2" class="hidden lg:block pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg text-white hover:bg-gray-800">
|
||||||
<svg class="w-8 h-8 text-white block xl:hidden" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 17 20">
|
<svg class="w-8 h-8 text-white block xl:hidden" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 17 20">
|
||||||
<path d="M7.958 19.393a7.7 7.7 0 0 1-6.715-3.439c-2.868-4.832 0-9.376.944-10.654l.091-.122a3.286 3.286 0 0 0 .765-3.288A1 1 0 0 1 4.6.8c.133.1.313.212.525.347A10.451 10.451 0 0 1 10.6 9.3c.5-1.06.772-2.213.8-3.385a1 1 0 0 1 1.592-.758c1.636 1.205 4.638 6.081 2.019 10.441a8.177 8.177 0 0 1-7.053 3.795Z"/>
|
<path d="M7.958 19.393a7.7 7.7 0 0 1-6.715-3.439c-2.868-4.832 0-9.376.944-10.654l.091-.122a3.286 3.286 0 0 0 .765-3.288A1 1 0 0 1 4.6.8c.133.1.313.212.525.347A10.451 10.451 0 0 1 10.6 9.3c.5-1.06.772-2.213.8-3.385a1 1 0 0 1 1.592-.758c1.636 1.205 4.638 6.081 2.019 10.441a8.177 8.177 0 0 1-7.053 3.795Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
<a title="Open settings" id="settings-button" class="lg:block sm:hidden p-2 text-gray-200 ml-1 absolute bottom-2 focus:outline-nones transition-colors duration-200 rounded-lg text-gray-200 bg-gray-800">
|
<a title="Open settings" id="settings-button" class="lg:block sm:hidden p-2 text-gray-200 ml-1 absolute bottom-2 focus:outline-nones transition-colors duration-200 rounded-lg text-gray-200 bg-gray-800">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||||
@ -289,7 +292,7 @@
|
|||||||
|
|
||||||
<!-- Tabs for local files -->
|
<!-- Tabs for local files -->
|
||||||
<div class="min-w-screen flex grow flex-col">
|
<div class="min-w-screen flex grow flex-col">
|
||||||
<ul id="local-script-tabs" class="text-xl flex text-sm font-medium text-center text-white dark:text-white bg-neutral-900 space-x-2 lg:space-x-8">
|
<ul id="local-script-tabs" class="text-xl flex text-sm font-medium text-center text-white bg-neutral-900 space-x-2 lg:space-x-8">
|
||||||
<li class="pl-5">
|
<li class="pl-5">
|
||||||
<a title="Local Script 1" id="tab-1" class="bg-orange-300 inline-block px-4 py-1 text-white hover:bg-gray-800">1</a>
|
<a title="Local Script 1" id="tab-1" class="bg-orange-300 inline-block px-4 py-1 text-white hover:bg-gray-800">1</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
321
src/API.ts
321
src/API.ts
@ -22,21 +22,230 @@ interface ControlChange {
|
|||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ======================================================================
|
||||||
|
// Array prototype extensions: easier work with lists
|
||||||
|
// ======================================================================
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Array<T> {
|
||||||
|
palindrome(): T[];
|
||||||
|
random(index: number): T;
|
||||||
|
rand(index: number): T;
|
||||||
|
degrade(amount: number): T;
|
||||||
|
repeatAll(amount: number): T;
|
||||||
|
repeatPair(amount: number): T;
|
||||||
|
repeatOdd(amount: number): T;
|
||||||
|
loop(index: number): T;
|
||||||
|
div(division: number): T;
|
||||||
|
shuffle(): this;
|
||||||
|
rotate(steps: number): this;
|
||||||
|
unique(): this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.prototype.shuffle = function () {
|
||||||
|
/**
|
||||||
|
* Shuffles the array in place.
|
||||||
|
*
|
||||||
|
* @returns The shuffled array
|
||||||
|
*/
|
||||||
|
let currentIndex = this.length,
|
||||||
|
randomIndex;
|
||||||
|
while (currentIndex !== 0) {
|
||||||
|
randomIndex = Math.floor(Math.random() * currentIndex);
|
||||||
|
currentIndex--;
|
||||||
|
[this[currentIndex], this[randomIndex]] = [
|
||||||
|
this[randomIndex],
|
||||||
|
this[currentIndex],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Array.prototype.rotate = function (steps: number) {
|
||||||
|
/**
|
||||||
|
* Rotates the array in place.
|
||||||
|
*
|
||||||
|
* @param steps - The number of steps to rotate the array by
|
||||||
|
* @returns The rotated array
|
||||||
|
*/
|
||||||
|
const length = this.length;
|
||||||
|
if (steps < 0) {
|
||||||
|
steps = length + (steps % length);
|
||||||
|
} else if (steps > 0) {
|
||||||
|
steps = steps % length;
|
||||||
|
} else {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
const rotated = this.splice(-steps, steps);
|
||||||
|
this.unshift(...rotated);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Array.prototype.unique = function () {
|
||||||
|
/**
|
||||||
|
* Removes duplicate elements from the array in place.
|
||||||
|
*
|
||||||
|
* @returns The array without duplicates
|
||||||
|
*/
|
||||||
|
const seen = new Set();
|
||||||
|
let writeIndex = 0;
|
||||||
|
for (let readIndex = 0; readIndex < this.length; readIndex++) {
|
||||||
|
const value = this[readIndex];
|
||||||
|
if (!seen.has(value)) {
|
||||||
|
seen.add(value);
|
||||||
|
this[writeIndex++] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.length = writeIndex;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Array.prototype.degrade = function <T>(this: T[], amount: number) {
|
||||||
|
/**
|
||||||
|
* Removes elements from the array at random. If the array has
|
||||||
|
* only one element left, it will not be removed.
|
||||||
|
*
|
||||||
|
* @param amount - The amount of elements to remove
|
||||||
|
* @returns The degraded array
|
||||||
|
*/
|
||||||
|
if (amount < 0 || amount > 100) {
|
||||||
|
throw new Error("Amount should be between 0 and 100");
|
||||||
|
}
|
||||||
|
if (this.length <= 1) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < this.length; ) {
|
||||||
|
const rand = Math.random() * 100;
|
||||||
|
if (rand < amount) {
|
||||||
|
if (this.length > 1) {
|
||||||
|
this.splice(i, 1);
|
||||||
|
} else {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Array.prototype.repeatAll = function <T>(this: T[], amount: number) {
|
||||||
|
/**
|
||||||
|
* Repeats all elements in the array n times.
|
||||||
|
*
|
||||||
|
* @param amount - The amount of times to repeat the elements
|
||||||
|
* @returns The repeated array
|
||||||
|
*/
|
||||||
|
if (amount < 1) {
|
||||||
|
throw new Error("Amount should be at least 1");
|
||||||
|
}
|
||||||
|
let result = [];
|
||||||
|
for (let i = 0; i < this.length; i++) {
|
||||||
|
for (let j = 0; j < amount; j++) {
|
||||||
|
result.push(this[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.length = 0;
|
||||||
|
this.push(...result);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Array.prototype.repeatPair = function <T>(this: T[], amount: number) {
|
||||||
|
/**
|
||||||
|
* Repeats all elements in the array n times, except for the
|
||||||
|
* elements at odd indexes.
|
||||||
|
*
|
||||||
|
* @param amount - The amount of times to repeat the elements
|
||||||
|
* @returns The repeated array
|
||||||
|
*/
|
||||||
|
if (amount < 1) {
|
||||||
|
throw new Error("Amount should be at least 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < this.length; i++) {
|
||||||
|
// If the index is even, repeat the element
|
||||||
|
if (i % 2 === 0) {
|
||||||
|
for (let j = 0; j < amount; j++) {
|
||||||
|
result.push(this[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push(this[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.length = 0;
|
||||||
|
this.push(...result);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Array.prototype.repeatOdd = function <T>(this: T[], amount: number) {
|
||||||
|
/**
|
||||||
|
* Repeats all elements in the array n times, except for the
|
||||||
|
* elements at even indexes.
|
||||||
|
*
|
||||||
|
* @param amount - The amount of times to repeat the elements
|
||||||
|
* @returns The repeated array
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* This function is the opposite of repeatPair.
|
||||||
|
*/
|
||||||
|
if (amount < 1) {
|
||||||
|
throw new Error("Amount should be at least 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < this.length; i++) {
|
||||||
|
// If the index is odd, repeat the element
|
||||||
|
if (i % 2 !== 0) {
|
||||||
|
for (let j = 0; j < amount; j++) {
|
||||||
|
result.push(this[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push(this[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the original array
|
||||||
|
this.length = 0;
|
||||||
|
this.push(...result);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
Array.prototype.palindrome = function (index) {
|
Array.prototype.palindrome = function <T>() {
|
||||||
|
/**
|
||||||
|
* Returns a palindrome of the array.
|
||||||
|
*
|
||||||
|
* @returns The palindrome of the array
|
||||||
|
*/
|
||||||
let left_to_right = Array.from(this);
|
let left_to_right = Array.from(this);
|
||||||
let right_to_left = Array.from(this.reverse());
|
let right_to_left = Array.from(this.reverse());
|
||||||
return left_to_right.concat(right_to_left);
|
return left_to_right.concat(right_to_left);
|
||||||
};
|
};
|
||||||
// @ts-ignore
|
|
||||||
Array.prototype.random = function (index) {
|
Array.prototype.loop = function <T>(this: T[], index: number): T {
|
||||||
return this[Math.floor(Math.random() * this.length)];
|
/**
|
||||||
};
|
* Returns an element from the array based on the index.
|
||||||
// @ts-ignore
|
* The index will wrap over the array.
|
||||||
Array.prototype.loop = function (index) {
|
*
|
||||||
|
* @param index - The index of the element to return
|
||||||
|
* @returns The element at the given index
|
||||||
|
*/
|
||||||
return this[index % this.length];
|
return this[index % this.length];
|
||||||
};
|
};
|
||||||
// @ts-ignore
|
|
||||||
|
Array.prototype.random = function () {
|
||||||
|
/**
|
||||||
|
* Returns a random element from the array.
|
||||||
|
*
|
||||||
|
* @returns A random element from the array
|
||||||
|
*/
|
||||||
|
return this[Math.floor(Math.random() * this.length)];
|
||||||
|
};
|
||||||
Array.prototype.rand = Array.prototype.random;
|
Array.prototype.rand = Array.prototype.random;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,19 +260,16 @@ Array.prototype.in = function <T>(this: T[], value: T): boolean {
|
|||||||
return this.includes(value);
|
return this.includes(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function loadSamples() {
|
export async function loadSamples() {
|
||||||
// const ds = "https://raw.githubusercontent.com/felixroos/dough-samples/main/";
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
initAudioOnFirstClick(),
|
initAudioOnFirstClick(),
|
||||||
samples("github:Bubobubobubobubo/Topos-Samples/main"),
|
|
||||||
samples("github:tidalcycles/Dirt-Samples/master").then(() =>
|
samples("github:tidalcycles/Dirt-Samples/master").then(() =>
|
||||||
registerSynthSounds()
|
registerSynthSounds()
|
||||||
),
|
),
|
||||||
|
samples("github:Bubobubobubobubo/Topos-Samples/main"),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadSamples();
|
|
||||||
|
|
||||||
export const generateCacheKey = (...args: any[]): string => {
|
export const generateCacheKey = (...args: any[]): string => {
|
||||||
return args.map((arg) => JSON.stringify(arg)).join(",");
|
return args.map((arg) => JSON.stringify(arg)).join(",");
|
||||||
};
|
};
|
||||||
@ -92,6 +298,10 @@ export class UserAPI {
|
|||||||
//this.load = samples("github:tidalcycles/Dirt-Samples/master");
|
//this.load = samples("github:tidalcycles/Dirt-Samples/master");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_all_samples = (): object => {
|
||||||
|
return soundMap.get();
|
||||||
|
};
|
||||||
|
|
||||||
_reportError = (error: any): void => {
|
_reportError = (error: any): void => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
clearTimeout(this.errorTimeoutID);
|
clearTimeout(this.errorTimeoutID);
|
||||||
@ -236,7 +446,11 @@ export class UserAPI {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public midi = (value: number|object = 60): NoteEvent => {
|
public midi = (
|
||||||
|
value: number | object = 60,
|
||||||
|
velocity?: number,
|
||||||
|
channel?: number
|
||||||
|
): NoteEvent => {
|
||||||
/**
|
/**
|
||||||
* Sends a MIDI note to the current MIDI output.
|
* Sends a MIDI note to the current MIDI output.
|
||||||
*
|
*
|
||||||
@ -244,6 +458,24 @@ export class UserAPI {
|
|||||||
* @param options - an object containing options for that note
|
* @param options - an object containing options for that note
|
||||||
* { channel: 0, velocity: 100, duration: 0.5 }
|
* { channel: 0, velocity: 100, duration: 0.5 }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
if (velocity !== undefined) {
|
||||||
|
// Check if value is of type number
|
||||||
|
if (typeof value === "number") {
|
||||||
|
value = { note: value };
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
value["velocity"] = velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel !== undefined) {
|
||||||
|
if (typeof value === "number") {
|
||||||
|
value = { note: value };
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
value["channel"] = channel;
|
||||||
|
}
|
||||||
|
|
||||||
return new NoteEvent(value, this.app);
|
return new NoteEvent(value, this.app);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -323,8 +555,8 @@ export class UserAPI {
|
|||||||
this.app.api.patternCache.set(key, player);
|
this.app.api.patternCache.set(key, player);
|
||||||
}
|
}
|
||||||
if ((player && player.notStarted()) || player.played) {
|
if ((player && player.notStarted()) || player.played) {
|
||||||
player.callTime = this.epulse();
|
player.callTime = this.epulse();
|
||||||
player.played = false;
|
player.played = false;
|
||||||
}
|
}
|
||||||
return player;
|
return player;
|
||||||
};
|
};
|
||||||
@ -702,6 +934,30 @@ export class UserAPI {
|
|||||||
// Probability functions
|
// Probability functions
|
||||||
// =============================================================
|
// =============================================================
|
||||||
|
|
||||||
|
public prob = (p: number): boolean => {
|
||||||
|
/**
|
||||||
|
* Returns true p% of the time.
|
||||||
|
*
|
||||||
|
* @param p - The probability of returning true
|
||||||
|
* @returns True p% of the time
|
||||||
|
*/
|
||||||
|
return this.randomGen() * 100 < p;
|
||||||
|
};
|
||||||
|
|
||||||
|
public toss = (): boolean => {
|
||||||
|
/**
|
||||||
|
* Returns true 50% of the time.
|
||||||
|
*
|
||||||
|
* @returns True 50% of the time
|
||||||
|
* @see sometimes
|
||||||
|
* @see rarely
|
||||||
|
* @see often
|
||||||
|
* @see almostAlways
|
||||||
|
* @see almostNever
|
||||||
|
*/
|
||||||
|
return this.randomGen() > 0.5;
|
||||||
|
};
|
||||||
|
|
||||||
public odds = (n: number, sec: number = 15): boolean => {
|
public odds = (n: number, sec: number = 15): boolean => {
|
||||||
/**
|
/**
|
||||||
* Returns true n% of the time.
|
* Returns true n% of the time.
|
||||||
@ -865,6 +1121,10 @@ export class UserAPI {
|
|||||||
return this.app.clock.pulses_since_origin;
|
return this.app.clock.pulses_since_origin;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// =============================================================
|
||||||
|
// Time Filters
|
||||||
|
// =============================================================
|
||||||
|
|
||||||
onbar = (n: number, ...bar: number[]): boolean => {
|
onbar = (n: number, ...bar: number[]): boolean => {
|
||||||
// n is acting as a modulo on the bar number
|
// n is acting as a modulo on the bar number
|
||||||
const bar_list = [...Array(n).keys()].map((i) => i + 1);
|
const bar_list = [...Array(n).keys()].map((i) => i + 1);
|
||||||
@ -889,7 +1149,7 @@ export class UserAPI {
|
|||||||
let integral_part = Math.floor(b);
|
let integral_part = Math.floor(b);
|
||||||
let decimal_part = b - integral_part;
|
let decimal_part = b - integral_part;
|
||||||
final_pulses.push(
|
final_pulses.push(
|
||||||
integral_part === this.app.clock.time_position.beat &&
|
integral_part === this.app.clock.time_position.beat &&
|
||||||
this.app.clock.time_position.pulse ===
|
this.app.clock.time_position.pulse ===
|
||||||
decimal_part * this.app.clock.ppqn
|
decimal_part * this.app.clock.ppqn
|
||||||
);
|
);
|
||||||
@ -897,30 +1157,6 @@ export class UserAPI {
|
|||||||
return final_pulses.some((p) => p == true);
|
return final_pulses.some((p) => p == true);
|
||||||
};
|
};
|
||||||
|
|
||||||
prob = (p: number): boolean => {
|
|
||||||
/**
|
|
||||||
* Returns true p% of the time.
|
|
||||||
*
|
|
||||||
* @param p - The probability of returning true
|
|
||||||
* @returns True p% of the time
|
|
||||||
*/
|
|
||||||
return this.randomGen() * 100 < p;
|
|
||||||
};
|
|
||||||
|
|
||||||
toss = (): boolean => {
|
|
||||||
/**
|
|
||||||
* Returns true 50% of the time.
|
|
||||||
*
|
|
||||||
* @returns True 50% of the time
|
|
||||||
* @see sometimes
|
|
||||||
* @see rarely
|
|
||||||
* @see often
|
|
||||||
* @see almostAlways
|
|
||||||
* @see almostNever
|
|
||||||
*/
|
|
||||||
return this.randomGen() > 0.5;
|
|
||||||
};
|
|
||||||
|
|
||||||
public min = (...values: number[]): number => {
|
public min = (...values: number[]): number => {
|
||||||
/**
|
/**
|
||||||
* Returns the minimum value of a list of numbers.
|
* Returns the minimum value of a list of numbers.
|
||||||
@ -1235,13 +1471,12 @@ export class UserAPI {
|
|||||||
// Trivial functions
|
// Trivial functions
|
||||||
// =============================================================
|
// =============================================================
|
||||||
|
|
||||||
sound = (sound: string|object) => {
|
sound = (sound: string | object) => {
|
||||||
return new SoundEvent(sound, this.app);
|
return new SoundEvent(sound, this.app);
|
||||||
};
|
};
|
||||||
|
|
||||||
snd = this.sound;
|
snd = this.sound;
|
||||||
samples = samples;
|
samples = samples;
|
||||||
soundMap = soundMap;
|
|
||||||
|
|
||||||
log = console.log;
|
log = console.log;
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -8,10 +8,10 @@ export class RestEvent extends Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fallbackMethod = (): Event => {
|
_fallbackMethod = (): Event => {
|
||||||
return this;
|
return RestEvent.createRestProxy(this.values["duration"], this.app);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static createRestProxy = (duration: number, app: Editor) => {
|
public static createRestProxy = (duration: number, app: Editor): RestEvent => {
|
||||||
const instance = new RestEvent(duration, app);
|
const instance = new RestEvent(duration, app);
|
||||||
return new Proxy(instance, {
|
return new Proxy(instance, {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
export class SkipEvent {
|
export class SkipEvent {
|
||||||
|
|
||||||
_fallbackMethod = (): SkipEvent => {
|
_fallbackMethod = (): SkipEvent => {
|
||||||
return this;
|
return SkipEvent.createSkipProxy();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static createSkipProxy = () => {
|
public static createSkipProxy = (): SkipEvent => {
|
||||||
const instance = new SkipEvent();
|
const instance = new SkipEvent();
|
||||||
return new Proxy(instance, {
|
return new Proxy(instance, {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { type Editor } from '../main';
|
import { type Editor } from "../main";
|
||||||
import { AudibleEvent } from './AbstractEvents';
|
import { AudibleEvent } from "./AbstractEvents";
|
||||||
import { midiToFreq, noteFromPc } from 'zifferjs';
|
import { midiToFreq, noteFromPc } from "zifferjs";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
superdough,
|
superdough,
|
||||||
@ -8,7 +8,6 @@ import {
|
|||||||
} from "superdough";
|
} from "superdough";
|
||||||
|
|
||||||
export class SoundEvent extends AudibleEvent {
|
export class SoundEvent extends AudibleEvent {
|
||||||
|
|
||||||
constructor(sound: string | object, public app: Editor) {
|
constructor(sound: string | object, public app: Editor) {
|
||||||
super(app);
|
super(app);
|
||||||
if (typeof sound === "string") this.values = { s: sound, dur: 0.5 };
|
if (typeof sound === "string") this.values = { s: sound, dur: 0.5 };
|
||||||
|
|||||||
80
src/main.ts
80
src/main.ts
@ -14,10 +14,10 @@ import { indentWithTab } from "@codemirror/commands";
|
|||||||
import { vim } from "@replit/codemirror-vim";
|
import { vim } from "@replit/codemirror-vim";
|
||||||
import { AppSettings, Universe } from "./AppSettings";
|
import { AppSettings, Universe } from "./AppSettings";
|
||||||
import { editorSetup } from "./EditorSetup";
|
import { editorSetup } from "./EditorSetup";
|
||||||
import { documentation } from "./Documentation";
|
import { documentation_factory } from "./Documentation";
|
||||||
import { EditorView } from "codemirror";
|
import { EditorView } from "codemirror";
|
||||||
import { Clock } from "./Clock";
|
import { Clock } from "./Clock";
|
||||||
import { UserAPI } from "./API";
|
import { loadSamples, UserAPI } from "./API";
|
||||||
import "./style.css";
|
import "./style.css";
|
||||||
import {
|
import {
|
||||||
Universes,
|
Universes,
|
||||||
@ -27,9 +27,6 @@ import {
|
|||||||
} from "./AppSettings";
|
} from "./AppSettings";
|
||||||
import { tryEvaluate } from "./Evaluator";
|
import { tryEvaluate } from "./Evaluator";
|
||||||
|
|
||||||
type Documentation = { [key: string]: string };
|
|
||||||
const Docs: Documentation = documentation;
|
|
||||||
|
|
||||||
// Importing showdown and setting up the markdown converter
|
// Importing showdown and setting up the markdown converter
|
||||||
import showdown from "showdown";
|
import showdown from "showdown";
|
||||||
showdown.setFlavor("github");
|
showdown.setFlavor("github");
|
||||||
@ -37,8 +34,8 @@ import showdownHighlight from "showdown-highlight";
|
|||||||
const classMap = {
|
const classMap = {
|
||||||
h1: "text-white lg:text-4xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-8 mb-4 bg-neutral-900 rounded-lg py-2 px-2",
|
h1: "text-white lg:text-4xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-8 mb-4 bg-neutral-900 rounded-lg py-2 px-2",
|
||||||
h2: "text-white lg:text-3xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-8 mb-4 bg-neutral-900 rounded-lg py-2 px-2",
|
h2: "text-white lg:text-3xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-8 mb-4 bg-neutral-900 rounded-lg py-2 px-2",
|
||||||
ul: "text-underline",
|
ul: "text-underline pl-6",
|
||||||
li: "ml-12 list-disc lg:text-2xl text-base text-white lg:mx-4 mx-2 my-4 lg:pl-4 my-2 leading-normal",
|
li: "list-disc lg:text-2xl text-base text-white lg:mx-4 mx-2 my-4 my-2 leading-normal",
|
||||||
p: "lg:text-2xl text-base text-white lg:mx-4 mx-2 my-4 leading-normal",
|
p: "lg:text-2xl text-base text-white lg:mx-4 mx-2 my-4 leading-normal",
|
||||||
a: "lg:text-2xl text-base text-orange-300",
|
a: "lg:text-2xl text-base text-orange-300",
|
||||||
code: "lg:my-4 sm:my-1 text-base lg:text-xl block whitespace-pre overflow-x-hidden",
|
code: "lg:my-4 sm:my-1 text-base lg:text-xl block whitespace-pre overflow-x-hidden",
|
||||||
@ -66,7 +63,7 @@ export class Editor {
|
|||||||
universes: Universes = template_universes;
|
universes: Universes = template_universes;
|
||||||
selected_universe: string;
|
selected_universe: string;
|
||||||
local_index: number = 1;
|
local_index: number = 1;
|
||||||
editor_mode: "global" | "local" | "init" | "notes" = "local";
|
editor_mode: "global" | "local" | "init" | "notes" = "global";
|
||||||
fontSize: Compartment;
|
fontSize: Compartment;
|
||||||
withLineNumbers: Compartment;
|
withLineNumbers: Compartment;
|
||||||
vimModeCompartment: Compartment;
|
vimModeCompartment: Compartment;
|
||||||
@ -78,6 +75,7 @@ export class Editor {
|
|||||||
userPlugins: Extension[] = [];
|
userPlugins: Extension[] = [];
|
||||||
state: EditorState;
|
state: EditorState;
|
||||||
api: UserAPI;
|
api: UserAPI;
|
||||||
|
docs: { [key: string]: string } = {};
|
||||||
|
|
||||||
// Audio stuff
|
// Audio stuff
|
||||||
audioContext: AudioContext;
|
audioContext: AudioContext;
|
||||||
@ -92,19 +90,19 @@ export class Editor {
|
|||||||
// Transport elements
|
// Transport elements
|
||||||
play_buttons: HTMLButtonElement[] = [
|
play_buttons: HTMLButtonElement[] = [
|
||||||
document.getElementById("play-button-1") as HTMLButtonElement,
|
document.getElementById("play-button-1") as HTMLButtonElement,
|
||||||
document.getElementById("play-button-2") as HTMLButtonElement,
|
//document.getElementById("play-button-2") as HTMLButtonElement,
|
||||||
];
|
];
|
||||||
pause_buttons: HTMLButtonElement[] = [
|
pause_buttons: HTMLButtonElement[] = [
|
||||||
document.getElementById("pause-button-1") as HTMLButtonElement,
|
document.getElementById("pause-button-1") as HTMLButtonElement,
|
||||||
document.getElementById("pause-button-2") as HTMLButtonElement,
|
//document.getElementById("pause-button-2") as HTMLButtonElement,
|
||||||
];
|
];
|
||||||
stop_buttons: HTMLButtonElement[] = [
|
stop_buttons: HTMLButtonElement[] = [
|
||||||
document.getElementById("stop-button-1") as HTMLButtonElement,
|
document.getElementById("stop-button-1") as HTMLButtonElement,
|
||||||
document.getElementById("stop-button-2") as HTMLButtonElement,
|
//document.getElementById("stop-button-2") as HTMLButtonElement,
|
||||||
];
|
];
|
||||||
clear_buttons: HTMLButtonElement[] = [
|
clear_buttons: HTMLButtonElement[] = [
|
||||||
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,
|
||||||
];
|
];
|
||||||
documentation_button: HTMLButtonElement = document.getElementById(
|
documentation_button: HTMLButtonElement = document.getElementById(
|
||||||
"doc-button-1"
|
"doc-button-1"
|
||||||
@ -235,31 +233,13 @@ export class Editor {
|
|||||||
];
|
];
|
||||||
|
|
||||||
let dynamicPlugins = new Compartment();
|
let dynamicPlugins = new Compartment();
|
||||||
this.state = EditorState.create({
|
|
||||||
extensions: [
|
|
||||||
...this.editorExtensions,
|
|
||||||
EditorView.lineWrapping,
|
|
||||||
dynamicPlugins.of(this.userPlugins),
|
|
||||||
Prec.highest(
|
|
||||||
keymap.of([
|
|
||||||
{
|
|
||||||
key: "Ctrl-Enter",
|
|
||||||
run: () => {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
),
|
|
||||||
keymap.of([indentWithTab]),
|
|
||||||
],
|
|
||||||
doc: this.universes[this.selected_universe].locals[this.local_index]
|
|
||||||
.candidate,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.view = new EditorView({
|
// ================================================================================
|
||||||
parent: document.getElementById("editor") as HTMLElement,
|
// Building the documentation
|
||||||
state: this.state,
|
loadSamples().then(() => {
|
||||||
|
this.docs = documentation_factory(this);
|
||||||
});
|
});
|
||||||
|
// ================================================================================
|
||||||
|
|
||||||
// ================================================================================
|
// ================================================================================
|
||||||
// Application event listeners
|
// Application event listeners
|
||||||
@ -579,6 +559,33 @@ export class Editor {
|
|||||||
(globalThis as Record<string, any>)[name] = value;
|
(globalThis as Record<string, any>)[name] = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.state = EditorState.create({
|
||||||
|
extensions: [
|
||||||
|
...this.editorExtensions,
|
||||||
|
EditorView.lineWrapping,
|
||||||
|
dynamicPlugins.of(this.userPlugins),
|
||||||
|
Prec.highest(
|
||||||
|
keymap.of([
|
||||||
|
{
|
||||||
|
key: "Ctrl-Enter",
|
||||||
|
run: () => {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
),
|
||||||
|
keymap.of([indentWithTab]),
|
||||||
|
],
|
||||||
|
doc: this.universes[this.selected_universe].global.candidate,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.view = new EditorView({
|
||||||
|
parent: document.getElementById("editor") as HTMLElement,
|
||||||
|
state: this.state,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.changeModeFromInterface("global");
|
||||||
|
|
||||||
// Loading from URL bar
|
// Loading from URL bar
|
||||||
let url = new URLSearchParams(window.location.search);
|
let url = new URLSearchParams(window.location.search);
|
||||||
if (url !== undefined) {
|
if (url !== undefined) {
|
||||||
@ -660,10 +667,11 @@ export class Editor {
|
|||||||
const converter = new showdown.Converter({
|
const converter = new showdown.Converter({
|
||||||
emoji: true,
|
emoji: true,
|
||||||
moreStyling: true,
|
moreStyling: true,
|
||||||
|
backslashEscapesHTMLTags: true,
|
||||||
extensions: [showdownHighlight({ auto_detection: true }), ...bindings],
|
extensions: [showdownHighlight({ auto_detection: true }), ...bindings],
|
||||||
});
|
});
|
||||||
const converted_markdown = converter.makeHtml(
|
const converted_markdown = converter.makeHtml(
|
||||||
Docs[this.currentDocumentationPane]
|
this.docs[this.currentDocumentationPane]
|
||||||
);
|
);
|
||||||
function wrapCodeWithPre(inputString: string): string {
|
function wrapCodeWithPre(inputString: string): string {
|
||||||
let newString = inputString.replace(/<code>/g, "<pre><code>");
|
let newString = inputString.replace(/<code>/g, "<pre><code>");
|
||||||
|
|||||||
Reference in New Issue
Block a user