This commit is contained in:
2023-11-26 23:06:49 +01:00
parent fc47d598ac
commit 22508acb9f
21 changed files with 243 additions and 232 deletions

View File

@ -3,28 +3,23 @@ name: Build and Push Docker Images
on:
push:
branches:
- 'main'
- "main"
jobs:
topos:
runs-on: ubuntu-latest
steps:
-
name: Checkout
- name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Login to Docker Hub
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true

View File

@ -1,2 +1 @@
{
}
{}

View File

@ -14,7 +14,7 @@
</p>
</p>
Topos is a web-based live coding environment. It lives [here](https://topos.live). Documentation is directly embedded in the application itself. Topos is an emulation and extension of the [Monome Teletype](https://monome.org/docs/teletype/) that gradually evolved into something a bit more personal.
Topos is a web-based live coding environment. It lives [here](https://topos.live). Documentation is directly embedded in the application itself. Topos is an emulation and extension of the [Monome Teletype](https://monome.org/docs/teletype/) that gradually evolved into something a bit more personal.
![Screenshot](https://github.com/Bubobubobubobubo/Topos/blob/main/img/topos_gif.gif)
@ -46,15 +46,18 @@ The `tauri` version is only here to quickstart future developments but nothing
## Docker
### Run the application
`docker run -p 8001:80 yassinsiouda/topos:latest`
### Build and run the prod image
`docker compose --profile prod up`
### Build and run the dev image
**First installation**
First you need to map node_modules to your local machine for your ide intellisense to work properly
```bash
docker compose --profile dev up -d
docker cp topos-dev:/app/node_modules .
@ -62,7 +65,7 @@ docker compose --profile dev down
```
**Then**
```bash
docker compose --profile dev up
```

View File

@ -1,9 +1,9 @@
version: '3.7'
version: "3.7"
services:
topos-dev:
container_name: topos-dev
profiles: ["dev"]
build:
build:
context: .
target: "dev"
volumes:
@ -21,8 +21,8 @@ services:
topos-prod:
container_name: topos-prod
profiles: ["prod"]
build:
build:
context: .
target: "prod"
ports:
- "8001:80"
- "8001:80"

View File

@ -1,19 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

View File

@ -1,7 +1,8 @@
@font-face {
font-family: "IBM Plex Mono";
src: url("woff2/IBMPlexMono-Regular.woff2") format("woff2"),
url("woff/IBMPlexMono-Regular.woff") format("woff");
src:
url("woff2/IBMPlexMono-Regular.woff2") format("woff2"),
url("woff/IBMPlexMono-Regular.woff") format("woff");
font-weight: 400;
font-style: normal;
font-display: swap;
@ -9,8 +10,9 @@
@font-face {
font-family: "IBM PLex Mono";
src: url("woff2/IBMPlexMono-Italic.woff2") format("woff2"),
url("woff/IBMPlexMono-Italic.woff") format("woff");
src:
url("woff2/IBMPlexMono-Italic.woff2") format("woff2"),
url("woff/IBMPlexMono-Italic.woff") format("woff");
font-weight: 400;
font-style: italic;
font-display: swap;
@ -18,8 +20,9 @@
@font-face {
font-family: "IBM PLex Mono";
src: url("woff2/IBMPlexMono-Bold.woff2") format("woff2"),
url("woff/IBMPlexMono-Bold.woff") format("woff");
src:
url("woff2/IBMPlexMono-Bold.woff2") format("woff2"),
url("woff/IBMPlexMono-Bold.woff") format("woff");
font-weight: 700;
font-style: normal;
font-display: swap;
@ -27,8 +30,9 @@
@font-face {
font-family: "IBM Plex Mono";
src: url("woff2/IBMPlexMono-BoldItalic.woff2") format("woff2"),
url("woff/IBMPlexMono-BoldItalic.woff") format("woff");
src:
url("woff2/IBMPlexMono-BoldItalic.woff2") format("woff2"),
url("woff/IBMPlexMono-BoldItalic.woff") format("woff");
font-weight: 700;
font-style: italic;
font-display: swap;
@ -37,84 +41,85 @@
@font-face {
font-family: "Comic Mono";
font-weight: normal;
src: url(./woff/ComicMono.woff) format("woff"),
url(./woff2/ComicMono.woff2) format("wooff2");
src:
url(./woff/ComicMono.woff) format("woff"),
url(./woff2/ComicMono.woff2) format("wooff2");
}
@font-face {
font-family: "Comic Mono";
font-weight: bold;
src: url(./woff/ComicMono-Bold.woff) format("woff"),
url(./woff/ComicMono-Bold.woff2) format("woff2"),
}
@font-face {
font-family: 'jgs7';
src: url('./woff2/jgs7.woff2') format('woff2'),
url('./woff/jgs7.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
src:
url(./woff/ComicMono-Bold.woff) format("woff"),
url(./woff/ComicMono-Bold.woff2) format("woff2");
}
@font-face {
font-family: 'jgs5';
src: url('./woff2/jgs5.woff2') format('woff2'),
url('./woff/jgs5.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
font-family: "jgs7";
src:
url("./woff2/jgs7.woff2") format("woff2"),
url("./woff/jgs7.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'jgs9';
src: url('./woff2/jgs9.woff2') format('woff2'),
url('./woff/jgs9.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'jgs_vecto';
src: url('./woff2/jgs_vecto.woff2') format('woff2');
font-weight: normal;
font-style: normal;
font-display: swap;
font-family: "jgs5";
src:
url("./woff2/jgs5.woff2") format("woff2"),
url("./woff/jgs5.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Steps Mono';
src: url('./woff2/Steps-Mono.woff2') format('woff2');
font-weight: normal;
font-style: normal;
font-display: swap;
font-family: "jgs9";
src:
url("./woff2/jgs9.woff2") format("woff2"),
url("./woff/jgs9.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Steps Mono Thin';
src: url('./woff2/Steps-Mono-Thin.woff2') format('woff2');
font-weight: normal;
font-style: normal;
font-display: swap;
font-family: "jgs_vecto";
src: url("./woff2/jgs_vecto.woff2") format("woff2");
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Jet Brains';
src: url('./woff2/JetBrainsMono-Regular.woff2') format('woff2');
font-weight: normal;
font-style: normal;
font-display: swap;
font-family: "Steps Mono";
src: url("./woff2/Steps-Mono.woff2") format("woff2");
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Jet Brains';
src: url('./woff2/JetBrainsMono-Bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
font-family: "Steps Mono Thin";
src: url("./woff2/Steps-Mono-Thin.woff2") format("woff2");
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Jet Brains";
src: url("./woff2/JetBrainsMono-Regular.woff2") format("woff2");
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Jet Brains";
src: url("./woff2/JetBrainsMono-Bold.woff2") format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}

View File

@ -3,4 +3,4 @@ export default {
tailwindcss: {},
autoprefixer: {},
},
}
};

View File

@ -111,7 +111,7 @@ export class UserAPI {
}
this.app.settings.saveApplicationToLocalStorage(
this.app.universes,
this.app.settings
this.app.settings,
);
this.app.updateKnownUniversesView();
};
@ -203,7 +203,7 @@ export class UserAPI {
// @ts-ignore
this.errorTimeoutID = setTimeout(
() => this.app.interface.error_line.classList.add("hidden"),
2000
2000,
);
};
@ -217,7 +217,7 @@ export class UserAPI {
// @ts-ignore
this.printTimeoutID = setTimeout(
() => this.app.interface.error_line.classList.add("hidden"),
4000
4000,
);
};
@ -268,7 +268,7 @@ export class UserAPI {
*/
this.app.clock.tick = beat * this.app.clock.ppqn;
this.app.clock.time_position = this.app.clock.convertTicksToTimeposition(
beat * this.app.clock.ppqn
beat * this.app.clock.ppqn,
);
};
@ -325,7 +325,7 @@ export class UserAPI {
blinkScript(this.app, "local", arg);
tryEvaluate(
this.app,
this.app.universes[this.app.selected_universe].locals[arg]
this.app.universes[this.app.selected_universe].locals[arg],
);
}
});
@ -372,7 +372,7 @@ export class UserAPI {
delete this.app.universes[universe];
this.app.settings.saveApplicationToLocalStorage(
this.app.universes,
this.app.settings
this.app.settings,
);
this.app.updateKnownUniversesView();
};
@ -388,7 +388,7 @@ export class UserAPI {
};
this.app.settings.saveApplicationToLocalStorage(
this.app.universes,
this.app.settings
this.app.settings,
);
}
this.app.selected_universe = "Default";
@ -425,7 +425,7 @@ export class UserAPI {
value: number | number[] = 60,
velocity?: number | number[],
channel?: number | number[],
port?: number | string | number[] | string[]
port?: number | string | number[] | string[],
): MidiEvent => {
/**
* Sends a MIDI note to the current MIDI output.
@ -500,7 +500,7 @@ export class UserAPI {
};
public active_note_events = (
channel?: number
channel?: number,
): MidiNoteEvent[] | undefined => {
/**
* @returns A list of currently active MIDI notes
@ -637,7 +637,7 @@ export class UserAPI {
scale: number | string,
channel: number = 0,
port: number | string = this.MidiConnection.currentOutputIndex || 0,
soundOff: boolean = false
soundOff: boolean = false,
): void => {
/**
* Sends given scale to midi output for visual aid
@ -661,7 +661,7 @@ export class UserAPI {
// @ts-ignore
scale: number | string = 0,
channel: number = 0,
port: number | string = this.MidiConnection.currentOutputIndex || 0
port: number | string = this.MidiConnection.currentOutputIndex || 0,
): void => {
/**
* Hides all notes by sending all notes off to midi output
@ -676,7 +676,7 @@ export class UserAPI {
midi_notes_off = (
channel: number = 0,
port: number | string = this.MidiConnection.currentOutputIndex || 0
port: number | string = this.MidiConnection.currentOutputIndex || 0,
): void => {
/**
* Sends all notes off to midi output
@ -686,7 +686,7 @@ export class UserAPI {
midi_sound_off = (
channel: number = 0,
port: number | string = this.MidiConnection.currentOutputIndex || 0
port: number | string = this.MidiConnection.currentOutputIndex || 0,
): void => {
/**
* Sends all sound off to midi output
@ -713,7 +713,7 @@ export class UserAPI {
public z = (
input: string | Generator<number>,
options: InputOptions = {},
id: number | string = ""
id: number | string = "",
): Player => {
const zid = "z" + id.toString();
const key = id === "" ? this.generateCacheKey(input, options) : zid;
@ -790,7 +790,7 @@ export class UserAPI {
public counter = (
name: string | number,
limit?: number,
step?: number
step?: number,
): number => {
/**
* Returns the current value of a counter, and increments it by the step value.
@ -1297,8 +1297,8 @@ export class UserAPI {
const results: boolean[] = nArray.map(
(value) =>
(this.app.clock.pulses_since_origin - Math.floor(nudge * this.ppqn())) %
Math.floor(value * this.ppqn()) ===
0
Math.floor(value * this.ppqn()) ===
0,
);
return results.some((value) => value === true);
};
@ -1317,8 +1317,8 @@ export class UserAPI {
const results: boolean[] = nArray.map(
(value) =>
(this.app.clock.pulses_since_origin - nudgeInPulses) %
Math.floor(value * barLength) ===
0
Math.floor(value * barLength) ===
0,
);
return results.some((value) => value === true);
};
@ -1333,7 +1333,7 @@ export class UserAPI {
*/
const nArray = Array.isArray(n) ? n : [n];
const results: boolean[] = nArray.map(
(value) => (this.app.clock.pulses_since_origin - nudge) % value === 0
(value) => (this.app.clock.pulses_since_origin - nudge) % value === 0,
);
return results.some((value) => value === true);
};
@ -1342,7 +1342,7 @@ export class UserAPI {
public tick = (tick: number | number[], offset: number = 0): boolean => {
const nArray = Array.isArray(tick) ? tick : [tick];
const results: boolean[] = nArray.map(
(value) => this.app.clock.time_position.pulse === value + offset
(value) => this.app.clock.time_position.pulse === value + offset,
);
return results.some((value) => value === true);
};
@ -1391,7 +1391,7 @@ export class UserAPI {
public onbar = (
bars: number[] | number,
n: number = this.app.clock.time_signature[0]
n: number = this.app.clock.time_signature[0],
): boolean => {
let current_bar = (this.app.clock.time_position.bar % n) + 1;
return typeof bars === "number"
@ -1419,7 +1419,7 @@ export class UserAPI {
if (decimal_part <= 0)
decimal_part = decimal_part + this.ppqn() * this.nominator();
final_pulses.push(
integral_part === this.cbeat() && this.cpulse() === decimal_part
integral_part === this.cbeat() && this.cpulse() === decimal_part,
);
});
return final_pulses.some((p) => p == true);
@ -1501,7 +1501,7 @@ export class UserAPI {
iterator: number,
pulses: number,
length: number,
rotate: number = 0
rotate: number = 0,
): boolean => {
/**
* Returns a euclidean cycle of size length, with n pulses, rotated or not.
@ -1520,7 +1520,7 @@ export class UserAPI {
div: number,
pulses: number,
length: number,
rotate: number = 0
rotate: number = 0,
): boolean => {
return (
this.beat(div) && this._euclidean_cycle(pulses, length, rotate).beat(div)
@ -1530,7 +1530,7 @@ export class UserAPI {
_euclidean_cycle(
pulses: number,
length: number,
rotate: number = 0
rotate: number = 0,
): boolean[] {
if (pulses == length) return Array.from({ length }, () => true);
function startsDescent(list: number[], i: number): boolean {
@ -1541,7 +1541,7 @@ export class UserAPI {
if (pulses >= length) return [true];
const resList = Array.from(
{ length },
(_, i) => (((pulses * (i - 1)) % length) + length) % length
(_, i) => (((pulses * (i - 1)) % length) + length) % length,
);
let cycle = resList.map((_, i) => startsDescent(resList, i));
if (rotate != 0) {
@ -1660,7 +1660,7 @@ export class UserAPI {
triangle = (
freq: number = 1,
times: number = 1,
offset: number = 0
offset: number = 0,
): number => {
/**
* Returns a triangle wave between -1 and 1.
@ -1677,7 +1677,7 @@ export class UserAPI {
utriangle = (
freq: number = 1,
times: number = 1,
offset: number = 0
offset: number = 0,
): number => {
/**
* Returns a triangle wave between 0 and 1.
@ -1694,7 +1694,7 @@ export class UserAPI {
freq: number = 1,
times: number = 1,
offset: number = 0,
duty: number = 0.5
duty: number = 0.5,
): number => {
/**
* Returns a square wave with a specified duty cycle between -1 and 1.
@ -1714,7 +1714,7 @@ export class UserAPI {
freq: number = 1,
times: number = 1,
offset: number = 0,
duty: number = 0.5
duty: number = 0.5,
): number => {
/**
* Returns a square wave between 0 and 1.
@ -1774,7 +1774,7 @@ export class UserAPI {
*/
const sum = values.reduce(
(accumulator, currentValue) => accumulator + currentValue,
0
0,
);
return sum / values.length;
};
@ -1784,7 +1784,7 @@ export class UserAPI {
yMin: number,
yMax: number,
xMin: number,
xMax: number
xMax: number,
): number => {
const percent = (inputY - yMin) / (yMax - yMin);
const outputX = percent * (xMax - xMin) + xMin;
@ -1814,7 +1814,7 @@ export class UserAPI {
lang: string = "en-US",
voice: number = 0,
rate: number = 1,
pitch: number = 1
pitch: number = 1,
): void => {
/*
* Speaks the given text using the browser's speech synthesis API.
@ -1889,7 +1889,7 @@ export class UserAPI {
const elements = args.slice(1); // Get the rest of the arguments as an array
const timepos = this.app.clock.pulses_since_origin;
const slice_count = Math.floor(
timepos / Math.floor(chunk_size * this.ppqn())
timepos / Math.floor(chunk_size * this.ppqn()),
);
return elements[slice_count % elements.length];
};
@ -1917,7 +1917,7 @@ export class UserAPI {
// =============================================================
register = (name: string, operation: EventOperation<AbstractEvent>): void => {
AbstractEvent.prototype[name] = function(
AbstractEvent.prototype[name] = function (
this: AbstractEvent,
...args: any[]
) {
@ -2024,7 +2024,7 @@ export class UserAPI {
".cm-comment": {
fontFamily: commentFont,
},
})
}),
),
});
};

View File

@ -14,7 +14,7 @@ export const drawCircle = (
x: number,
y: number,
radius: number,
color: string
color: string,
): void => {
// @ts-ignore
const canvas: HTMLCanvasElement = app.interface.feedback;
@ -36,7 +36,7 @@ export const blinkScript = (
*/
app: Editor,
script: "local" | "global" | "init",
no?: number
no?: number,
) => {
if (no !== undefined && no < 1 && no > 9) return;
const blinkDuration =
@ -55,7 +55,7 @@ export const blinkScript = (
horizontalOffset + shift,
app.interface.feedback.clientHeight - 15,
8,
"#fdba74"
"#fdba74",
);
};
@ -91,7 +91,7 @@ export const blinkScript = (
0,
0,
(app.interface.feedback as HTMLCanvasElement).width,
(app.interface.feedback as HTMLCanvasElement).height
(app.interface.feedback as HTMLCanvasElement).height,
);
}, blinkDuration);
}
@ -137,7 +137,7 @@ let lastRenderTime: number = 0;
export const runOscilloscope = (
canvas: HTMLCanvasElement,
app: Editor
app: Editor,
): void => {
/**
* Runs the oscilloscope visualization on the provided canvas element.
@ -157,7 +157,7 @@ export const runOscilloscope = (
width: number,
height: number,
offset_height: number,
offset_width: number
offset_width: number,
) {
const maxFPS = 30;
const now = performance.now();
@ -172,11 +172,11 @@ export const runOscilloscope = (
const performanceFactor = 1;
const reducedDataSize = Math.floor(
freqDataArray.length * performanceFactor
freqDataArray.length * performanceFactor,
);
const numBars = Math.min(
reducedDataSize,
app.osc.orientation === "horizontal" ? width : height
app.osc.orientation === "horizontal" ? width : height,
);
const barWidth =
app.osc.orientation === "horizontal" ? width / numBars : height / numBars;
@ -189,7 +189,7 @@ export const runOscilloscope = (
for (let i = 0; i < numBars; i++) {
barHeight = Math.floor(
freqDataArray[Math.floor((i * freqDataArray.length) / numBars)] *
((height / 256) * app.osc.size)
((height / 256) * app.osc.size),
);
if (app.osc.orientation === "horizontal") {
@ -197,7 +197,7 @@ export const runOscilloscope = (
x + offset_width,
(height - barHeight) / 2 + offset_height,
barWidth + 1,
barHeight
barHeight,
);
x += barWidth;
} else {
@ -205,7 +205,7 @@ export const runOscilloscope = (
(width - barHeight) / 2 + offset_width,
y + offset_height,
barHeight,
barWidth + 1
barWidth + 1,
);
y += barWidth;
}
@ -234,7 +234,7 @@ export const runOscilloscope = (
-OFFSET_WIDTH,
-OFFSET_HEIGHT,
WIDTH + 2 * OFFSET_WIDTH,
HEIGHT + 2 * OFFSET_HEIGHT
HEIGHT + 2 * OFFSET_HEIGHT,
);
return;
}
@ -261,7 +261,7 @@ export const runOscilloscope = (
-OFFSET_WIDTH,
-OFFSET_HEIGHT,
WIDTH + 2 * OFFSET_WIDTH,
HEIGHT + 2 * OFFSET_HEIGHT
HEIGHT + 2 * OFFSET_HEIGHT,
);
}
canvasCtx.lineWidth = app.osc.thickness;

View File

@ -48,7 +48,10 @@ export class Clock {
lastPlayPressTime: number;
totalPauseTime: number;
constructor(public app: Editor, ctx: AudioContext) {
constructor(
public app: Editor,
ctx: AudioContext,
) {
this.time_position = { bar: 0, beat: 0, pulse: 0 };
this.time_signature = [4, 4];
this.logicalTime = 0;

View File

@ -47,7 +47,7 @@ export const makeExampleFactory = (application: Editor): Function => {
const make_example = (
description: string,
code: string,
open: boolean = false
open: boolean = false,
) => {
const codeId = `codeExample${application.exampleCounter++}`;
// Store the code snippet in the data structure
@ -160,7 +160,7 @@ export const updateDocumentationContent = (app: Editor, bindings: any) => {
extensions: [showdownHighlight({ auto_detection: true }), ...bindings],
});
const converted_markdown = converter.makeHtml(
app.docs[app.currentDocumentationPane]
app.docs[app.currentDocumentationPane],
);
document.getElementById("documentation-content")!.innerHTML =
converted_markdown;

View File

@ -8,7 +8,7 @@ const codeReplace = (code: string): string => {
const tryCatchWrapper = async (
application: Editor,
code: string
code: string,
): Promise<boolean> => {
/**
* Wraps the provided code in a try-catch block and executes it.
@ -20,7 +20,7 @@ const tryCatchWrapper = async (
try {
await new Function(`"use strict"; ${codeReplace(code)}`).call(
application.api
application.api,
);
return true;
} catch (error) {
@ -48,7 +48,7 @@ const addFunctionToCache = (code: string, fn: Function) => {
export const tryEvaluate = async (
application: Editor,
code: File,
timeout = 5000
timeout = 5000,
): Promise<void> => {
/**
* Tries to evaluate the provided code within a specified timeout period.
@ -77,7 +77,7 @@ export const tryEvaluate = async (
if (isCodeValid) {
code.committed = code.candidate;
const newFunction = new Function(
`"use strict"; ${codeReplace(wrappedCode)}`
`"use strict"; ${codeReplace(wrappedCode)}`,
);
addFunctionToCache(candidateCode, newFunction);
} else {
@ -93,7 +93,7 @@ export const tryEvaluate = async (
export const evaluate = async (
application: Editor,
code: File,
timeout = 1000
timeout = 1000,
): Promise<void> => {
/**
* Evaluates the given code using the provided application and timeout.
@ -117,7 +117,7 @@ export const evaluate = async (
export const evaluateOnce = async (
application: Editor,
code: string
code: string,
): Promise<void> => {
/**
* Evaluates the code once without any caching or error-handling mechanisms besides the tryCatchWrapper.

View File

@ -154,7 +154,7 @@ export class AppSettings {
constructor() {
const settingsFromStorage = JSON.parse(
localStorage.getItem("topos") || "{}"
localStorage.getItem("topos") || "{}",
);
if (settingsFromStorage && Object.keys(settingsFromStorage).length !== 0) {
@ -210,7 +210,7 @@ export class AppSettings {
saveApplicationToLocalStorage(
universes: Universes,
settings: Settings
settings: Settings,
): void {
/**
* Main method to store the application to local storage.
@ -328,7 +328,7 @@ export const loadUniverserFromUrl = (app: Editor): void => {
export const loadUniverse = (
app: Editor,
universeName: string,
universe: Universe = template_universe
universe: Universe = template_universe,
): void => {
/**
* Loads a universe into the application.

View File

@ -1,6 +1,6 @@
export function objectWithArraysToArrayOfObjects(
input: Record<string, any>,
arraysToArrays: string[]
arraysToArrays: string[],
): Record<string, any>[] {
/*
* Transforms object with arrays into array of objects
@ -24,7 +24,7 @@ export function objectWithArraysToArrayOfObjects(
acc.keys.push(key);
return acc;
},
{ keys: [] as string[], maxLength: 0 }
{ keys: [] as string[], maxLength: 0 },
);
const output: Record<string, any>[] = [];
@ -44,7 +44,7 @@ export function objectWithArraysToArrayOfObjects(
export function arrayOfObjectsToObjectWithArrays<T extends Record<string, any>>(
array: T[],
mergeObject: Record<string, any> = {}
mergeObject: Record<string, any> = {},
): Record<string, any> {
/*
* Transforms array of objects into object with arrays
@ -54,21 +54,24 @@ export function arrayOfObjectsToObjectWithArrays<T extends Record<string, any>>(
* @returns {object} Merged object with arrays
*
*/
return array.reduce((acc, obj) => {
const mergedObj = { ...obj, ...mergeObject };
Object.keys(mergedObj).forEach((key) => {
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(mergedObj[key]);
});
return acc;
}, {} as Record<string, any>);
return array.reduce(
(acc, obj) => {
const mergedObj = { ...obj, ...mergeObject };
Object.keys(mergedObj).forEach((key) => {
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(mergedObj[key]);
});
return acc;
},
{} as Record<string, any>,
);
}
export function filterObject(
obj: Record<string, any>,
filter: string[]
filter: string[],
): Record<string, any> {
/*
* Filter certain keys from object
@ -79,6 +82,6 @@ export function filterObject(
*
*/
return Object.fromEntries(
Object.entries(obj).filter(([key]) => filter.includes(key))
Object.entries(obj).filter(([key]) => filter.includes(key)),
);
}

View File

@ -32,13 +32,13 @@ export const saveBeforeExit = (app: Editor): null => {
export const installWindowBehaviors = (
app: Editor,
window: Window,
preventMultipleTabs: boolean = false
preventMultipleTabs: boolean = false,
) => {
window.addEventListener("resize", () =>
handleResize(app.interface.scope as HTMLCanvasElement)
handleResize(app.interface.scope as HTMLCanvasElement),
);
window.addEventListener("resize", () =>
handleResize(app.interface.feedback as HTMLCanvasElement)
handleResize(app.interface.feedback as HTMLCanvasElement),
);
window.addEventListener("beforeunload", (event) => {
event.preventDefault();
@ -61,11 +61,11 @@ export const installWindowBehaviors = (
if (e.key == "page_available") {
document.getElementById("all")!.classList.add("invisible");
alert(
"Topos is already opened in another tab. Close this tab now to prevent data loss."
"Topos is already opened in another tab. Close this tab now to prevent data loss.",
);
}
},
false
false,
);
}
};

View File

@ -38,7 +38,7 @@ export class SoundEvent extends AudibleEvent {
public updateValue<T>(
key: string,
value: T | T[] | SoundParams[] | null
value: T | T[] | SoundParams[] | null,
): this {
if (value == null) return this;
this.values[key] = value;
@ -82,7 +82,7 @@ export class SoundEvent extends AudibleEvent {
a: number,
d: number,
s: number,
r: number
r: number,
) {
self.updateValue("fmattack", a);
self.updateValue("fmdecay", d);
@ -106,7 +106,7 @@ export class SoundEvent extends AudibleEvent {
a: number,
d: number,
s: number,
r: number
r: number,
) {
self.updateValue("attack", a);
self.updateValue("decay", d);
@ -152,7 +152,7 @@ export class SoundEvent extends AudibleEvent {
a: number,
d: number,
s: number,
r: number
r: number,
) {
self.updateValue("lpenv", depth);
self.updateValue("lpattack", a);
@ -198,7 +198,7 @@ export class SoundEvent extends AudibleEvent {
a: number,
d: number,
s: number,
r: number
r: number,
) {
self.updateValue("hpenv", depth);
self.updateValue("hpattack", a);
@ -241,7 +241,7 @@ export class SoundEvent extends AudibleEvent {
a: number,
d: number,
s: number,
r: number
r: number,
) {
self.updateValue("bpenv", depth);
self.updateValue("bpattack", a);
@ -336,7 +336,10 @@ export class SoundEvent extends AudibleEvent {
},
};
constructor(sound: string | string[] | SoundParams, public app: Editor) {
constructor(
sound: string | string[] | SoundParams,
public app: Editor,
) {
super(app);
this.nudge = app.dough_nudge / 100;
@ -367,7 +370,7 @@ export class SoundEvent extends AudibleEvent {
}
private processSound = (
sound: string | string[] | SoundParams | SoundParams[]
sound: string | string[] | SoundParams | SoundParams[],
): SoundParams => {
if (Array.isArray(sound) && typeof sound[0] === "string") {
const s: string[] = [];
@ -438,7 +441,7 @@ export class SoundEvent extends AudibleEvent {
(event.key as number) || "C4",
(event.pitch as number) || 0,
(event.parsedScale as number[]) || event.scale || "MAJOR",
(event.octave as number) || 0
(event.octave as number) || 0,
);
event.note = note;
event.freq = midiToFreq(note);
@ -458,7 +461,7 @@ export class SoundEvent extends AudibleEvent {
public invert = (howMany: number = 0) => {
if (this.values.chord) {
let notes = this.values.chord.map(
(obj: { [key: string]: number }) => obj.note
(obj: { [key: string]: number }) => obj.note,
);
notes = howMany < 0 ? [...notes].reverse() : notes;
for (let i = 0; i < Math.abs(howMany); i++) {
@ -500,7 +503,7 @@ export class SoundEvent extends AudibleEvent {
superdough(
filteredEvent,
this.nudge - this.app.clock.deviation,
filteredEvent.dur
filteredEvent.dur,
);
}
};

View File

@ -29,7 +29,7 @@ export class Player extends AbstractEvent {
input: string | number | Generator<number>,
options: InputOptions,
public app: Editor,
zid: string = ""
zid: string = "",
) {
super(app);
this.options = options;
@ -159,7 +159,7 @@ export class Player extends AbstractEvent {
if (this.areWeThereYet()) {
const event = this.next() as Pitch | Chord | ZRest;
const noteLengthInSeconds = this.app.clock.convertPulseToSecond(
event.duration * 4 * this.app.clock.ppqn
event.duration * 4 * this.app.clock.ppqn,
);
if (event instanceof Pitch) {
const obj = event.getExisting(
@ -169,7 +169,7 @@ export class Player extends AbstractEvent {
"key",
"scale",
"octave",
"parsedScale"
"parsedScale",
) as SoundParams;
if (event.sound) name = event.sound as string;
if (event.soundIndex) obj.n = event.soundIndex as number;
@ -184,14 +184,14 @@ export class Player extends AbstractEvent {
"key",
"scale",
"octave",
"parsedScale"
"parsedScale",
);
}) as SoundParams[];
const add = { dur: noteLengthInSeconds } as SoundParams;
if (name) add.s = name;
let sound = arrayOfObjectsToObjectWithArrays(
pitches,
add
add,
) as SoundParams;
return new SoundEvent(sound, this.app);
} else if (event instanceof ZRest) {
@ -212,7 +212,7 @@ export class Player extends AbstractEvent {
"key",
"scale",
"octave",
"parsedScale"
"parsedScale",
) as MidiParams;
if (event instanceof Pitch) {
if (event.soundIndex) obj.channel = event.soundIndex as number;

View File

@ -20,11 +20,11 @@ Some features have been included as a bonus. These features are often about patt
${makeExample(
"Hydra integration",
`beat(4) :: hydra.osc(3, 0.5, 2).out()`,
true
true,
)}
Close the documentation to see the effect: ${key_shortcut(
"Ctrl+D"
"Ctrl+D",
)}! **Boom, all shiny!**
Be careful not to call <ic>hydra</ic> too often as it can impact performances. You can use any rhythmical function like <ic>beat()</ic> function to limit the number of function calls. You can write any Topos code like <ic>[1,2,3].beat()</ic> to bring some life and movement in your Hydra sketches.
@ -37,7 +37,7 @@ ${makeExample(
beat(4) :: stop_hydra() // this one
beat(4) :: hydra.hush() // or this one
`,
true
true,
)}
@ -48,7 +48,7 @@ You can change Hydra resolution using this simple method:
${makeExample(
"Changing Hydra resolution",
`hydra.setResolution(1024, 768)`,
true
true,
)}
### Documentation
@ -82,7 +82,7 @@ beat(0.25)::gif({
posX: ir(1,1200), // CSS Horizontal Position
posY: ir(1, 800), // CSS Vertical Position
`,
true
true,
)}
`;
};

View File

@ -65,12 +65,12 @@ On this page, you will find an exhaustive list of all the samples currently load
A very large collection of wavetables for wavetable synthesis. This collection has been released by Kristoffer Ekstrand: [AKWF Waveforms](https://www.adventurekid.se/akrt/waveforms/adventure-kid-waveforms/). Every sound sample that starts with <ic>wt_</ic> will be looped. Look at this demo:
${makeExample(
"Wavetable synthesis made easy :)",
`
"Wavetable synthesis made easy :)",
`
beat(0.5)::sound('wt_stereo').n([0, 1].pick()).ad(0, .25).out()
`,
true,
)}
true,
)}
Pick one folder and spend some time exploring it. There is a lot of different waveforms.
@ -84,12 +84,12 @@ ${samples_to_markdown(application, "Waveforms")}
A set of 72 classic drum machines created by **Geikha**: [Geikha Drum Machines](https://github.com/geikha/tidal-drum-machines). To use them efficiently, it is best to use the <ic>.bank()</ic> parameter like so:
${makeExample(
"Using a classic drum machine",
`
"Using a classic drum machine",
`
beat(0.5)::sound(['bd', 'cp'].pick()).bank("AkaiLinn").out()
`,
true,
)}
true,
)}
Here is the complete list of available machines:
@ -119,12 +119,12 @@ ${samples_to_markdown(application, "Amiga")}
A collection of many different amen breaks. Use <ic>.stretch()</ic> to play with these:
${makeExample(
"Stretching an amen break",
`
"Stretching an amen break",
`
beat(4)::sound('amen1').stretch(4).out()
`,
true,
)}
true,
)}
The stretch should be adapted based on the length of each amen break.

View File

@ -246,7 +246,7 @@ export class Editor {
* If any required elements or templates are missing, warning messages are logged and the function returns early.
*/
let itemTemplate = document.getElementById(
"ui-known-universe-item-template"
"ui-known-universe-item-template",
) as HTMLTemplateElement;
if (!itemTemplate) {
console.warn("Missing template #ui-known-universe-item-template");
@ -274,10 +274,10 @@ export class Editor {
item
.querySelector(".delete-universe")
?.addEventListener("click", () =>
api._deleteUniverseFromInterface(it)
api._deleteUniverseFromInterface(it),
);
return item;
})
}),
);
existing_universes.innerHTML = "";
@ -369,7 +369,7 @@ export class Editor {
this.view.dispatch({
effects: this.chosenLanguage.reconfigure(
this.editor_mode == "notes" ? [markdown()] : [javascript()]
this.editor_mode == "notes" ? [markdown()] : [javascript()],
),
});
@ -378,7 +378,7 @@ export class Editor {
setButtonHighlighting(
button: "play" | "pause" | "stop" | "clear",
highlight: boolean
highlight: boolean,
) {
/**
* Sets the highlighting for a specific button.
@ -432,7 +432,7 @@ export class Editor {
// All other buttons must lose the highlighting
document
.querySelectorAll(
possible_selectors.filter((_, index) => index != selector).join(",")
possible_selectors.filter((_, index) => index != selector).join(","),
)
.forEach((button) => {
button.children[0].classList.remove("animate-pulse");
@ -478,28 +478,28 @@ export class Editor {
*/
const domElement = this.view.dom;
const gutters = domElement.getElementsByClassName(
"cm-gutter"
"cm-gutter",
) as HTMLCollectionOf<HTMLElement>;
domElement.classList.add("fluid-bg-transition");
Array.from(gutters).forEach((gutter) =>
gutter.classList.add("fluid-bg-transition")
gutter.classList.add("fluid-bg-transition"),
);
domElement.style.backgroundColor = color;
Array.from(gutters).forEach(
(gutter) => (gutter.style.backgroundColor = color)
(gutter) => (gutter.style.backgroundColor = color),
);
setTimeout(() => {
domElement.style.backgroundColor = "";
Array.from(gutters).forEach(
(gutter) => (gutter.style.backgroundColor = "")
(gutter) => (gutter.style.backgroundColor = ""),
);
domElement.classList.remove("fluid-bg-transition");
Array.from(gutters).forEach((gutter) =>
gutter.classList.remove("fluid-bg-transition")
gutter.classList.remove("fluid-bg-transition"),
);
}, duration);
}
@ -507,7 +507,7 @@ export class Editor {
private initializeElements(): void {
for (const [key, value] of Object.entries(singleElements)) {
this.interface[key] = document.getElementById(
value
value,
) as ElementMap[keyof ElementMap];
}
}
@ -515,7 +515,7 @@ export class Editor {
private initializeButtonGroups(): void {
for (const [key, ids] of Object.entries(buttonGroups)) {
this.buttonElements[key] = ids.map(
(id) => document.getElementById(id) as HTMLButtonElement
(id) => document.getElementById(id) as HTMLButtonElement,
);
}
}

View File

@ -15,7 +15,7 @@
/* Linting */
"strict": true,
"forceConsistentCasingInFileNames": true,
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": true,
"noUnusedLocals": true,
"noUnusedParameters": true,