Merge branch 'main' of https://github.com/Bubobubobubobubo/Topos into logicaltime

This commit is contained in:
2023-08-26 11:10:32 +03:00
7 changed files with 778 additions and 503 deletions

View File

@ -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>

View File

@ -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);
}; };
@ -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);
@ -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.
@ -1241,7 +1477,6 @@ export class UserAPI {
snd = this.sound; snd = this.sound;
samples = samples; samples = samples;
soundMap = soundMap;
log = console.log; log = console.log;

View File

@ -1,11 +1,41 @@
import { type Editor } from "./main";
const key_shortcut = (shortcut: string): string => { const key_shortcut = (shortcut: string): string => {
return `<kbd class="lg:px-2 lg:py-1.5 px-1 py-1 lg:text-sm text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">${shortcut}</kbd>`; return `<kbd class="lg:px-2 lg:py-1.5 px-1 py-1 lg:text-sm text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">${shortcut}</kbd>`;
}; };
const injectAvailableSamples = (): string => { const samples_to_markdown = (samples: object) => {
return ""; let markdownList = "";
let keys = Object.keys(samples);
let i = -1;
while (i++ < keys.length - 1) {
//@ts-ignore
if (!samples[keys[i]].data) continue;
//@ts-ignore
if (!samples[keys[i]].data.samples) continue;
markdownList += `**${keys[i]}** (_${
//@ts-ignore
samples[keys[i]].data.samples.length
}_) `;
// let i2 = -1;
// while (i2++ < samples[keys[i]].data.samples.length - 1) {
// console.log(samples[keys[i]].data.samples[i2]);
// markdownList += `\t- <audio controls> <source src="${
// samples[keys[i]].data.samples[i2]
// }" type="audio/wav">\n
// </audio>
// `;
// }
}
return markdownList;
}; };
const injectAvailableSamples = (application: Editor): string => {
let test = samples_to_markdown(application.api._all_samples());
return test;
};
export const documentation_factory = (application: Editor) => {
const introduction: string = ` const introduction: string = `
# Welcome # Welcome
@ -27,14 +57,14 @@ Press ${key_shortcut(
)} to switch to the global file. This is where everything starts! Evaluate the following script there by pasting and pressing ${key_shortcut( )} to switch to the global file. This is where everything starts! Evaluate the following script there by pasting and pressing ${key_shortcut(
"Ctrl + Enter" "Ctrl + Enter"
)}. You are now making music: )}. You are now making music:
<pre><code class="language-javascript"> <pre><code class="language-javascript">
bpm(80) bpm(80)
mod(0.25) :: sound('sawtooth') mod(0.25) :: sound('sawtooth')
.note(seqbar( .note(seqbar(
pick(60, 67, 63) - 12, pick(60, 67, 63) - 12, pick(60, 67, 63) - 12, pick(60, 67, 63) - 12,
pick(60, 67, 63) - 12 + 5, pick(60, 67, 63) - 12 + 5, pick(60, 67, 63) - 12 + 5, pick(60, 67, 63) - 12 + 5,
pick(60, 67, 63) - 12 + 7, pick(60, 67, 63) - 12 + 7) + (sometimes() ? 24 : 12)) pick(60, 67, 63) - 12 + 7, pick(60, 67, 63) - 12 + 7) + (sometimes() ? 24 : 12)
)
.sustain(0.1).fmi(8).fmh(4).room(0.9) .sustain(0.1).fmi(8).fmh(4).room(0.9)
.gain(0.75).cutoff(500 + usine(8) * 10000) .gain(0.75).cutoff(500 + usine(8) * 10000)
.delay(0.5).delaytime(bpm() / 60 / 4 / 3) .delay(0.5).delaytime(bpm() / 60 / 4 / 3)
@ -318,7 +348,6 @@ mod(0.25) && midi(60)
"Ctrl + Shift + I" "Ctrl + Shift + I"
)} in many web browsers. )} in many web browsers.
- <icode>midi_output(output_name: string)</icode>: Selects the MIDI output to use. You can use the <icode>midi_outputs()</icode> function to get a list of available MIDI outputs first. If the MIDI output is not available, the function will do nothing and keep on with the currently selected MIDI Port. - <icode>midi_output(output_name: string)</icode>: Selects the MIDI output to use. You can use the <icode>midi_outputs()</icode> function to get a list of available MIDI outputs first. If the MIDI output is not available, the function will do nothing and keep on with the currently selected MIDI Port.
`; `;
const sound: string = ` const sound: string = `
@ -495,7 +524,7 @@ There are three basic filters: a _lowpass_, _highpass_ and _bandpass_ filters wi
\`\`\`javascript \`\`\`javascript
mod(.5) && snd('sawtooth') mod(.5) && snd('sawtooth')
.cutoff(pick(2000,500)) + usine(.5) * 4000) .cutoff(pick(2000,500) + usine(.5) * 4000)
.resonance(0.9).freq(pick(100,150)) .resonance(0.9).freq(pick(100,150))
.out() .out()
\`\`\` \`\`\`
@ -547,9 +576,13 @@ mod(.5)::snd('pad').crush(divseq(2, 16, 8, 4)).clip(.5).out()
const samples: string = ` const samples: string = `
# Audio Samples # Audio Samples
Audio samples are dynamically loaded from the web. By default, Topos is providing some samples coming from the classic [Dirt-Samples](https://github.com/tidalcycles/Dirt-Samples) but also from the [Topos-Samples](https://github.com/Bubobubobubobubo/Topos-Samples) repository. You can contribute to the latter if you want to share your samples with the community! For each sample folder, we are indicating how many of them are available in parentheses.
- **sample_folder** (_how_many_)
## Available audio samples ## Available audio samples
${injectAvailableSamples()} ${injectAvailableSamples(application)}
`; `;
@ -578,7 +611,7 @@ Here is a simple example of a substractive synth:
\`\`\`javascript \`\`\`javascript
mod(.5) && snd('sawtooth') mod(.5) && snd('sawtooth')
.cutoff(pick(2000,500)) + usine(.5) * 4000) .cutoff(pick(2000,500) + usine(.5) * 4000)
.resonance(0.9).freq(pick(100,150)) .resonance(0.9).freq(pick(100,150))
.out() .out()
\`\`\` \`\`\`
@ -676,8 +709,6 @@ You will often need to use iterators and/or counters to index over data structur
- <icode>drunk_min(min: number)</icode>: sets the minimum value. - <icode>drunk_min(min: number)</icode>: sets the minimum value.
- <icode>drunk_wrap(wrap: boolean)</icode>: whether to wrap the drunk walk to 0 once the upper limit is reached or not. - <icode>drunk_wrap(wrap: boolean)</icode>: whether to wrap the drunk walk to 0 once the upper limit is reached or not.
## Scripts ## Scripts
You can control scripts programatically. This is the core concept of Topos after all! You can control scripts programatically. This is the core concept of Topos after all!
@ -769,8 +800,6 @@ Chance operators returning a boolean value are also available:
- <icode>delay(ms: number, func: Function): void</icode>: Delays the execution of a function by a given number of milliseconds. - <icode>delay(ms: number, func: Function): void</icode>: Delays the execution of a function by a given number of milliseconds.
- <icode>delayr(ms: number, nb: number, func: Function): void</icode>: Delays the execution of a function by a given number of milliseconds, repeated a given number of times. - <icode>delayr(ms: number, nb: number, func: Function): void</icode>: Delays the execution of a function by a given number of milliseconds, repeated a given number of times.
`; `;
const reference: string = ` const reference: string = `
@ -814,7 +843,7 @@ Topos is made to be controlled entirely with a keyboard. It is recommanded to st
- Switch the editor to Vim Mode: ${key_shortcut("Ctrl + V")}. - Switch the editor to Vim Mode: ${key_shortcut("Ctrl + V")}.
`; `;
export const documentation = { return {
introduction: introduction, introduction: introduction,
interface: software_interface, interface: software_interface,
code: code, code: code,
@ -829,3 +858,4 @@ export const documentation = {
shortcuts: shortcuts, shortcuts: shortcuts,
about: about, about: about,
}; };
};

View File

@ -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

View File

@ -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

View File

@ -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 };

View File

@ -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>");