Compare commits
305 Commits
clockstuff
...
fixes
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e60504de5 | |||
| fc0c7cc34c | |||
| bb6a52bd4b | |||
| 01235727c7 | |||
| b61ea25836 | |||
| 2b6e092d37 | |||
| 7f48b94ffa | |||
| 0d2f7046c9 | |||
| 1824a345fc | |||
| 605db460e5 | |||
| 0b09f16624 | |||
| 2304015975 | |||
| 9dfac1141f | |||
| 8c0555b02f | |||
| 4c7cfb44ce | |||
| d3ac9f19a2 | |||
| 75481e19f0 | |||
| 137840778a | |||
| 13cf95b71e | |||
| cee061a100 | |||
| 7100d5a470 | |||
| 75daa2cf6a | |||
| 73c0df333a | |||
| 2870cb124a | |||
| fcae478461 | |||
| e06119ba8c | |||
| 5548d30da8 | |||
| 4b6275d2e0 | |||
| 4801f78deb | |||
| 57f0c9dfe6 | |||
| b2a8e18b0e | |||
| 358195bf97 | |||
| 0a6d779867 | |||
| a21d6c9a88 | |||
| 7cef78bc3b | |||
| d395c9487a | |||
| b222fc25c9 | |||
| d2dee8f371 | |||
| aa2eb0651d | |||
| ec68419775 | |||
| cf702fd9f1 | |||
| 5e565e3a11 | |||
| d9906857d3 | |||
| e592499711 | |||
| c13d1bb072 | |||
| d2f9376197 | |||
| 8940bf3505 | |||
| 9d7fe9e815 | |||
| 59dc42b103 | |||
| 47534a0724 | |||
| a36aa53e04 | |||
| 4db41275b4 | |||
| 8b10af555c | |||
| 2ee66cd9fb | |||
| 694b994227 | |||
| f451b81ea7 | |||
| 70b4ce714c | |||
| 7bf69a1d27 | |||
| 588934d113 | |||
| b4f2ff0fd9 | |||
| 835a30eafb | |||
| d0f62231d8 | |||
| 3314c089ed | |||
| 6dfbdbb6d4 | |||
| e5afc41fd4 | |||
| 85b0306bb6 | |||
| 61051c9e42 | |||
| 2039ee8518 | |||
| b4b507b2d6 | |||
| 3ddcc38f87 | |||
| 96ef7b04ee | |||
| 05692a61fa | |||
| 0e939a81c7 | |||
| 2ee01186f8 | |||
| 385c023446 | |||
| 1a72125a45 | |||
| 4ec0c66484 | |||
| d5d7d5ca7f | |||
| df025751fc | |||
| 0d04fb0ebd | |||
| 4913dde4a1 | |||
| fea2a3eb21 | |||
| b30fd06e7b | |||
| 2d933ae223 | |||
| 95650c5d01 | |||
| 30983147ea | |||
| 3556b180cf | |||
| 98f431f6b2 | |||
| 4421c37527 | |||
| f797434f6a | |||
| 85f0da3652 | |||
| 3afc278926 | |||
| 4e2ec4e08b | |||
| 5fc7ce3c12 | |||
| d4fed334ab | |||
| fd634ee85f | |||
| 6d1624ffd6 | |||
| 8757d7906a | |||
| 30caa07a17 | |||
| 24dabca102 | |||
| 78c0a67a77 | |||
| 36b5a07199 | |||
| 14d5c39fbe | |||
| 6f886ecc10 | |||
| b01449ee60 | |||
| 70cf7f2562 | |||
| d977f2e8f2 | |||
| b47d041a99 | |||
| 678b3305ac | |||
| facb30be3a | |||
| ae96d20b70 | |||
| 83e901491f | |||
| adf343e0bf | |||
| ecd68ae7c6 | |||
| 6f1f879f5e | |||
| b491637794 | |||
| 88ad863664 | |||
| 591332576e | |||
| a1e664eaa3 | |||
| c0cb7887c0 | |||
| 251b7ed277 | |||
| 37f8581b42 | |||
| 6ca338fac4 | |||
| d44d016357 | |||
| 024b083726 | |||
| 4254136584 | |||
| 2606b8f989 | |||
| b8e197d64a | |||
| 620ca7af59 | |||
| 6305e0ce65 | |||
| e557e5565b | |||
| 331ddab544 | |||
| f46565f5c2 | |||
| 8819a159ff | |||
| caabfc2e65 | |||
| 7a5f15b29d | |||
| 20d2e3a176 | |||
| 62ed707c59 | |||
| 0ba7ed2756 | |||
| 9458733492 | |||
| 7086682336 | |||
| 3c602dc63b | |||
| 5456410d08 | |||
| 61fb6365a0 | |||
| 11be35c677 | |||
| 122cd55ea2 | |||
| f9bce56f9e | |||
| ad6f8a5e91 | |||
| b5988d07f6 | |||
| ffaf7ea157 | |||
| 88ceb99bae | |||
| d5e34d2728 | |||
| ccd56bb805 | |||
| 16c4117c5a | |||
| 04142dbbbc | |||
| 84955cb355 | |||
| 117bc020e7 | |||
| 1f06e855d1 | |||
| ff333a0526 | |||
| 13360faf0c | |||
| 29617fb0f2 | |||
| 9bab06ad2a | |||
| 88358e1254 | |||
| 07df2a8bdc | |||
| a7b8a846c0 | |||
| e7129585b1 | |||
| 5eb4f29120 | |||
| 4156c0d399 | |||
| cb882bdbaf | |||
| 57fe8a4824 | |||
| 8739c3b9b6 | |||
| 81beb352e1 | |||
| b5f9bf05f3 | |||
| 6354a137c9 | |||
| e1d70f2e58 | |||
| 0a3aca69cc | |||
| 78cbc3ac19 | |||
| c140c1d3e1 | |||
| 915d83e69e | |||
| 09a7295f18 | |||
| f21faa3798 | |||
| ee732420d6 | |||
| da656d1adf | |||
| a53f465792 | |||
| 3663cc43f5 | |||
| e288ecb316 | |||
| 175bd97c24 | |||
| 255b35240e | |||
| 02d8863039 | |||
| 8f463097bc | |||
| 932c8cb6ca | |||
| 46d4562012 | |||
| 96959e7b8f | |||
| 427a6e470f | |||
| 17e30a506e | |||
| 6ccd4936f3 | |||
| cad9fdbb40 | |||
| 278ab026cd | |||
| 94c1574d96 | |||
| 69c5b00b1f | |||
| 969e3db499 | |||
| fb4d311ba8 | |||
| 32ae67b2c6 | |||
| 0883e26f21 | |||
| 69cd462c68 | |||
| eb103dbebd | |||
| eb5e1fb384 | |||
| 2e548b83f0 | |||
| c282cb0c47 | |||
| 0873340ec9 | |||
| e5cb18d8bf | |||
| ce1f005f07 | |||
| ca617d233b | |||
| 04dd6c079d | |||
| 65fc8fa4ab | |||
| 1ff7896ed2 | |||
| 10c28a7ecf | |||
| 73d514d6e3 | |||
| 4395b29482 | |||
| afa6457f88 | |||
| 9bc869c0d9 | |||
| cbb9b9d0f8 | |||
| 2cad89a29a | |||
| ee3d9a63e9 | |||
| d70f11441e | |||
| 818e1a62ef | |||
| 0e8ef2ad75 | |||
| 1950f5af97 | |||
| 7ae2c03ba1 | |||
| c93eac267a | |||
| ba973b028f | |||
| 46fed8faaf | |||
| 62c1ccd9c4 | |||
| 04e17bef80 | |||
| 3f450fc5c7 | |||
| 2d3c48c1c1 | |||
| 491461e354 | |||
| aef26b0811 | |||
| 9328a14de4 | |||
| c04de0d582 | |||
| e1a1a5e501 | |||
| 1043af0e74 | |||
| 096b45ff9d | |||
| 8d30c34ef7 | |||
| 625b1acfb2 | |||
| ff90e4dc18 | |||
| 819cca4385 | |||
| 204a5ae2ab | |||
| 35c8c1beaa | |||
| 657bde733d | |||
| faed3f8868 | |||
| 65fccac799 | |||
| 750516d2d2 | |||
| 98c71953a4 | |||
| 0aa6039f17 | |||
| 4cdde35835 | |||
| e68ac4fcac | |||
| cc963ac54f | |||
| 04a4f28f68 | |||
| 4c0eb8c043 | |||
| 583b3cb104 | |||
| d353d6cc1f | |||
| 2b609c4dcb | |||
| c68a090e02 | |||
| 9031f7b87d | |||
| 1bc7fcd3cb | |||
| 34c68c2f8a | |||
| 0e63f87271 | |||
| bcb0ddc1cb | |||
| e5a331c6cf | |||
| a34f1a33eb | |||
| 31adc17a36 | |||
| dada6c1614 | |||
| a905d9b2df | |||
| bb5dd6b348 | |||
| 5b9a59effe | |||
| 2309bcd95c | |||
| 53821983e9 | |||
| c192988e70 | |||
| 49f7998425 | |||
| 50ace56de8 | |||
| 077e7acb4a | |||
| ee6dbf9e29 | |||
| 278dce0196 | |||
| 22508acb9f | |||
| fc47d598ac | |||
| b935cda91a | |||
| 70c20b2d4a | |||
| c56d6b1688 | |||
| d717fc8410 | |||
| 626a8be77c | |||
| eb8ef879e7 | |||
| 22b52456fc | |||
| 7119080be2 | |||
| 6e05c3927a | |||
| bbba63365c | |||
| 060cddd82c | |||
| 80a7bc9dc8 | |||
| dbacc913e2 | |||
| 565fc60113 | |||
| ea0c7f3165 | |||
| 8195511332 | |||
| fa67fdc2e5 | |||
| b9c59ab948 | |||
| f6c86712aa |
17
.github/workflows/build_docker.yml
vendored
@ -3,28 +3,23 @@ name: Build and Push Docker Images
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
- "main"
|
||||||
jobs:
|
jobs:
|
||||||
topos:
|
topos:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
-
|
- name: Checkout
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
-
|
- name: Set up QEMU
|
||||||
name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
-
|
- name: Set up Docker Buildx
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
-
|
- name: Login to Docker Hub
|
||||||
name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
-
|
- name: Build and push
|
||||||
name: Build and push
|
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
|
|||||||
3
.github/workflows/deploy.yml
vendored
@ -47,9 +47,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: "main"
|
path: "main"
|
||||||
|
|
||||||
- name: Copy favicon folder
|
|
||||||
run: cp -r main/favicon ./dist/
|
|
||||||
|
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
uses: peaceiris/actions-gh-pages@v3
|
uses: peaceiris/actions-gh-pages@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
3
.vscode/settings.json
vendored
@ -1,2 +1 @@
|
|||||||
{
|
{}
|
||||||
}
|
|
||||||
|
|||||||
63
README.md
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
<p align="center"> |
|
<p align="center"> |
|
||||||
<a href="https://discord.gg/aPgV7mSFZh">Discord</a> |
|
<a href="https://discord.gg/aPgV7mSFZh">Discord</a> |
|
||||||
<a href="https://raphaelforment.fr/">BuboBubo</a> |
|
<a href="https://raphaelforment.fr/">BuboBubo</a> |
|
||||||
<a href="about:blank">Amiika</a> |
|
<a href="https://github.com/amiika">Amiika</a> |
|
||||||
<a href="https://toplap.org/">About Live Coding</a> |
|
<a href="https://toplap.org/">About Live Coding</a> |
|
||||||
<br><br>
|
<br><br>
|
||||||
<h2 align="center"><b>Contributors</b></h2>
|
<h2 align="center"><b>Contributors</b></h2>
|
||||||
@ -12,57 +12,90 @@
|
|||||||
<img src="https://contrib.rocks/image?repo=bubobubobubobubo/Topos" />
|
<img src="https://contrib.rocks/image?repo=bubobubobubobubo/Topos" />
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href='https://ko-fi.com/I2I2RSBHF' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi3.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
||||||
|
</p>
|
||||||
</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 designed to be installation-free, independant and fun. Topos is loosely based on the [Monome Teletype](https://monome.org/docs/teletype/). The application follows the same operating principle, but adapts it to the rich multimedia context offered by web browsers. Topos is capable of many things:
|
||||||
|
|
||||||
|
- it is a generative/algorithmic music sequencer made for **improvisation** and **composition** alike
|
||||||
|
- it is a synthesizer capable of _additive_, _substractive_, _FM_ and _wavetable
|
||||||
|
synthesis_, backed up by a [powerful web based audio engine](https://www.npmjs.com/package/superdough)
|
||||||
|
- it can also generate video thanks to [Hydra](https://hydra.ojack.xyz/),
|
||||||
|
oscilloscopes, frequency visualizers and image/canvas sequencing capabilities
|
||||||
|
- it can be used to sequence other MIDI and OSC devices (the latter using a **NodeJS** script)
|
||||||
|
- it is made to be used without the need of installing anything, always ready at
|
||||||
|
[https://topos.live](https://topos.live)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
**Topos** is a fairly young project developed by two part time hobbyists :) Do not expect stable features and/or user support in the initial development stage. Contributors and curious people are welcome! The software is working quite well and we are continuously striving to improve it.
|
**Topos** is still a young and experimental project developed by two hobbyists :) Contributions are welcome! We wish to be as inclusive and welcoming as possible to your ideas and suggestions! The software is working quite well and we are continuously striving to improve it. Note that most features are rather experimental and that we don't really have any classical training in web development.
|
||||||
|
|
||||||
## Installation (for devs and contributors)
|
## Local Installation (for devs and contributors)
|
||||||
|
|
||||||
To run the application, you will need to install [Node.js](https://nodejs.org/en/) and [Yarn](https://yarnpkg.com/en/). Then, clone the repository and run:
|
To run the application, you will need to install [Node.js](https://nodejs.org/en/) and [Yarn](https://yarnpkg.com/en/). Then, clone the repository and run:
|
||||||
|
|
||||||
- `yarn install`
|
- `yarn install`
|
||||||
- `yarn run dev`
|
- `yarn run dev`
|
||||||
|
|
||||||
To build the application for production, you will need to install [Node.js](https://nodejs.org/en/) and [Yarn](https://yarnpkg.com/en/). Then, clone the repository and run:
|
You are good to go. The application will update itself automatically with every change to the codebase. To test the production version of the applicationn, you will need to install [Node.js](https://nodejs.org/en/) and [Yarn](https://yarnpkg.com/en/). Then, clone the repository and run:
|
||||||
|
|
||||||
- `yarn run build`
|
- `yarn run build`
|
||||||
- `yarn run start`
|
- `yarn run start`
|
||||||
|
|
||||||
Always run a build before committing to check for compiler errors. The automatic deployment on the `main` branch will not accept compiler errors!
|
If the build passes, you can be sure that it will also pass our **CI** pipeline that deploys the application to [https://topos.live](https://topos.live). Always run a build before committing to check for compiler errors. The automatic deployment on the `main` branch will not accept compiler errors!
|
||||||
|
|
||||||
To build a standalone browser application using [Tauri](https://tauri.app/), you will need to have [Node.js](https://nodejs.org/en/), [Yarn](https://yarnpkg.com/en/) and [Rust](https://www.rust-lang.org/) installed. Then, clone the repository and run:
|
## Tauri version
|
||||||
|
|
||||||
|
Topos can also be compiled as a standalone application using [Tauri](https://tauri.app/). You will need [Node.js](https://nodejs.org/en/), [Yarn](https://yarnpkg.com/en/) and [Rust](https://www.rust-lang.org/) to be installed on your computer. Then, clone the repository and run:
|
||||||
|
|
||||||
- `yarn tauri build`
|
- `yarn tauri build`
|
||||||
- `yarn tauri dev`
|
- `yarn tauri dev`
|
||||||
|
|
||||||
The `tauri` version is only here to quickstart future developments but nothing has been done yet.
|
The `tauri` version has never been fleshed out. It's a template for later developments if Topos ever wants to escape from the web :)
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
### Run the application
|
To run the **Docker** version, run the following command:
|
||||||
`docker run -p 8001:80 yassinsiouda/topos:latest`
|
|
||||||
|
`docker run -p 8001:80 bubobubobubo/topos:latest`
|
||||||
|
|
||||||
### Build and run the prod image
|
### Build and run the prod image
|
||||||
|
|
||||||
`docker compose --profile prod up`
|
`docker compose --profile prod up`
|
||||||
|
|
||||||
### Build and run the dev image
|
### 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 :
|
||||||
First you need to map node_modules to your local machine for your ide intellisense to work properly
|
|
||||||
```bash
|
```bash
|
||||||
docker compose --profile dev up -d
|
docker compose --profile dev up -d
|
||||||
docker cp topos-dev:/app/node_modules .
|
docker cp topos-dev:/app/node_modules .
|
||||||
docker compose --profile dev down
|
docker compose --profile dev down
|
||||||
```
|
```
|
||||||
|
|
||||||
**Then**
|
then run the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose --profile dev up
|
docker compose --profile dev up
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that a Docker version of Topos is automatically generated everytime a commit is done on the `main` branch.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
- Felix Roos for the [SuperDough](https://www.npmjs.com/package/superdough) audio engine.
|
||||||
|
- Frank Force for the [ZzFX](https://github.com/KilledByAPixel/ZzFX) synthesizer.
|
||||||
|
- Kristoffer Ekstrand for the [AKWF](https://www.adventurekid.se/akrt/waveforms/adventure-kid-waveforms/) waveforms.
|
||||||
|
- Ryan Kirkbride for some of the audio samples in the [Dough-Fox](https://github.com/Bubobubobubobubo/Dough-Fox) sample pack, taken from [here](https://github.com/Qirky/FoxDot/tree/master/FoxDot/snd).
|
||||||
|
- Adel Faure for the [JGS](https://adelfaure.net/https://adelfaure.net/) font.
|
||||||
|
- Raphaël Bastide for the [Steps Mono](https://github.com/raphaelbastide/steps-mono/) font.
|
||||||
|
|
||||||
|
Many thanks to all the contributors and folks who tried the software already :)
|
||||||
36
ToposServer/OSCtoTopos.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
const WebSocket = require("ws");
|
||||||
|
const osc = require("osc");
|
||||||
|
|
||||||
|
const cleanIncomingOSC = (oscMsg) => {
|
||||||
|
let data = oscMsg.args;
|
||||||
|
// Remove information about type of data
|
||||||
|
data = data.map((item) => {
|
||||||
|
return item.value;
|
||||||
|
});
|
||||||
|
return { data: data, address: oscMsg.address };
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==============================================
|
||||||
|
// Receiving and forwarding OSC UDP messages
|
||||||
|
// Create an osc.js UDP Port listening on port 57121.
|
||||||
|
console.log("> OSC Input: 127.0.0.1:30000");
|
||||||
|
const wss = new WebSocket.Server({ port: 3001 });
|
||||||
|
var udpPort = new osc.UDPPort({
|
||||||
|
localAddress: "0.0.0.0",
|
||||||
|
localPort: 30000,
|
||||||
|
metadata: true,
|
||||||
|
});
|
||||||
|
udpPort.on("message", function (oscMsg, timeTag, info) {
|
||||||
|
console.log(
|
||||||
|
`> Incoming OSC to ${oscMsg.address}:`,
|
||||||
|
oscMsg.args.map((item) => {
|
||||||
|
return item.value;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
wss.clients.forEach((client) => {
|
||||||
|
if (client.readyState === WebSocket.OPEN) {
|
||||||
|
client.send(JSON.stringify(cleanIncomingOSC(oscMsg)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
udpPort.open();
|
||||||
83
ToposServer/ToposToOSC.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
const WebSocket = require("ws");
|
||||||
|
const osc = require("osc");
|
||||||
|
|
||||||
|
// Listening to WebSocket messages
|
||||||
|
const wss = new WebSocket.Server({ port: 3000 });
|
||||||
|
|
||||||
|
// Setting up for message broadcasting
|
||||||
|
wss.on("connection", function (ws) {
|
||||||
|
console.log("> Client connected");
|
||||||
|
ws.on("message", function (data) {
|
||||||
|
try {
|
||||||
|
const message = JSON.parse(data);
|
||||||
|
sendOscMessage(
|
||||||
|
formatAndTypeMessage(message),
|
||||||
|
message.address,
|
||||||
|
message.port,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`> Message sent to ${message.address}:${message.port}: ${JSON.stringify(
|
||||||
|
message.args,
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("> Error processing message:", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
wss.on("error", function (error) {
|
||||||
|
console.error("> Server error:", error);
|
||||||
|
});
|
||||||
|
|
||||||
|
wss.on("close", function () {
|
||||||
|
// Close the websocket server
|
||||||
|
wss.close();
|
||||||
|
console.log("> Closing websocket server");
|
||||||
|
});
|
||||||
|
|
||||||
|
let udpPort = new osc.UDPPort({
|
||||||
|
localAddress: "0.0.0.0",
|
||||||
|
localPort: 3000,
|
||||||
|
metadata: true,
|
||||||
|
remoteAddress: "0.0.0.0",
|
||||||
|
remotePort: 57120,
|
||||||
|
});
|
||||||
|
udpPort.on("error", function (error) {
|
||||||
|
console.error("> UDP Port error:", error);
|
||||||
|
});
|
||||||
|
udpPort.on("ready", function () {
|
||||||
|
//console.log(`> UDP Receive: ${udpPort.options.localPort}`);
|
||||||
|
console.log("> WebSocket server: 127.0.0.1:3000");
|
||||||
|
});
|
||||||
|
|
||||||
|
udpPort.open();
|
||||||
|
|
||||||
|
function sendOscMessage(message, address, port) {
|
||||||
|
try {
|
||||||
|
udpPort.options.remotePort = port;
|
||||||
|
message.address = address;
|
||||||
|
udpPort.send(message);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("> Error sending OSC message:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatAndTypeMessage = (message) => {
|
||||||
|
let newMessage = {};
|
||||||
|
delete message.args["address"];
|
||||||
|
delete message.args["port"];
|
||||||
|
newMessage.address = message.address;
|
||||||
|
newMessage.timestamp = osc.timeTag(message.timetag);
|
||||||
|
|
||||||
|
args = [...Object.entries(message.args)].flat().map((arg) => {
|
||||||
|
if (typeof arg === "string") return { type: "s", value: arg };
|
||||||
|
if (typeof arg === "number") return { type: "f", value: arg };
|
||||||
|
if (typeof arg === "boolean")
|
||||||
|
return value ? { type: "s", value: 1 } : { type: "s", value: 0 };
|
||||||
|
});
|
||||||
|
|
||||||
|
newMessage.args = args;
|
||||||
|
|
||||||
|
return newMessage;
|
||||||
|
};
|
||||||
14
ToposServer/banner.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
var pjson = require("./package.json");
|
||||||
|
let banner = `
|
||||||
|
┏┳┓ ┏┓┏┓┏┓
|
||||||
|
┃ ┏┓┏┓┏┓┏ ┃┃┗┓┃
|
||||||
|
┻ ┗┛┣┛┗┛┛ ┗┛┗┛┗┛
|
||||||
|
┛
|
||||||
|
${pjson.version}\n`;
|
||||||
|
function greet() {
|
||||||
|
console.log(banner);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
greet: greet,
|
||||||
|
};
|
||||||
332
ToposServer/package-lock.json
generated
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
{
|
||||||
|
"name": "topos-server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "topos-server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "GPL-3.0-or-later",
|
||||||
|
"dependencies": {
|
||||||
|
"osc": "^2.4.4",
|
||||||
|
"ws": "^8.14.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@serialport/binding-mock": {
|
||||||
|
"version": "10.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.2.2.tgz",
|
||||||
|
"integrity": "sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@serialport/bindings-interface": "^1.2.1",
|
||||||
|
"debug": "^4.3.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@serialport/bindings-cpp": {
|
||||||
|
"version": "10.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.8.0.tgz",
|
||||||
|
"integrity": "sha512-OMQNJz5kJblbmZN5UgJXLwi2XNtVLxSKmq5VyWuXQVsUIJD4l9UGHnLPqM5LD9u3HPZgDI5w7iYN7gxkQNZJUw==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@serialport/bindings-interface": "1.2.2",
|
||||||
|
"@serialport/parser-readline": "^10.2.1",
|
||||||
|
"debug": "^4.3.2",
|
||||||
|
"node-addon-api": "^5.0.0",
|
||||||
|
"node-gyp-build": "^4.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.17.0 <13.0 || >=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/serialport/donate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@serialport/bindings-interface": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-CJaUd5bLvtM9c5dmO9rPBHPXTa9R2UwpkJ0wdh9JCYcbrPWsKz+ErvR0hBLeo7NPeiFdjFO4sonRljiw4d2XiA==",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.22 || ^14.13 || >=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@serialport/parser-byte-length": {
|
||||||
|
"version": "10.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-10.5.0.tgz",
|
||||||
|
"integrity": "sha512-eHhr4lHKboq1OagyaXAqkemQ1XyoqbLQC8XJbvccm95o476TmEdW5d7AElwZV28kWprPW68ZXdGF2VXCkJgS2w==",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/serialport/donate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@serialport/parser-cctalk": {
|
||||||
|
"version": "10.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-10.5.0.tgz",
|
||||||
|
"integrity": "sha512-Iwsdr03xmCKAiibLSr7b3w6ZUTBNiS+PwbDQXdKU/clutXjuoex83XvsOtYVcNZmwJlVNhAUbkG+FJzWwIa4DA==",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/serialport/donate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@serialport/parser-delimiter": {
|
||||||
|
"version": "10.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.5.0.tgz",
|
||||||
|
"integrity": "sha512-/uR/yT3jmrcwnl2FJU/2ySvwgo5+XpksDUR4NF/nwTS5i3CcuKS+FKi/tLzy1k8F+rCx5JzpiK+koqPqOUWArA==",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/serialport/donate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@serialport/parser-inter-byte-timeout": {
|
||||||
|
"version": "10.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-10.5.0.tgz",
|
||||||
|
"integrity": "sha512-WPvVlSx98HmmUF9jjK6y9mMp3Wnv6JQA0cUxLeZBgS74TibOuYG3fuUxUWGJALgAXotOYMxfXSezJ/vSnQrkhQ==",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/serialport/donate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@serialport/parser-packet-length": {
|
||||||
|
"version": "10.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-10.5.0.tgz",
|
||||||
|
"integrity": "sha512-jkpC/8w4/gUBRa2Teyn7URv1D7T//0lGj27/4u9AojpDVXsR6dtdcTG7b7dNirXDlOrSLvvN7aS5/GNaRlEByw==",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@serialport/parser-readline": {
|
||||||
|
"version": "10.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-10.5.0.tgz",
|
||||||
|
"integrity": "sha512-0aXJknodcl94W9zSjvU+sLdXiyEG2rqjQmvBWZCr8wJZjWEtv3RgrnYiWq4i2OTOyC8C/oPK8ZjpBjQptRsoJQ==",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@serialport/parser-delimiter": "10.5.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/serialport/donate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@serialport/parser-ready": {
|
||||||
|
"version": "10.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-10.5.0.tgz",
|
||||||
|
"integrity": "sha512-QIf65LTvUoxqWWHBpgYOL+soldLIIyD1bwuWelukem2yDZVWwEjR288cLQ558BgYxH4U+jLAQahhqoyN1I7BaA==",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/serialport/donate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@serialport/parser-regex": {
|
||||||
|
"version": "10.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-10.5.0.tgz",
|
||||||
|
"integrity": "sha512-9jnr9+PCxRoLjtGs7uxwsFqvho+rxuJlW6ZWSB7oqfzshEZWXtTJgJRgac/RuLft4hRlrmRz5XU40i3uoL4HKw==",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/serialport/donate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@serialport/parser-slip-encoder": {
|
||||||
|
"version": "10.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-10.5.0.tgz",
|
||||||
|
"integrity": "sha512-wP8m+uXQdkWSa//3n+VvfjLthlabwd9NiG6kegf0fYweLWio8j4pJRL7t9eTh2Lbc7zdxuO0r8ducFzO0m8CQw==",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/serialport/donate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@serialport/parser-spacepacket": {
|
||||||
|
"version": "10.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-10.5.0.tgz",
|
||||||
|
"integrity": "sha512-BEZ/HAEMwOd8xfuJSeI/823IR/jtnThovh7ils90rXD4DPL1ZmrP4abAIEktwe42RobZjIPfA4PaVfyO0Fjfhg==",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/serialport/donate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@serialport/stream": {
|
||||||
|
"version": "10.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-10.5.0.tgz",
|
||||||
|
"integrity": "sha512-gbcUdvq9Kyv2HsnywS7QjnEB28g+6OGB5Z8TLP7X+UPpoMIWoUsoQIq5Kt0ZTgMoWn3JGM2lqwTsSHF+1qhniA==",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@serialport/bindings-interface": "1.2.2",
|
||||||
|
"debug": "^4.3.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/serialport/donate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/debug": {
|
||||||
|
"version": "4.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "2.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/long": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
|
||||||
|
},
|
||||||
|
"node_modules/ms": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/node-addon-api": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/node-gyp-build": {
|
||||||
|
"version": "4.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.7.0.tgz",
|
||||||
|
"integrity": "sha512-PbZERfeFdrHQOOXiAKOY0VPbykZy90ndPKk0d+CFDegTKmWp1VgOTz2xACVbr1BjCWxrQp68CXtvNsveFhqDJg==",
|
||||||
|
"optional": true,
|
||||||
|
"bin": {
|
||||||
|
"node-gyp-build": "bin.js",
|
||||||
|
"node-gyp-build-optional": "optional.js",
|
||||||
|
"node-gyp-build-test": "build-test.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/osc": {
|
||||||
|
"version": "2.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/osc/-/osc-2.4.4.tgz",
|
||||||
|
"integrity": "sha512-YJr2bUCQMc9BIaq1LXgqYpt5Ii7wNy2n0e0BkQiCSziMNrrsYHhH5OlExNBgCrQsum60EgXZ32lFsvR4aUf+ew==",
|
||||||
|
"dependencies": {
|
||||||
|
"long": "4.0.0",
|
||||||
|
"slip": "1.0.2",
|
||||||
|
"wolfy87-eventemitter": "5.2.9",
|
||||||
|
"ws": "8.13.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"serialport": "10.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/osc/node_modules/ws": {
|
||||||
|
"version": "8.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
|
||||||
|
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/serialport": {
|
||||||
|
"version": "10.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/serialport/-/serialport-10.5.0.tgz",
|
||||||
|
"integrity": "sha512-7OYLDsu5i6bbv3lU81pGy076xe0JwpK6b49G6RjNvGibstUqQkI+I3/X491yBGtf4gaqUdOgoU1/5KZ/XxL4dw==",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@serialport/binding-mock": "10.2.2",
|
||||||
|
"@serialport/bindings-cpp": "10.8.0",
|
||||||
|
"@serialport/parser-byte-length": "10.5.0",
|
||||||
|
"@serialport/parser-cctalk": "10.5.0",
|
||||||
|
"@serialport/parser-delimiter": "10.5.0",
|
||||||
|
"@serialport/parser-inter-byte-timeout": "10.5.0",
|
||||||
|
"@serialport/parser-packet-length": "10.5.0",
|
||||||
|
"@serialport/parser-readline": "10.5.0",
|
||||||
|
"@serialport/parser-ready": "10.5.0",
|
||||||
|
"@serialport/parser-regex": "10.5.0",
|
||||||
|
"@serialport/parser-slip-encoder": "10.5.0",
|
||||||
|
"@serialport/parser-spacepacket": "10.5.0",
|
||||||
|
"@serialport/stream": "10.5.0",
|
||||||
|
"debug": "^4.3.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/serialport/donate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/slip": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/slip/-/slip-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-XrcHe3NAcyD3wO+O4I13RcS4/3AF+S9RvGNj9JhJeS02HyImwD2E3QWLrmn9hBfL+fB6yapagwxRkeyYzhk98g=="
|
||||||
|
},
|
||||||
|
"node_modules/wolfy87-eventemitter": {
|
||||||
|
"version": "5.2.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/wolfy87-eventemitter/-/wolfy87-eventemitter-5.2.9.tgz",
|
||||||
|
"integrity": "sha512-P+6vtWyuDw+MB01X7UeF8TaHBvbCovf4HPEMF/SV7BdDc1SMTiBy13SRD71lQh4ExFTG1d/WNzDGDCyOKSMblw=="
|
||||||
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.14.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz",
|
||||||
|
"integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
ToposServer/package.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "topos-server",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Topos OSC Server",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "Raphaël Forment",
|
||||||
|
"license": "GPL-3.0-or-later",
|
||||||
|
"dependencies": {
|
||||||
|
"osc": "^2.4.4",
|
||||||
|
"ws": "^8.14.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
8
ToposServer/server.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
const WebSocket = require("ws");
|
||||||
|
const osc = require("osc");
|
||||||
|
|
||||||
|
require("./banner").greet();
|
||||||
|
// Topos to OSC
|
||||||
|
require("./ToposToOSC");
|
||||||
|
// OSC to Topos
|
||||||
|
require("./OSCtoTopos");
|
||||||
@ -1,4 +1,4 @@
|
|||||||
version: '3.7'
|
version: "3.7"
|
||||||
services:
|
services:
|
||||||
topos-dev:
|
topos-dev:
|
||||||
container_name: topos-dev
|
container_name: topos-dev
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
@ -1,6 +1,7 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "IBM Plex Mono";
|
font-family: "IBM Plex Mono";
|
||||||
src: url("woff2/IBMPlexMono-Regular.woff2") format("woff2"),
|
src:
|
||||||
|
url("woff2/IBMPlexMono-Regular.woff2") format("woff2"),
|
||||||
url("woff/IBMPlexMono-Regular.woff") format("woff");
|
url("woff/IBMPlexMono-Regular.woff") format("woff");
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@ -9,7 +10,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "IBM PLex Mono";
|
font-family: "IBM PLex Mono";
|
||||||
src: url("woff2/IBMPlexMono-Italic.woff2") format("woff2"),
|
src:
|
||||||
|
url("woff2/IBMPlexMono-Italic.woff2") format("woff2"),
|
||||||
url("woff/IBMPlexMono-Italic.woff") format("woff");
|
url("woff/IBMPlexMono-Italic.woff") format("woff");
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
@ -18,7 +20,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "IBM PLex Mono";
|
font-family: "IBM PLex Mono";
|
||||||
src: url("woff2/IBMPlexMono-Bold.woff2") format("woff2"),
|
src:
|
||||||
|
url("woff2/IBMPlexMono-Bold.woff2") format("woff2"),
|
||||||
url("woff/IBMPlexMono-Bold.woff") format("woff");
|
url("woff/IBMPlexMono-Bold.woff") format("woff");
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@ -27,7 +30,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "IBM Plex Mono";
|
font-family: "IBM Plex Mono";
|
||||||
src: url("woff2/IBMPlexMono-BoldItalic.woff2") format("woff2"),
|
src:
|
||||||
|
url("woff2/IBMPlexMono-BoldItalic.woff2") format("woff2"),
|
||||||
url("woff/IBMPlexMono-BoldItalic.woff") format("woff");
|
url("woff/IBMPlexMono-BoldItalic.woff") format("woff");
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
@ -37,83 +41,84 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Comic Mono";
|
font-family: "Comic Mono";
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
src: url(./woff/ComicMono.woff) format("woff"),
|
src:
|
||||||
|
url(./woff/ComicMono.woff) format("woff"),
|
||||||
url(./woff2/ComicMono.woff2) format("wooff2");
|
url(./woff2/ComicMono.woff2) format("wooff2");
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Comic Mono";
|
font-family: "Comic Mono";
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
src: url(./woff/ComicMono-Bold.woff) format("woff"),
|
src:
|
||||||
url(./woff/ComicMono-Bold.woff2) format("woff2"),
|
url(./woff/ComicMono-Bold.woff) format("woff"),
|
||||||
|
url(./woff/ComicMono-Bold.woff2) format("woff2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'jgs7';
|
font-family: "jgs7";
|
||||||
src: url('./woff2/jgs7.woff2') format('woff2'),
|
src:
|
||||||
url('./woff/jgs7.woff') format('woff');
|
url("./woff2/jgs7.woff2") format("woff2"),
|
||||||
|
url("./woff/jgs7.woff") format("woff");
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'jgs5';
|
font-family: "jgs5";
|
||||||
src: url('./woff2/jgs5.woff2') format('woff2'),
|
src:
|
||||||
url('./woff/jgs5.woff') format('woff');
|
url("./woff2/jgs5.woff2") format("woff2"),
|
||||||
|
url("./woff/jgs5.woff") format("woff");
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'jgs9';
|
font-family: "jgs9";
|
||||||
src: url('./woff2/jgs9.woff2') format('woff2'),
|
src:
|
||||||
url('./woff/jgs9.woff') format('woff');
|
url("./woff2/jgs9.woff2") format("woff2"),
|
||||||
font-weight: normal;
|
url("./woff/jgs9.woff") format("woff");
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'jgs_vecto';
|
|
||||||
src: url('./woff2/jgs_vecto.woff2') format('woff2');
|
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Steps Mono';
|
font-family: "jgs_vecto";
|
||||||
src: url('./woff2/Steps-Mono.woff2') format('woff2');
|
src: url("./woff2/jgs_vecto.woff2") format("woff2");
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Steps Mono Thin';
|
font-family: "Steps Mono";
|
||||||
src: url('./woff2/Steps-Mono-Thin.woff2') format('woff2');
|
src: url("./woff2/Steps-Mono.woff2") format("woff2");
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Jet Brains';
|
font-family: "Steps Mono Thin";
|
||||||
src: url('./woff2/JetBrainsMono-Regular.woff2') format('woff2');
|
src: url("./woff2/Steps-Mono-Thin.woff2") format("woff2");
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
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-face {
|
||||||
font-family: 'Jet Brains';
|
font-family: "Jet Brains";
|
||||||
src: url('./woff2/JetBrainsMono-Bold.woff2') format('woff2');
|
src: url("./woff2/JetBrainsMono-Bold.woff2") format("woff2");
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
|
|||||||
2
global.d.ts
vendored
@ -1 +1,3 @@
|
|||||||
/// <reference types="vite-plugin-pwa/client" />
|
/// <reference types="vite-plugin-pwa/client" />
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 590 KiB |
460
index.html
@ -1,25 +1,24 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="description" content="Topos is a live coding environment inspired by the Monome Teletype.">
|
|
||||||
<title>Topos</title>
|
<title>Topos</title>
|
||||||
|
<meta name="description" content="Topos is a live coding environment inspired by the Monome Teletype.">
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="favicon/apple-touch-icon.png">
|
<link rel="icon" href="/favicon/favicon.ico" sizes="48x48" ><!-- REVISED (Aug 11, 2023)! -->
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="favicon/favicon-32x32.png">
|
<link rel="icon" href="/favicon/favicon.svg" sizes="any" type="image/svg+xml"><!-- REVISED (Aug 11, 2023)! -->
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="favicon/favicon-16x16.png">
|
<link rel="apple-touch-icon" href="/favicon/apple-touch-icon.png"/>
|
||||||
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
<link rel="mask-icon" href="favicon/safari-pinned-tab.svg" color="#5bbad5">
|
<link rel="mask-icon" href="favicon/safari-pinned-tab.svg" color="#5bbad5">
|
||||||
<meta name="msapplication-TileColor" content="#da532c">
|
<meta name="msapplication-TileColor" content="#da532c">
|
||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff">
|
||||||
<link rel="stylesheet" href="/src/output.css" />
|
|
||||||
<link rel="stylesheet" href='/fonts/index.css' >
|
<link rel="stylesheet" href='/fonts/index.css' >
|
||||||
|
<link rel="stylesheet" href="/src/output.css" />
|
||||||
<script src="https://unpkg.com/hydra-synth"></script>
|
<script src="https://unpkg.com/hydra-synth"></script>
|
||||||
</head>
|
</head>
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: "Arial";
|
font-family: "Arial";
|
||||||
background-color: #111827;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
@ -28,20 +27,27 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fluid-bg-transition {
|
.fluid-transition {
|
||||||
transition: background-color 0.05s ease-in-out;
|
transition: background-color 0.05s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hydracanvas {
|
||||||
.fullscreencanvas {
|
position: fixed;
|
||||||
position: fixed; /* ignore margins */
|
top: 0px; left: 0px;
|
||||||
top: 0px;
|
width: 100%; height: 100%;
|
||||||
left: 0px;
|
|
||||||
width: 100%; /* fill screen */
|
|
||||||
height: 100%;
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
z-index: -1; /* place behind everything else */
|
z-index: -5;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreencanvas {
|
||||||
|
position: fixed;
|
||||||
|
top: 0px; left: 0px;
|
||||||
|
width: 100%; height: 100%;
|
||||||
|
background-size: cover;
|
||||||
|
overflow-y: hidden;
|
||||||
|
z-index: -1;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,183 +72,220 @@
|
|||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bar_button {
|
||||||
|
@apply mx-2 px-2 py-2 flex inline rounded-lg bg-background text-foreground hover:bg-foreground hover:text-background
|
||||||
|
}
|
||||||
|
|
||||||
|
.side_button {
|
||||||
|
@apply px-2 py-2 bg-background text-foreground rounded-lg hover:bg-foreground hover:text-background
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
@apply bg-selection_foreground text-sm lg:text-xl border-b py-4 text-foreground
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab_panel {
|
||||||
|
@apply inline-block lg:px-4 px-8 py-1 text-brightwhite
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc_header {
|
||||||
|
@apply pl-2 pr-2 lg:text-xl text-sm py-1 my-1 rounded-lg text-white hover:text-brightwhite hover:bg-brightblack
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc_subheader {
|
||||||
|
@apply pl-2 pr-2 lg:text-xl text-sm ml-6 py-1 my-1 rounded-lg text-white hover:text-brightwhite hover:bg-brightblack
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<body id="all" class="z-0 bg-neutral-800 overflow-y-hidden">
|
|
||||||
|
|
||||||
|
<body id="all" class="z-0 overflow-y-hidden bg-black">
|
||||||
<!-- The header is hidden on smaller devices -->
|
<!-- The header is hidden on smaller devices -->
|
||||||
<header class="py-0 block text-white bg-neutral-900">
|
<header class="py-0 block">
|
||||||
<div class="mx-auto flex flex-wrap pl-2 py-1 flex-row items-center">
|
<div id="topbar" class="mx-auto flex flex-wrap pl-2 py-1 flex-row items-center bg-background">
|
||||||
<a class="flex title-font font-medium items-center text-black mb-0">
|
<a class="flex title-font font-medium items-center mb-0">
|
||||||
<img id="topos-logo" src="topos_frog.svg" class="w-12 h-12 text-black p-2 bg-white rounded-full" alt="Topos Frog Logo" />
|
<img id="topos_logo" src="topos_frog.svg" class="w-12 h-12 text-selection_foreground p-2 rounded-full bg-foreground" alt="Topos Frog Logo"/>
|
||||||
<input id="universe-viewer" class="hidden bg-transparent xl:block ml-4 text-2xl text-white placeholder-white" id="renamer" type="text" placeholder="Topos">
|
<input id="universe-viewer" class="hidden transparent xl:block ml-4 text-2xl bg-background text-brightwhite placeholder-brightwhite" id="renamer" type="text" placeholder="Topos">
|
||||||
|
|
||||||
</a>
|
</a>
|
||||||
<nav class="py-2 flex flex-wrap items-center text-base absolute right-0">
|
<nav class="py-2 flex flex-wrap items-center text-base absolute right-0">
|
||||||
<a title="Play button (Ctrl+P)" id="play-button-1" class="flex flex-row mr-2 hover:bg-gray-800 px-2 py-2 rounded-lg">
|
<!-- Play Button -->
|
||||||
|
<a title="Play button (Ctrl+P)" id="play-button" class="bar_button">
|
||||||
<svg id="play-icon" class="w-7 h-7" fill="currentColor" viewBox="0 0 14 16">
|
<svg id="play-icon" class="w-7 h-7" 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"/>
|
||||||
</svg>
|
</svg>
|
||||||
<svg id="pause-icon" class="hidden w-7 h-7 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
<svg id="pause-icon" class="hidden w-7 h-7" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9 13a1 1 0 0 1-2 0V7a1 1 0 0 1 2 0v6Zm4 0a1 1 0 0 1-2 0V7a1 1 0 0 1 2 0v6Z"/>
|
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9 13a1 1 0 0 1-2 0V7a1 1 0 0 1 2 0v6Zm4 0a1 1 0 0 1-2 0V7a1 1 0 0 1 2 0v6Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<p id="play-label" class="hidden lg:block text-xl pl-2 text-white inline-block">Play</p>
|
<p id="play-label" class="hidden lg:block text-xl pl-2 inline-block">Play</p>
|
||||||
</a>
|
</a>
|
||||||
<a title="Stop button (Ctrl+R)" id="stop-button-1" class="flex flex-row mr-2 hover:bg-gray-800 px-2 py-2 rounded-lg">
|
|
||||||
<svg class="w-7 h-7 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
<!-- Stop button -->
|
||||||
|
<a title="Stop button (Ctrl+R)" id="stop-button" class="bar_button">
|
||||||
|
<svg class="w-7 h-7 " aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Z"/>
|
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Z"/>
|
||||||
<rect x="6.5" y="6.5" width="7" height="7" fill="black" rx="1" ry="1"/>
|
<rect x="6.5" y="6.5" width="7" height="7" fill="selection_background" rx="1" ry="1"/>
|
||||||
</svg>
|
</svg>
|
||||||
<p class="hidden lg:block text-xl pl-2 text-white inline-block">Stop</p>
|
<p class="hidden lg:block text-xl pl-2 inline-block">Stop</p>
|
||||||
</a>
|
</a>
|
||||||
<a title="Eval button (Ctrl+Enter)" id="eval-button-1" class="flex flex-row mr-2 hover:text-gray-900 hover:bg-gray-800 px-2 py-2 rounded-lg">
|
|
||||||
<svg class="w-7 h-7 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 20">
|
<!-- Eval button -->
|
||||||
|
<a title="Eval button (Ctrl+Enter)" id="eval-button-1" class="bar_button">
|
||||||
|
<svg class="w-7 h-7 " 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>
|
||||||
<p class="hidden lg:block text-xl pl-2 text-white inline-block">Eval</p>
|
<p class="hidden lg:block text-xl pl-2 inline-block">Eval</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a title="Clear button" id="clear-button-1" class="flex flex-row mr-2 hover:text-gray-900 hover:bg-gray-800 px-2 py-2 rounded-lg">
|
<a title="Share button" id="share-button" class="bar_button">
|
||||||
<svg class="w-7 h-7 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 20">
|
<svg class="w-7 h-7 " aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 19 19">
|
||||||
<path d="M17 4h-4V2a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v2H1a1 1 0 0 0 0 2h1v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V6h1a1 1 0 1 0 0-2ZM7 2h4v2H7V2Zm1 14a1 1 0 1 1-2 0V8a1 1 0 0 1 2 0v8Zm4 0a1 1 0 0 1-2 0V8a1 1 0 0 1 2 0v8Z"/>
|
|
||||||
</svg>
|
|
||||||
<p class="hidden lg:block text-xl pl-2 text-white inline-block">Clear</p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a title="Share button" id="share-button" class="flex flex-row mr-2 hover:text-gray-900 hover:bg-gray-800 px-2 py-2 rounded-lg">
|
|
||||||
<svg class="w-7 h-7 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 19 19">
|
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.013 7.962a3.519 3.519 0 0 0-4.975 0l-3.554 3.554a3.518 3.518 0 0 0 4.975 4.975l.461-.46m-.461-4.515a3.518 3.518 0 0 0 4.975 0l3.553-3.554a3.518 3.518 0 0 0-4.974-4.975L10.3 3.7"/>
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.013 7.962a3.519 3.519 0 0 0-4.975 0l-3.554 3.554a3.518 3.518 0 0 0 4.975 4.975l.461-.46m-.461-4.515a3.518 3.518 0 0 0 4.975 0l3.553-3.554a3.518 3.518 0 0 0-4.974-4.975L10.3 3.7"/>
|
||||||
</svg>
|
</svg>
|
||||||
<p class="hidden lg:block text-xl pl-2 text-white inline-block">Share</p>
|
<p class="hidden lg:block text-xl pl-2 inline-block">Share</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a title="Open Documentation (Ctrl+D)" id="doc-button-1" class="flex flex-row hover:text-gray-900 hover:bg-gray-800 px-2 py-2 rounded-lg">
|
<a title="Open Documentation (Ctrl+D)" id="doc-button-1" class="bar_button">
|
||||||
<svg class="w-7 h-7 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
<svg class="w-7 h-7 " aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
|
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<p class="hidden lg:block text-xl pl-2 text-white inline-block">Docs</p>
|
<p class="hidden lg:block text-xl pl-2 inline-block">Docs</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<div id="transport_viewer" class="pr-2 text-selection_background"></div>
|
||||||
</nav>
|
</nav>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div id="documentation" class="hidden">
|
<div id="documentation" class="hidden">
|
||||||
<div id="documentation-page" class="flex flex-row">
|
<div id="documentation-page" class="flex flex-row transparent">
|
||||||
<aside class="w-1/8 flex-shrink-0 h-screen overflow-y-auto p-1 lg:p-6 bg-neutral-900 text-white">
|
<aside class="w-1/8 flex-shrink-0 h-screen overflow-y-auto p-1 lg:p-6 bg-background">
|
||||||
<nav class="text-xl sm:text-sm overflow-y-scroll mb-24">
|
<nav class="text-xl sm:text-sm overflow-y-scroll mb-24 bg-background">
|
||||||
<details class="" open=true>
|
<details class="" open>
|
||||||
<summary class="font-semibold lg:text-xl text-orange-300">Basics</summary>
|
<summary class="font-semibold lg:text-xl text-orange-300">Basics</summary>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<p rel="noopener noreferrer" id="docs_introduction" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Welcome </p>
|
<p rel="noopener noreferrer" id="docs_introduction" class="doc_header">Welcome </p>
|
||||||
<p rel="noopener noreferrer" id="docs_interface" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Interface</p>
|
<p rel="noopener noreferrer" id="docs_atelier" class="doc_header">Atelier (FR)</p>
|
||||||
<p rel="noopener noreferrer" id="docs_interaction" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Interaction</p>
|
<p rel="noopener noreferrer" id="docs_interface" class="doc_header">Interface</p>
|
||||||
<p rel="noopener noreferrer" id="docs_shortcuts" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Keyboard</p>
|
<p rel="noopener noreferrer" id="docs_interaction" class="doc_header">Interaction</p>
|
||||||
<p rel="noopener noreferrer" id="docs_mouse" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Mouse</p>
|
<p rel="noopener noreferrer" id="docs_shortcuts" class="doc_header">Keyboard</p>
|
||||||
<p rel="noopener noreferrer" id="docs_code" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Coding</p>
|
<p rel="noopener noreferrer" id="docs_mouse" class="doc_header">Mouse</p>
|
||||||
|
<p rel="noopener noreferrer" id="docs_code" class="doc_header">Coding</p>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<details class="space-y-2" open=true>
|
<details class="space-y-2" open>
|
||||||
<summary class="font-semibold lg:text-xl pb-1 pt-1 text-orange-300">Learning</summary>
|
<summary class="font-semibold lg:text-xl pb-1 pt-1 text-orange-300">Learning</summary>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
|
|
||||||
<!-- Time -->
|
<!-- Time -->
|
||||||
<details class="space-y-2" open=false>
|
<details class="space-y-2">
|
||||||
<summary class="ml-2 lg:text-xl pb-1 pt-1 text-white">Time</summary>
|
<summary class="ml-2 lg:text-xl pb-1 pt-1 doc_header">Time</summary>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<p rel="noopener noreferrer" id="docs_time" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Dealing with time</p>
|
<p rel="noopener noreferrer" id="docs_time" class="doc_subheader">Dealing with time</p>
|
||||||
<p rel="noopener noreferrer" id="docs_linear" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Time & Transport</p>
|
<p rel="noopener noreferrer" id="docs_linear" class="doc_subheader">Time & Transport</p>
|
||||||
<p rel="noopener noreferrer" id="docs_cyclic" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Time & Cycles</p>
|
<p rel="noopener noreferrer" id="docs_cyclic" class="doc_subheader">Time & Cycles</p>
|
||||||
<p rel="noopener noreferrer" id="docs_longform" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Time & Structure</p>
|
<p rel="noopener noreferrer" id="docs_longform" class="doc_subheader">Time & Structure</p>
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<!-- Samples -->
|
|
||||||
<details class="space-y-2" open=false>
|
|
||||||
<summary class="ml-2 lg:text-xl pb-1 pt-1 text-white">Audio Engine</summary>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<p rel="noopener noreferrer" id="docs_audio_basics" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Playing a sound</p>
|
|
||||||
<p rel="noopener noreferrer" id="docs_amplitude" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Amplitude</p>
|
|
||||||
<p rel="noopener noreferrer" id="docs_sampler" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Sampler</p>
|
|
||||||
<p rel="noopener noreferrer" id="docs_synths" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Synths</p>
|
|
||||||
<p rel="noopener noreferrer" id="docs_reverb_delay" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Effects</p>
|
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<!-- Audio Engine -->
|
<!-- Audio Engine -->
|
||||||
<details class="space-y-2" open=false>
|
<details class="space-y-2">
|
||||||
<summary class="ml-2 lg:text-xl pb-1 pt-1 text-white">Samples</summary>
|
<summary class="ml-2 lg:text-xl pb-1 pt-1 doc_header">Audio Engine</summary>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<p rel="noopener noreferrer" id="docs_sample_list" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">List of samples</p>
|
<p rel="noopener noreferrer" id="docs_audio_basics" class="doc_subheader">Playing a sound</p>
|
||||||
<p rel="noopener noreferrer" id="docs_loading_samples" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Loading Samples</p>
|
<p rel="noopener noreferrer" id="docs_amplitude" class="doc_subheader">Amplitude</p>
|
||||||
|
<p rel="noopener noreferrer" id="docs_sampler" class="doc_subheader">Sampler</p>
|
||||||
|
<p rel="noopener noreferrer" id="docs_synths" class="doc_subheader">Synths</p>
|
||||||
|
<p rel="noopener noreferrer" id="docs_filters" class="doc_subheader">Filters</p>
|
||||||
|
<p rel="noopener noreferrer" id="docs_effects" class="doc_subheader">Effects</p>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<p rel="noopener noreferrer" id="docs_patterns" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Patterns</p>
|
<!-- Samples -->
|
||||||
<p rel="noopener noreferrer" id="docs_midi" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">MIDI</p>
|
<details class="space-y-2">
|
||||||
|
<summary class="ml-2 lg:text-xl pb-1 pt-1 doc_header ">Samples</summary>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<p rel="noopener noreferrer" id="docs_sample_list" class="doc_subheader">List of samples</p>
|
||||||
|
<p rel="noopener noreferrer" id="docs_loading_samples" class="doc_subheader">External samples</p>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<details class="space-y-2" open=true>
|
<p rel="noopener noreferrer" id="docs_midi" class="doc_header">MIDI</p>
|
||||||
|
<p rel="noopener noreferrer" id="docs_osc" class="doc_header">OSC</p>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
<details class="space-y-2" open>
|
||||||
<summary class="font-semibold lg:text-xl pb-1 pt-1 text-orange-300">Patterns</summary>
|
<summary class="font-semibold lg:text-xl pb-1 pt-1 text-orange-300">Patterns</summary>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
|
|
||||||
<p rel="noopener noreferrer" id="docs_variables" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Global Variables</p>
|
<p rel="noopener noreferrer" id="docs_patterns" class="pl-2 pr-2 lg:text-xl text-sm hover:neutral-800 py-1 my-1 rounded-lg doc_header">Array patterns</p>
|
||||||
<p rel="noopener noreferrer" id="docs_lfos" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Low Freq Oscs.</p>
|
<!-- Ziffers -->
|
||||||
<p rel="noopener noreferrer" id="docs_probabilities" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Probabilities</p>
|
<details class="space-y-2">
|
||||||
<p rel="noopener noreferrer" id="docs_chaining" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Chaining</p>
|
<summary class="doc_header">Ziffers</summary>
|
||||||
<p rel="noopener noreferrer" id="docs_functions" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Functions</p>
|
|
||||||
<p rel="noopener noreferrer" id="docs_ziffers" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Ziffers</p>
|
|
||||||
<!--
|
|
||||||
<p rel="noopener noreferrer" id="docs_reference" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Reference</p>
|
|
||||||
-->
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
<details class="space-y-2" open=true>
|
|
||||||
<summary class="font-semibold lg:text-xl text-orange-300">More</summary>
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<a rel="noopener noreferrer" id="docs_synchronisation" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Synchronisation</a>
|
<p rel="noopener noreferrer" id="docs_ziffers_basics" class="doc_subheader">Basics</p>
|
||||||
<a rel="noopener noreferrer" id="docs_oscilloscope" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Oscilloscope</a>
|
<p rel="noopener noreferrer" id="docs_ziffers_scales" class="doc_subheader">Scales</p>
|
||||||
<a rel="noopener noreferrer" id="docs_bonus" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Bonus/Trivia</a>
|
<p rel="noopener noreferrer" id="docs_ziffers_rhythm" class="doc_subheader">Rhythm</p>
|
||||||
<a rel="noopener noreferrer" id="docs_about" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">About Topos</a>
|
<p rel="noopener noreferrer" id="docs_ziffers_algorithmic" class="doc_subheader">Algorithmic</p>
|
||||||
|
<p rel="noopener noreferrer" id="docs_ziffers_tonnetz" class="doc_subheader">Tonnetz</p>
|
||||||
|
<p rel="noopener noreferrer" id="docs_ziffers_syncing" class="doc_subheader">Syncing</p>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<details class="" open=true>
|
<p rel="noopener noreferrer" id="docs_variables" class="doc_header">Global Variables</p>
|
||||||
|
<p rel="noopener noreferrer" id="docs_lfos" class="doc_header">Low Freq Oscs.</p>
|
||||||
|
<p rel="noopener noreferrer" id="docs_probabilities" class="doc_header">Probabilities</p>
|
||||||
|
<p rel="noopener noreferrer" id="docs_chaining" class="doc_header">Chaining</p>
|
||||||
|
<p rel="noopener noreferrer" id="docs_functions" class="doc_header">Functions</p>
|
||||||
|
<p rel="noopener noreferrer" id="docs_generators" class="doc_header">Generators</p>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
<details class="space-y-2" open>
|
||||||
|
<summary class="font-semibold lg:text-xl doc_header">More</summary>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<a rel="noopener noreferrer" id="docs_synchronisation" class="doc_subheader">Synchronisation</a>
|
||||||
|
<a rel="noopener noreferrer" id="docs_oscilloscope" class="doc_subheader">Oscilloscope</a>
|
||||||
|
<a rel="noopener noreferrer" id="docs_visualization" class="doc_header">Visualization</a>
|
||||||
|
<a rel="noopener noreferrer" id="docs_bonus" class="doc_header">Bonus/Trivia</a>
|
||||||
|
<a rel="noopener noreferrer" id="docs_about" class="doc_header">About Topos</a>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
<details class="" open>
|
||||||
<summary class="font-semibold lg:text-xl text-orange-300">Community</summary>
|
<summary class="font-semibold lg:text-xl text-orange-300">Community</summary>
|
||||||
<form action="https://github.com/Bubobubobubobubo/topos">
|
<form action="https://github.com/Bubobubobubobubo/topos">
|
||||||
<input rel="noopener noreferrer" id="docs_introduction" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg" type="submit" value="GitHub" />
|
<input rel="noopener noreferrer" id="github_link" class="doc_header" type="submit" value="GitHub" />
|
||||||
</form>
|
</form>
|
||||||
<form action="https://discord.gg/6T67DqBNNT">
|
<form action="https://discord.gg/6T67DqBNNT">
|
||||||
<input rel="noopener noreferrer" id="docs_introduction" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg" type="submit" value="Discord" />
|
<input rel="noopener noreferrer" id="discord_link" class="doc_header" type="submit" value="Discord" />
|
||||||
|
</form>
|
||||||
|
<form action="https://ko-fi.com/raphaelbubo">
|
||||||
|
<input rel="noopener noreferrer" id="support_link" class="doc_header" type="submit" value="Support" />
|
||||||
</form>
|
</form>
|
||||||
</details>
|
</details>
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
<div id="documentation-content" class="w-full flex-grow-1 h-screen overflow-y-scroll lg:px-12 mx-2 my-2 break-words pb-32">
|
<div id="documentation-content" class="w-full flex-grow-1 h-screen overflow-y-scroll lg:px-12 mx-2 my-2 break-words pb-32 transparent"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<!-- This modal is used for switching between buffers -->
|
<!-- This modal is used for switching between buffers -->
|
||||||
<div id="modal-buffers" class="invisible bg-gray-900 bg-opacity-50 flex justify-center items-center absolute top-0 right-0 bottom-0 left-0">
|
<div id="modal-buffers" class="invisible flex justify-center items-center absolute top-0 right-0 bottom-0 left-0">
|
||||||
<div id="start-button" class="lg:px-16 px-4 lg:pt-4 lg:pb-4 pt-2 pb-2 rounded-md text-center bg-white">
|
<div id="start-button" class="lg:px-16 px-4 lg:pt-4 lg:pb-4 pt-2 pb-2 rounded-md text-center bg-foreground">
|
||||||
<p class="text-semibold lg:text-2xl text-sm pb-4">Known universes</p>
|
<p class="text-semibold lg:text-2xl text-sm pb-4 text-selection_foreground">Known universes</p>
|
||||||
<p id="existing-universes" class="text-normal lg:h-auto h-48 overflow-y-auto mb-2"></p>
|
<p id="existing-universes" class="text-normal lg:h-auto h-48 overflow-y-auto mb-2"></p>
|
||||||
<div id="disclaimer" class="pb-4">
|
<div id="disclaimer" class="pb-4">
|
||||||
<form id="universe-creator">
|
<form id="universe-creator">
|
||||||
<label for="search" class="mb-2 text-sm font-medium text-gray-900 sr-only text-white">Search</label>
|
<label for="search" class="mb-2 text-sm font-medium sr-only ">Search</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||||
<svg class="w-4 h-4 text-gray-500 text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
|
<svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"/>
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<input name="universe" minlength="2" autocomplete="off" type="text" id="buffer-search" class="block w-full p-4 pl-10 text-sm border border-neutral-800 outline-0 rounded-lg bg-neutral-800 text-white" placeholder="Buffer..." required>
|
<input name="universe" minlength="2" autocomplete="off" type="text" id="buffer-search" class="block w-full p-4 pl-10 text-sm border border-neutral-800 outline-0 rounded-lg neutral-800 " placeholder="Buffer..." required>
|
||||||
<button id="load-universe-button" class="text-black absolute right-2.5 bottom-2.5 bg-white hover:bg-white focus:outline-none font-medium rounded-lg text-sm px-4 py-2">Go</button>
|
<button id="load-universe-button" class="bg-background text-selection_background absolute right-2.5 bottom-2.5 focus:outline-none font-medium rounded-lg text-sm px-4 py-2">Go</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div class="mt-2 flex space-x-6 border-t border-gray-200 rounded-b dark:border-gray-600 border-spacing-y-4">
|
<div class="mt-2 flex space-x-6 border-t rounded-b border-spacing-y-4">
|
||||||
<button id="close-universes-button" data-modal-hide="defaultModal" type="button" class="mt-2 hover:bg-neutral-700 bg-neutral-800 text-white focus:ring-4 font-medium rounded-lg text-sm px-5 py-2.5 text-center">Close</button>
|
<button id="close-universes-button" data-modal-hide="defaultModal" type="button" class="mt-2 focus:ring-4 font-medium rounded-lg text-sm px-5 py-2.5 text-center bg-background text-selection_background">Close</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -257,23 +300,20 @@
|
|||||||
md:top-0 md:bottom-0 h-screen w-full"
|
md:top-0 md:bottom-0 h-screen w-full"
|
||||||
>
|
>
|
||||||
<div class="grid w-full grid-col-3">
|
<div class="grid w-full grid-col-3">
|
||||||
<div class="bg-white rounded-lg lg:mx-48 mx-0 lg:space-y-8 space-y-4 lg:px-8">
|
<div class="white rounded-lg lg:mx-48 mx-0 lg:space-y-8 space-y-4 lg:px-8 bg-foreground">
|
||||||
<h1 class="lg:mt-12 mt-6 font-semibold rounded-lg
|
<h1 class="lg:mt-12 mt-6 font-semibold rounded-lg justify-center lg:text-center lg:pl-0 pl-8 mx-4 subtitle">Topos Application Settings</h1>
|
||||||
bg-gray-800 justify-center lg:text-center lg:pl-0 pl-8 text-white mx-4
|
|
||||||
text-sm lg:text-xl border-b border-gray-300 py-4">Topos Application Settings</h1>
|
|
||||||
<div class="flex lg:flex-row flex-col mr-4 ml-4">
|
<div class="flex lg:flex-row flex-col mr-4 ml-4">
|
||||||
<!-- Font Size Selection -->
|
<!-- Font Size Selection -->
|
||||||
<div class="bg-gray-200 rounded-lg ml-0 lg:w-1/3 w-full pt-2 pb-1 mb-2">
|
<div class="rounded-lg ml-0 lg:w-1/3 w-full pt-2 pb-1 mb-2 bg-selection_foreground">
|
||||||
<p class="font-bold lg:text-xl text-sm ml-4 lg:pb-4 pb-2 underline underline-offset-4">Font Settings</p>
|
<p class="font-bold lg:text-xl text-sm ml-4 lg:pb-4 pb-2 underline underline-offset-4 text-selection_background">Theme Settings</p>
|
||||||
<div class="mb-6 mx-4 font-semibold">
|
<div class="mb-6 mx-4 font-semibold">
|
||||||
<label for="default-input" class="block mb-2 ml-1 font-normal sd:text-sm">Size:</label>
|
<label for="default-input" class="block mb-2 ml-1 font-normal sd:text-sm text-foreground">Size:</label>
|
||||||
<input type="text" id="font-size-input" type="number" class="bg-gray-50 border border-gray-300 text-gray-900
|
<input type="text" id="font-size-input" type="number" class="border
|
||||||
text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700
|
text-sm rounded-lg focus:border-blue-500 block w-full p-2.5 focus:border-blue-500">
|
||||||
dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
|
|
||||||
</div>
|
</div>
|
||||||
<label for="font" class="block ml-5 mb-2 font-medium sd:text-sm">Font:</label>
|
<label for="font" class="block ml-5 mb-2 font-medium sd:text-sm text-foreground">Font:</label>
|
||||||
<select id="font-family" class="bg-gray-50 ml-4 border border-gray-300 mb-2
|
<select id="font-family" class=" ml-4 border mb-2
|
||||||
text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white">
|
text-sm rounded-lg focus:border-blue-500 block p-2.5">
|
||||||
<option value="IBM Plex Mono">IBM Plex Mono</option>
|
<option value="IBM Plex Mono">IBM Plex Mono</option>
|
||||||
<option value="Jet Brains">Jet Brains</option>
|
<option value="Jet Brains">Jet Brains</option>
|
||||||
<option value="Courier">Courier</option>
|
<option value="Courier">Courier</option>
|
||||||
@ -285,186 +325,200 @@
|
|||||||
<option value="Steps Mono">Steps Mono</option>
|
<option value="Steps Mono">Steps Mono</option>
|
||||||
<option value="Steps Mono Thin">Steps Mono Thin</option>
|
<option value="Steps Mono Thin">Steps Mono Thin</option>
|
||||||
</select>
|
</select>
|
||||||
|
<div class="rounded-lg ml-0 lg:w-1/3 w-full pt-2 pb-1 mb-2">
|
||||||
|
<label for="theme" class="block ml-5 mb-2 font-medium sd:text-sm text-foreground">Theme:</label>
|
||||||
|
<select id="theme-selector" class="ml-4 border mb-2
|
||||||
|
text-sm rounded-lg block p-2.5">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Editor mode selection -->
|
<!-- Editor mode selection -->
|
||||||
<div class="bg-gray-200 rounded-lg lg:ml-4 lg:w-1/3 w-full pt-2 pb-1 mb-2">
|
<div class="rounded-lg lg:ml-4 lg:w-1/3 w-full pt-2 pb-1 mb-2 bg-selection_foreground">
|
||||||
<p class="font-bold lg:text-xl text-sm ml-4 pb-4 underline underline-offset-4">Editor options</p>
|
<p class="font-bold lg:text-xl text-sm ml-4 pb-4 underline underline-offset-4 text-selection_background">Editor options</p>
|
||||||
<!-- Checkboxes -->
|
<!-- Checkboxes -->
|
||||||
<div class="pr-4">
|
<div class="pr-4">
|
||||||
<div class="flex items-center mb-4 ml-5">
|
<div class="flex items-center mb-4 ml-5">
|
||||||
<input id="vim-mode" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
<input id="vim-mode" type="checkbox" value="" class="w-4 h-4 text-blue-600 rounded focus:ring-blue-600">
|
||||||
<label for="default-checkbox" class="ml-2 text-sm font-medium text-dark">Vim Mode</label>
|
<label for="default-checkbox" class="ml-2 text-sm font-medium text-selection_background">Vim Mode</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center mb-4 ml-5">
|
<div class="flex items-center mb-4 ml-5">
|
||||||
<input id="show-line-numbers" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
<input id="show-line-numbers" type="checkbox" value="" class="w-4 h-4 text-blue-600 rounded focus:ring-blue-600 focus:ring-2">
|
||||||
<label for="default-checkbox" class="ml-2 text-sm font-medium text-dark">Show Line Numbers</label>
|
<label for="default-checkbox" class="ml-2 text-sm font-medium text-foreground">Show Line Numbers</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center mb-4 ml-5">
|
<div class="flex items-center mb-4 ml-5">
|
||||||
<input id="show-time-position" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
<input id="show-time-position" type="checkbox" value="" class="w-4 h-4 text-blue-600 rounded focus:ring-blue-600 focus:ring-2">
|
||||||
<label for="default-checkbox" class="ml-2 text-sm font-medium text-dark">Show Time Position</label>
|
<label for="default-checkbox" class="ml-2 text-sm font-medium text-foreground">Show Time Position</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center mb-4 ml-5">
|
<div class="flex items-center mb-4 ml-5">
|
||||||
<input id="show-tips" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
<input id="show-tips" type="checkbox" value="" class="w-4 h-4 text-blue-600 rounded focus:ring-blue-600 focus:ring-2">
|
||||||
<label for="default-checkbox" class="ml-2 text-sm font-medium text-dark">Show Hovering Tips</label>
|
<label for="default-checkbox" class="ml-2 text-sm font-medium text-foreground">Show Hovering Tips</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center mb-4 ml-5">
|
<div class="flex items-center mb-4 ml-5">
|
||||||
<input id="show-completions" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
<input id="show-completions" type="checkbox" value="" class="w-4 h-4 text-blue-600 rounded focus:ring-blue-600 focus:ring-2">
|
||||||
<label for="default-checkbox" class="ml-2 text-sm font-medium text-dark">Show Completions</label>
|
<label for="default-checkbox" class="ml-2 text-sm font-medium text-foreground">Show Completions</label>
|
||||||
</div>
|
</div>
|
||||||
|
<!--
|
||||||
<div class="flex items-center mb-4 ml-5">
|
<div class="flex items-center mb-4 ml-5">
|
||||||
<input id="load-demo-songs" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
<input id="load-demo-songs" type="checkbox" value="" class="w-4 h-4 text-blue-600 rounded focus:ring-blue-600">
|
||||||
<label for="default-checkbox" class="ml-2 text-sm font-medium text-dark">Load Demo Song</label>
|
<label for="default-checkbox" class="ml-2 text-sm font-medium text-foreground">Load Demo Song</label>
|
||||||
</div>
|
</div>
|
||||||
|
-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-gray-200 rounded-lg lg:ml-4 lg:w-1/3 w-full pt-2 pb-1 mb-2">
|
<div class="rounded-lg lg:ml-4 lg:w-1/3 w-full pt-2 pb-1 mb-2 bg-selection_foreground">
|
||||||
<p class="font-bold lg:text-xl text-sm ml-4 pb-4 underline underline-offset-4">File Management</p>
|
<p class="font-bold lg:text-xl text-sm ml-4 pb-4 underline underline-offset-4 text-selection_background">File Management</p>
|
||||||
<div class="flex flex-col space-y-2 pb-2">
|
<div class="flex flex-col space-y-2 pb-2">
|
||||||
<button id="download-universes" class="bg-gray-800 hover:bg-gray-900 text-white font-bold lg:py-4 lg:px-2 px-1 py-2 rounded-lg inline-flex items-center mx-4">
|
<button id="download-universes" class="bg-brightwhite font-bold lg:py-4 lg:px-2 px-1 py-2 rounded-lg inline-flex items-center mx-4 text-selection_background">
|
||||||
<svg class="fill-current w-4 h-6 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M13 8V2H7v6H2l8 8 8-8h-5zM0 18h20v2H0v-2z"/></svg>
|
<svg class="fill-current w-4 h-6 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M13 8V2H7v6H2l8 8 8-8h-5zM0 18h20v2H0v-2z"/></svg>
|
||||||
<span>Download universes</span>
|
<span class="text-selection_foreground">Download universes</span>
|
||||||
</button>
|
</button>
|
||||||
<button id="upload-universes" class="bg-gray-800 hover:bg-gray-900 text-white font-bold lg:py-4 lg:px-2 px-1 py-2 rounded-lg inline-flex items-center mx-4">
|
<button id="upload-universes" class="bg-brightwhite font-bold lg:py-4 lg:px-2 px-1 py-2 rounded-lg inline-flex items-center mx-4 text-selection_background">
|
||||||
<svg class="rotate-180 fill-current w-4 h-6 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M13 8V2H7v6H2l8 8 8-8h-5zM0 18h20v2H0v-2z"/></svg>
|
<svg class="rotate-180 fill-current w-4 h-6 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M13 8V2H7v6H2l8 8 8-8h-5zM0 18h20v2H0v-2z"/></svg>
|
||||||
<span>Upload universes</span>
|
<span class="text-selection_foreground">Upload universes</span>
|
||||||
</button>
|
</button>
|
||||||
<button id="destroy-universes" class="bg-red-800 hover:bg-red-900 text-white font-bold lg:px-2 px-1 py-2 rounded-lg inline-flex items-center mx-4">
|
<button id="destroy-universes" class="bg-brightwhite font-bold lg:px-2 px-1 py-2 rounded-lg inline-flex items-center mx-4">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-6 mr-2">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-6 mr-2">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>Destroy universes</span>
|
<span class="text-selection_foreground">Destroy universes</span>
|
||||||
</button>
|
</button>
|
||||||
|
<!-- Upload audio samples -->
|
||||||
|
<p class="font-bold lg:text-xl text-sm ml-4 pb-2 pt-2 underline underline-offset-4 text-selection_background">Audio samples</p>
|
||||||
|
|
||||||
|
<label class="bg-brightwhite font-bold lg:py-4 lg:px-2 px-1 py-2 rounded-lg inline-flex items-center mx-4 text-selection_background">
|
||||||
|
<svg class="rotate-180 fill-current w-4 h-6 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M13 8V2H7v6H2l8 8 8-8h-5zM0 18h20v2H0v-2z"/></svg>
|
||||||
|
<input id="upload-samples" type="file" class="hidden" accept="file" webkitdirectory directory multiple>
|
||||||
|
<span id="sample-indicator" class="text-selection_foreground">Import samples</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Midi settings -->
|
<!-- Midi settings -->
|
||||||
<div id="midi-settings-container" class="bg-gray-200 rounded-lg flex lg:flex-row flex-col mx-4 my-4 pt-4">
|
<div id="midi-settings-container" class="rounded-lg flex lg:flex-row flex-col mx-4 my-4 pt-4 bg-color bg-selection_foreground">
|
||||||
<div class="lg:flex lg:flex-row w-fit">
|
<div class="lg:flex lg:flex-row w-fit">
|
||||||
<p class="font-bold lg:text-xl text-sm ml-4 pb-4 underline underline-offset-4">MIDI I/O Settings</p>
|
<p class="font-bold lg:text-xl text-sm ml-4 pb-4 underline underline-offset-4 text-selection_background">MIDI I/O Settings</p>
|
||||||
<div class="flex items-center mb-4 ml-6">
|
<div class="flex items-center mb-4 ml-6">
|
||||||
<label for="default-checkbox" class="ml-2 text-sm font-medium text-dark">MIDI Clock: </label>
|
<label for="default-checkbox" class="ml-2 text-sm font-medium text-foreground">MIDI Clock: </label>
|
||||||
<select id="midi-clock-input" class="w-32 h-8 text-sm font-medium text-black bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2">
|
<select id="midi-clock-input" class="w-32 h-8 text-sm font-medium text-black rounded focus:ring-blue-600">
|
||||||
<option value="-1">Internal</option>
|
<option value="-1">Internal</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="lg:flex block items-center mb-4 ml-6">
|
<div class="lg:flex block items-center mb-4 ml-6">
|
||||||
<label for="default-checkbox" class="ml-2 text-sm font-medium text-dark">Clock PPQN: </label>
|
<label for="default-checkbox" class="ml-2 mr-2 text-sm font-medium text-foreground">Clock PPQN: </label>
|
||||||
<select id="midi-clock-ppqn-input" class="w-32 h-8 text-sm font-medium text-black bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2">
|
<select id="midi-clock-ppqn-input" class="w-32 h-8 text-sm font-medium text-black rounded focus:ring-blue-600">
|
||||||
<option value="24">24</option>
|
<option value="24">24</option>
|
||||||
<option value="48">48</option>
|
<option value="48">48</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="lg:flex block items-center mb-4 ml-6">
|
<div class="lg:flex block items-center mb-4 ml-6">
|
||||||
<input id="send-midi-clock" type="checkbox" value="" class="lg:w-8 lg:h-8 h-4 w-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
<input id="send-midi-clock" type="checkbox" value="" class="lg:w-8 lg:h-8 h-4 w-4 text-blue-600 rounded focus:ring-blue-600 focus:ring-2">
|
||||||
<label for="default-checkbox" class="ml-2 text-sm font-medium text-dark">Send MIDI Clock</label>
|
<label for="default-checkbox" class="ml-2 text-sm font-medium text-foreground">Send MIDI Clock</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lg:flex block flex-row">
|
<div class="lg:flex block flex-row">
|
||||||
<div class="flex items-center mb-4 ml-6">
|
<div class="flex items-center mb-4 ml-6">
|
||||||
<label for="default-checkbox" class="ml-2 text-sm font-medium text-dark">MIDI input: </label>
|
<label for="default-checkbox" class="ml-2 mr-2 text-sm font-medium text-foreground">MIDI input: </label>
|
||||||
<select id="default-midi-input" class="w-32 h-8 text-sm font-medium text-black bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2">
|
<select id="default-midi-input" class="w-32 h-8 text-sm font-medium text-black rounded focus:ring-blue-600 focus:ring-2">
|
||||||
<option value="-1">None</option>
|
<option value="-1">None</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="lg:flex block items-center mb-4 ml-6">
|
<div class="lg:flex block items-center mb-4 ml-6">
|
||||||
<input id="midi-channels-scripts" type="checkbox" value="" class="lg:w-8 lg:h-8 h-4 w-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
<input id="midi-channels-scripts" type="checkbox" value="" class="lg:w-8 lg:h-8 h-4 w-4 text-blue-600 rounded focus:ring-blue-600">
|
||||||
<label for="default-checkbox" class="ml-2 text-sm font-medium text-dark">Route channels to scripts</label>
|
<label for="default-checkbox" class="ml-2 text-sm font-medium text-foreground">Route channels to scripts</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Audio nudge slider -->
|
<!-- Audio nudge slider -->
|
||||||
<div id="midi-settings-container" class="bg-gray-200 rounded-lg flex flex-col mx-4 my-4 pt-4 pb-2">
|
<div id="midi-settings-container" class="rounded-lg flex flex-col mx-4 my-4 pt-4 pb-2 bg-selection_foreground">
|
||||||
<p class="font-bold lg:text-xl text-sm ml-4 pb-4 underline underline-offset-4">Audio/Event Nudging</p>
|
<p class="font-bold lg:text-xl text-sm ml-4 pb-4 underline underline-offset-4 text-selection_background">Audio/Event Nudging</p> <div class="flex flex-column pb-2">
|
||||||
<div class="flex flex-column pb-2">
|
<p class="pt-0.5 ml-4 text-foreground">Clock:</p>
|
||||||
<p class="pt-0.5 ml-4">Clock:</p>
|
|
||||||
<input
|
<input
|
||||||
type="range" id="audio_nudge"
|
type="range" id="audio_nudge"
|
||||||
name="audiorangeInput"
|
name="audiorangeInput"
|
||||||
min="-200" max="200"
|
min="-200" max="200"
|
||||||
value="0"
|
value="0"
|
||||||
class="w-full ml-4"
|
class="w-full ml-4 text-red"
|
||||||
oninput="nudgenumber.value=audio_nudge.value"
|
oninput="nudgenumber.value=audio_nudge.value"
|
||||||
>
|
>
|
||||||
<output
|
<output
|
||||||
name="nudgenumber"
|
name="nudgenumber"
|
||||||
id="nudgenumber"
|
id="nudgenumber"
|
||||||
for="audiorangeInput"
|
for="audiorangeInput"
|
||||||
class="bg-gray-500 rounded-lg ml-2 mr-4 px-4 py-1 text-white"
|
class="rounded-lg ml-2 mr-4 px-4 py-1 text-foreground"
|
||||||
>0</output>
|
>0</output>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-column">
|
<div class="flex flex-column">
|
||||||
|
|
||||||
<p class="pt-0.5 ml-4">Audio:</p>
|
<p class="pt-0.5 ml-4 text-foreground">Audio:</p>
|
||||||
<input
|
<input
|
||||||
type="range" id="dough_nudge"
|
type="range" id="dough_nudge"
|
||||||
name="doughrangeInput"
|
name="doughrangeInput"
|
||||||
min="0" max="100"
|
min="0" max="100"
|
||||||
value="0"
|
value="0"
|
||||||
class="w-full ml-4"
|
class="w-full ml-4 text-foreground"
|
||||||
oninput="doughnumber.value=dough_nudge.value"
|
oninput="doughnumber.value=dough_nudge.value"
|
||||||
>
|
>
|
||||||
<output
|
<output
|
||||||
name="doughnumber"
|
name="doughnumber"
|
||||||
id="doughnumber"
|
id="doughnumber"
|
||||||
for="doughrangeInput"
|
for="doughrangeInput"
|
||||||
class="bg-gray-500 rounded-lg ml-2 mr-4 px-4 py-1 text-white"
|
class="rounded-lg ml-2 mr-4 px-4 py-1 text-foreground"
|
||||||
>0</output>
|
>0</output>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex space-x-6 border-t border-gray-200 rounded-b dark:border-gray-600 mx-4 border-spacing-y-4 pb-36 lg:pb-0">
|
<div class="flex space-x-6 border-t rounded-b mx-4 border-spacing-y-4 pb-36 lg:pb-0">
|
||||||
<button id="close-settings-button" data-modal-hide="defaultModal" type="button" class="
|
<button id="close-settings-button" data-modal-hide="defaultModal" type="button" class="
|
||||||
hover:bg-gray-700 bg-gray-800 mt-4 mb-4 text-white focus:ring-4
|
hover:bg-background bg-background mt-4 mb-4 focus:ring-4
|
||||||
font-medium rounded-lg text-sm px-5 py-2.5 text-center">OK</button>
|
font-medium rounded-lg text-sm px-5 py-2.5 text-center text-selection_background">OK</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-row max-h-fit">
|
<div class="flex flex-row max-h-fit">
|
||||||
|
|
||||||
<!-- 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 id="sidebar" class="
|
||||||
flex flex-col items-center w-14
|
flex flex-col items-center w-14
|
||||||
h-screen py-2 border-r
|
h-screen py-2 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-background
|
||||||
dark:border-neutral-700 border-none"
|
border-neutral-700 border-none"
|
||||||
>
|
>
|
||||||
<nav class="flex flex-col space-y-6">
|
<nav class="flex flex-col space-y-6">
|
||||||
<a title="Local Scripts (Ctrl + L)" id="local-button" class="pl-2 p-1.5 focus:outline-nones transition-colors duration-200 rounded-lg text-white hover:bg-gray-800">
|
<a title="Local Scripts (Ctrl + L)" id="local-button" class="side_button">
|
||||||
<svg class="w-8 h-8 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 18">
|
<svg class="w-8 h-8 " aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 18">
|
||||||
<path d="M18 5H0v11a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5Zm-7.258-2L9.092.8a2.009 2.009 0 0 0-1.6-.8H2.049a2 2 0 0 0-2 2v1h10.693Z"/>
|
<path d="M18 5H0v11a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5Zm-7.258-2L9.092.8a2.009 2.009 0 0 0-1.6-.8H2.049a2 2 0 0 0-2 2v1h10.693Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a title="Global Script (Ctrl + G)" id="global-button" class="pl-2 p-1.5 text-white focus:outline-nones transition-colors duration-200 rounded-lg hover:bg-gray-800">
|
<a title="Global Script (Ctrl + G)" id="global-button" class="side_button">
|
||||||
<svg class="w-8 h-8 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 16">
|
<svg class="w-8 h-8 " aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 16">
|
||||||
<path d="M14.316.051A1 1 0 0 0 13 1v8.473A4.49 4.49 0 0 0 11 9c-2.206 0-4 1.525-4 3.4s1.794 3.4 4 3.4 4-1.526 4-3.4a2.945 2.945 0 0 0-.067-.566c.041-.107.064-.22.067-.334V2.763A2.974 2.974 0 0 1 16 5a1 1 0 0 0 2 0C18 1.322 14.467.1 14.316.051ZM10 3H1a1 1 0 0 1 0-2h9a1 1 0 1 1 0 2Z"/>
|
<path d="M14.316.051A1 1 0 0 0 13 1v8.473A4.49 4.49 0 0 0 11 9c-2.206 0-4 1.525-4 3.4s1.794 3.4 4 3.4 4-1.526 4-3.4a2.945 2.945 0 0 0-.067-.566c.041-.107.064-.22.067-.334V2.763A2.974 2.974 0 0 1 16 5a1 1 0 0 0 2 0C18 1.322 14.467.1 14.316.051ZM10 3H1a1 1 0 0 1 0-2h9a1 1 0 1 1 0 2Z"/>
|
||||||
<path d="M10 7H1a1 1 0 0 1 0-2h9a1 1 0 1 1 0 2Zm-5 4H1a1 1 0 0 1 0-2h4a1 1 0 1 1 0 2Z"/>
|
<path d="M10 7H1a1 1 0 0 1 0-2h9a1 1 0 1 1 0 2Zm-5 4H1a1 1 0 0 1 0-2h4a1 1 0 1 1 0 2Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a title="Initialisation Script (Ctrl + I)" id="init-button" class="pl-2 p-1.5 focus:outline-nones transition-colors duration-200 rounded-lg text-white hover:bg-gray-800">
|
<a title="Initialisation Script (Ctrl + I)" id="init-button" class="side_button">
|
||||||
<svg class="w-8 h-8 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 14">
|
<svg class="w-8 h-8 " aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 14">
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 1v12m0 0 4-4m-4 4L1 9"/>
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 1v12m0 0 4-4m-4 4L1 9"/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a title="Project notes (Ctrl + N)" id="note-button" class="pl-2 p-1.5 text-white focus:outline-nones transition-colors duration-200 rounded-lg text-white hover:bg-gray-800">
|
<a title="Project notes (Ctrl + N)" id="note-button" class="side_button">
|
||||||
<svg class="w-8 h-8 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
<svg class="w-8 h-8 " aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path d="m13.835 7.578-.005.007-7.137 7.137 2.139 2.138 7.143-7.142-2.14-2.14Zm-10.696 3.59 2.139 2.14 7.138-7.137.007-.005-2.141-2.141-7.143 7.143Zm1.433 4.261L2 12.852.051 18.684a1 1 0 0 0 1.265 1.264L7.147 18l-2.575-2.571Zm14.249-14.25a4.03 4.03 0 0 0-5.693 0L11.7 2.611 17.389 8.3l1.432-1.432a4.029 4.029 0 0 0 0-5.689Z"/>
|
<path d="m13.835 7.578-.005.007-7.137 7.137 2.139 2.138 7.143-7.142-2.14-2.14Zm-10.696 3.59 2.139 2.14 7.138-7.137.007-.005-2.141-2.141-7.143 7.143Zm1.433 4.261L2 12.852.051 18.684a1 1 0 0 0 1.265 1.264L7.147 18l-2.575-2.571Zm14.249-14.25a4.03 4.03 0 0 0-5.693 0L11.7 2.611 17.389 8.3l1.432-1.432a4.029 4.029 0 0 0 0-5.689Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a title="Application Settings" id="settings-button" class="pl-2 p-1.5 text-white focus:outline-nones transition-colors duration-200 rounded-lg text-white hover:bg-gray-800">
|
<a title="Application Settings" id="settings-button" class="side_button">
|
||||||
<svg class="w-8 h-8 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
<svg class="w-8 h-8 " aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<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">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" />
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||||
@ -478,55 +532,53 @@
|
|||||||
|
|
||||||
<!-- 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=" flex text-xl font-medium text-center text-white bg-neutral-900 space-x-1 lg:space-x-8">
|
<ul id="local-script-tabs" class=" flex text-xl font-medium text-center bg-background space-x-1 lg:space-x-8">
|
||||||
<li class="pl-5">
|
<li class="pl-5">
|
||||||
<a title="Local Script 1 (F1)" id="tab-1" class="bg-orange-300 inline-block lg:px-4 px-2 py-1 text-white hover:bg-gray-800">1</a>
|
<a title="Local Script 1 (F1)" id="tab-1" class="tab_panel">1</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="">
|
<li class="">
|
||||||
<a title="Local Script 2 (F2)" id="tab-2" class="inline-block lg:px-4 px-2 py-1 hover:bg-gray-800">2</a>
|
<a title="Local Script 2 (F2)" id="tab-2" class="tab_panel">2</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="">
|
<li class="">
|
||||||
<a title="Local Script 3 (F3)" id="tab-3" class="inline-block lg:px-4 px-2 py-1 hover:bg-gray-800">3</a>
|
<a title="Local Script 3 (F3)" id="tab-3" class="tab_panel">3</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="">
|
<li class="">
|
||||||
<a title="Local Script 4 (F4)" id="tab-4" class="inline-block lg:px-4 px-2 py-1 hover:bg-gray-800">4</a>
|
<a title="Local Script 4 (F4)" id="tab-4" class="tab_panel">4</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="">
|
<li class="">
|
||||||
<a title="Local Script 5 (F5)" id="tab-5" class="inline-block lg:px-4 px-2 py-1 hover:bg-gray-800">5</a>
|
<a title="Local Script 5 (F5)" id="tab-5" class="tab_panel">5</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="">
|
<li class="">
|
||||||
<a title="Local Script 6 (F6)" id="tab-6" class="inline-block lg:px-4 px-2 py-1 hover:bg-gray-800">6</a>
|
<a title="Local Script 6 (F6)" id="tab-6" class="tab_panel">6</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="">
|
<li class="">
|
||||||
<a title="Local Script 7 (F7)" id="tab-7" class="inline-block lg:px-4 px-2 py-1 hover:bg-gray-800">7</a>
|
<a title="Local Script 7 (F7)" id="tab-7" class="tab_panel">7</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="">
|
<li class="">
|
||||||
<a title="Local Script 8 (F8)" id="tab-8" class="inline-block lg:px-4 px-2 py-1 hover:bg-gray-800">8</a>
|
<a title="Local Script 8 (F8)" id="tab-8" class="tab_panel">8</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="">
|
<li class="">
|
||||||
<a title="Local Script 9 (F9)" id="tab-9" class="inline-block lg:px-4 px-2 py-1 hover:bg-gray-800">9</a>
|
<a title="Local Script 9 (F9)" id="tab-9" class="tab_panel">9</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
<!-- Here comes the editor itself -->
|
<!-- Here comes the editor itself -->
|
||||||
<div id="editor" class="relative flex flex-row h-screen overflow-y-hidden">
|
<div id="editor" class="relative flex flex-row h-screen overflow-y-hidden">
|
||||||
<canvas id="hydra-bg" class="fullscreencanvas"></canvas>
|
|
||||||
<canvas id="scope" class="fullscreencanvas"></canvas>
|
<canvas id="scope" class="fullscreencanvas"></canvas>
|
||||||
<canvas id="feedback" class="fullscreencanvas"></canvas>
|
<canvas id="feedback" class="fullscreencanvas"></canvas>
|
||||||
|
<canvas id="hydra-bg" class="hydracanvas"></canvas>
|
||||||
</div>
|
</div>
|
||||||
<p id="error_line" class="hidden text-red-400 w-screen bg-neutral-900 font-mono absolute bottom-0 pl-2 py-2">Hello kids</p>
|
<p id="error_line" class="hidden w-screen bg-background font-mono absolute bottom-0 pl-2 py-2">Hello kids</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
|
||||||
<template id="ui-known-universe-item-template">
|
<template id="ui-known-universe-item-template">
|
||||||
<!-- A known universe button in "opening" interface -->
|
<!-- A known universe button in "opening" interface -->
|
||||||
<li class="hover:fill-black hover:bg-white py-2 hover:text-black flex justify-between px-4">
|
<li class="py-2 px-4 flex justify-between text-brightwhite hover:bg-selection_background hover:text-selection_foreground">
|
||||||
<button class="universe-name load-universe" title="Load this universe">Universe Name</button>
|
<button class="universe-name load-universe" title="Load this universe">Universe Name</button>
|
||||||
<button class="delete-universe" title="Delete this universe">🗑</button>
|
<button class="delete-universe" title="Delete this universe">🗑</button>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
</body>
|
</body>
|
||||||
<p id="timeviewer" class="rounded-lg px-2 py-2 font-bold bg-white cursor-textpointer-events-none select-none text-black text-sm absolute bottom-2 right-2"></p>
|
<p id="fillviewer" class="invisible rounded-lg px-2 py-2 font-bold cursor-textpointer-events-none select-none text-sm absolute right-2 bottom-12 bg-foreground text-background">/////// Fill ///////</p>
|
||||||
<p id="fillviewer" class="invisible rounded-lg px-2 py-2 font-bold bg-white cursor-textpointer-events-none select-none text-black text-sm absolute right-2 bottom-12">/////// Fill ///////</p>
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Topos",
|
|
||||||
"short_name": "Topos",
|
|
||||||
"description": "Live coding environment",
|
|
||||||
"theme_color": "#ffffff",
|
|
||||||
"background_color": "#ffffff",
|
|
||||||
"display": "standalone",
|
|
||||||
"scope": "/",
|
|
||||||
"start_url": "/",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "./favicon/android-chrome-192x192.png",
|
|
||||||
"sizes": "192x192",
|
|
||||||
"type": "image/png",
|
|
||||||
"purpose": "any maskable"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "./favicon/android-chrome-512x512.png",
|
|
||||||
"sizes": "512x512",
|
|
||||||
"type": "image/png",
|
|
||||||
"purpose": "any maskable"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
11
package.json
@ -14,7 +14,7 @@
|
|||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^4.4.5",
|
"vite": "^4.4.5",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-pwa": "^0.16.7"
|
"vite-plugin-pwa": "^0.17.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-javascript": "^6.1.9",
|
"@codemirror/lang-javascript": "^6.1.9",
|
||||||
@ -30,19 +30,22 @@
|
|||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"fflate": "^0.8.0",
|
"fflate": "^0.8.0",
|
||||||
|
"highlight.js": "^11.9.0",
|
||||||
"jisg": "^0.9.7",
|
"jisg": "^0.9.7",
|
||||||
"lru-cache": "^10.0.1",
|
"lru-cache": "^10.2.0",
|
||||||
"marked": "^7.0.3",
|
"marked": "^7.0.3",
|
||||||
|
"osc": "^2.4.4",
|
||||||
"postcss": "^8.4.27",
|
"postcss": "^8.4.27",
|
||||||
"showdown": "^2.1.0",
|
"showdown": "^2.1.0",
|
||||||
"showdown-highlight": "^3.1.0",
|
"showdown-highlight": "^3.1.0",
|
||||||
"superdough": "^0.9.11",
|
"superdough": "^0.9.12",
|
||||||
"tailwind-highlightjs": "^2.0.1",
|
"tailwind-highlightjs": "^2.0.1",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"tone": "^14.8.49",
|
"tone": "^14.8.49",
|
||||||
"unique-names-generator": "^4.7.1",
|
"unique-names-generator": "^4.7.1",
|
||||||
"vite-plugin-markdown": "^2.1.0",
|
"vite-plugin-markdown": "^2.1.0",
|
||||||
"zifferjs": "^0.0.39",
|
"zifferjs": "^0.0.62",
|
||||||
|
"zyklus": "^0.1.4",
|
||||||
"zzfx": "^1.2.0"
|
"zzfx": "^1.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,4 +3,4 @@ export default {
|
|||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
46
public/favicon/favicon.svg
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="48.000000pt" height="48.000000pt" viewBox="0 0 48.000000 48.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
|
||||||
|
<g transform="translate(0.000000,48.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M163 470 c-13 -5 -23 -12 -23 -15 0 -3 46 -5 102 -5 80 0 99 3 90 12
|
||||||
|
-15 15 -139 21 -169 8z"/>
|
||||||
|
<path d="M91 391 c-16 -16 -19 -32 -18 -84 1 -53 -2 -69 -18 -87 -15 -16 -20
|
||||||
|
-38 -22 -83 0 -33 2 -56 5 -50 9 13 44 13 39 -1 -2 -6 -18 -11 -34 -11 -39 0
|
||||||
|
-50 -10 -33 -30 8 -10 30 -15 60 -15 26 0 64 -7 83 -15 43 -18 125 -20 164 -3
|
||||||
|
15 6 57 14 93 17 73 7 87 24 51 60 -12 12 -21 17 -21 13 0 -4 5 -13 12 -20 8
|
||||||
|
-8 8 -12 1 -12 -18 0 -25 34 -9 45 10 7 12 16 6 25 -5 8 -7 24 -4 35 3 13 -2
|
||||||
|
28 -15 39 -12 11 -21 27 -21 36 0 9 -5 21 -11 27 -8 8 -8 17 0 31 17 32 13 56
|
||||||
|
-12 80 -31 29 -63 28 -91 -3 -16 -17 -34 -25 -56 -25 -22 0 -40 8 -56 25 -28
|
||||||
|
30 -67 32 -93 6z m89 -16 c19 -23 5 -29 -24 -10 -16 10 -33 14 -47 10 -14 -5
|
||||||
|
-19 -4 -15 4 10 16 72 13 86 -4z m211 -7 c12 -22 11 -22 -8 -5 -25 21 -41 22
|
||||||
|
-65 0 -22 -19 -36 -10 -18 12 20 24 77 19 91 -7z m-264 -30 c-3 -7 -5 -2 -5
|
||||||
|
12 0 14 2 19 5 13 2 -7 2 -19 0 -25z m229 -5 c-11 -11 -19 6 -11 24 8 17 8 17
|
||||||
|
12 0 3 -10 2 -21 -1 -24z m-80 -8 c4 -8 10 -12 15 -9 5 3 9 0 9 -6 0 -14 60
|
||||||
|
-40 77 -33 7 3 13 -2 13 -11 0 -13 -6 -15 -27 -9 -36 9 -210 9 -245 0 -22 -6
|
||||||
|
-28 -4 -28 9 0 8 6 14 13 11 6 -2 25 2 40 10 15 8 32 11 37 8 6 -4 7 1 3 11
|
||||||
|
-4 12 -3 15 5 10 7 -4 15 -1 18 8 8 21 63 21 70 1z m83 -91 c10 -9 -37 -33
|
||||||
|
-77 -39 -55 -8 -117 2 -155 26 l-30 18 54 4 c56 4 201 -2 208 -9z m-286 -30
|
||||||
|
c-15 -7 21 -44 42 -44 8 0 15 -4 15 -10 0 -16 -33 -11 -59 9 -25 19 -24 52 1
|
||||||
|
50 9 0 10 -2 1 -5z m355 -21 c2 -14 -4 -23 -17 -28 -12 -3 -21 -13 -21 -21 0
|
||||||
|
-19 -16 -18 -24 1 -5 15 -31 16 -53 2 -8 -5 -13 -4 -13 2 0 6 5 12 10 12 6 1
|
||||||
|
15 3 20 4 6 1 18 3 28 4 25 1 66 37 52 44 -6 3 -5 4 2 3 7 -1 14 -12 16 -23z
|
||||||
|
m-255 -37 c4 -10 1 -13 -9 -9 -7 3 -14 9 -14 14 0 14 17 10 23 -5z m42 -6 c3
|
||||||
|
-5 1 -10 -4 -10 -6 0 -11 5 -11 10 0 6 2 10 4 10 3 0 8 -4 11 -10z m35 0 c0
|
||||||
|
-5 -4 -10 -10 -10 -5 0 -10 5 -10 10 0 6 5 10 10 10 6 0 10 -4 10 -10z m-170
|
||||||
|
-10 c0 -5 -5 -10 -11 -10 -5 0 -7 5 -4 10 3 6 8 10 11 10 2 0 4 -4 4 -10z
|
||||||
|
m215 -39 c-6 -5 -25 10 -25 20 0 5 6 4 14 -3 8 -7 12 -15 11 -17z m45 8 c0
|
||||||
|
-14 -16 -11 -29 5 -10 12 -8 13 8 9 12 -3 21 -9 21 -14z m-220 1 c0 -5 -5 -10
|
||||||
|
-11 -10 -5 0 -7 5 -4 10 3 6 8 10 11 10 2 0 4 -4 4 -10z m40 0 c0 -5 -4 -10
|
||||||
|
-10 -10 -5 0 -10 5 -10 10 0 6 5 10 10 10 6 0 10 -4 10 -10z m215 0 c3 -5 2
|
||||||
|
-10 -4 -10 -5 0 -13 5 -16 10 -3 6 -2 10 4 10 5 0 13 -4 16 -10z m43 -10 c7
|
||||||
|
-11 10 -20 6 -20 -7 0 -34 27 -34 34 0 13 16 5 28 -14z m-160 -17 c-10 -2 -26
|
||||||
|
-2 -35 0 -10 3 -2 5 17 5 19 0 27 -2 18 -5z m99 1 c-3 -3 -12 -4 -19 -1 -8 3
|
||||||
|
-5 6 6 6 11 1 17 -2 13 -5z m40 0 c-3 -3 -12 -4 -19 -1 -8 3 -5 6 6 6 11 1 17
|
||||||
|
-2 13 -5z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 603 B After Width: | Height: | Size: 603 B |
BIN
public/favicon/screenshot_miniature.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
public/favicon/topos_code.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
37
public/manifest.webmanifest
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "Topos",
|
||||||
|
"short_name": "Topos",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "favicon/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"display": "standalone",
|
||||||
|
"start_url": "/",
|
||||||
|
"scope": "/",
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"description": "Topos is a web based live coding platform",
|
||||||
|
"screenshots": [
|
||||||
|
{
|
||||||
|
"src": "favicon/screenshot_miniature.png",
|
||||||
|
"sizes": "640x320",
|
||||||
|
"type": "image/gif",
|
||||||
|
"form_factor": "wide",
|
||||||
|
"label": "Topos application"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "favicon/topos_code.png",
|
||||||
|
"sizes": "1280x768",
|
||||||
|
"type": "image/gif",
|
||||||
|
"label": "Topos code"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
2147
src/API.ts
630
src/API/API.ts
Normal file
@ -0,0 +1,630 @@
|
|||||||
|
import * as Transport from './Time/Transport';
|
||||||
|
import * as Mouse from './DOM/Mouse';
|
||||||
|
import * as Theme from './DOM/Theme';
|
||||||
|
import * as Canvas from './DOM/Canvas';
|
||||||
|
import * as Cache from './Cache';
|
||||||
|
import * as Script from './Script';
|
||||||
|
import * as Drunk from './Drunk';
|
||||||
|
import * as Warp from './Time/Warp';
|
||||||
|
import * as Mathematics from './Math';
|
||||||
|
import * as Ziffers from './Ziffers';
|
||||||
|
import * as Filters from './Time/Filters';
|
||||||
|
import * as LFO from './LFO';
|
||||||
|
import * as Probability from './Probabilities';
|
||||||
|
import * as OSC from './IO/OSC';
|
||||||
|
import * as Randomness from './Randomness';
|
||||||
|
import * as Counter from './Counter';
|
||||||
|
import * as Sound from './Sound';
|
||||||
|
import * as Console from './DOM/Console';
|
||||||
|
import { type SoundEvent } from '../Classes/SoundEvent';
|
||||||
|
import { type SkipEvent } from '../Classes/SkipEvent';
|
||||||
|
import { OscilloscopeConfig } from "../DOM/Visuals/Oscilloscope";
|
||||||
|
import { Player } from "../Classes/ZPlayer";
|
||||||
|
import { InputOptions } from "../Classes/ZPlayer";
|
||||||
|
import { type ShapeObject } from "../API/DOM/Canvas";
|
||||||
|
import { nearScales } from "zifferjs";
|
||||||
|
import { MidiConnection } from "../IO/MidiConnection";
|
||||||
|
import { DrunkWalk } from "../Utils/Drunk";
|
||||||
|
import { Editor } from "../main";
|
||||||
|
import { LRUCache } from "lru-cache";
|
||||||
|
import {
|
||||||
|
loadUniverse,
|
||||||
|
openUniverseModal,
|
||||||
|
} from "../Editor/FileManagement";
|
||||||
|
import {
|
||||||
|
samples,
|
||||||
|
initAudioOnFirstClick,
|
||||||
|
registerSynthSounds,
|
||||||
|
registerZZFXSounds,
|
||||||
|
soundMap,
|
||||||
|
// @ts-ignore
|
||||||
|
} from "superdough";
|
||||||
|
import { getScaleNotes } from "zifferjs";
|
||||||
|
import drums from "../tidal-drum-machines.json";
|
||||||
|
import { updatePlayPauseIcon } from '../DOM/UILogic';
|
||||||
|
|
||||||
|
export async function loadSamples() {
|
||||||
|
return Promise.all([
|
||||||
|
initAudioOnFirstClick(),
|
||||||
|
samples("github:tidalcycles/Dirt-Samples/master", undefined, {
|
||||||
|
tag: "Tidal",
|
||||||
|
}).then(() => registerSynthSounds()),
|
||||||
|
registerZZFXSounds(),
|
||||||
|
samples(drums, "github:ritchse/tidal-drum-machines/main/machines/", {
|
||||||
|
tag: "Machines",
|
||||||
|
}),
|
||||||
|
samples("github:Bubobubobubobubo/Dough-Fox/main", undefined, {
|
||||||
|
tag: "FoxDot",
|
||||||
|
}),
|
||||||
|
samples("github:Bubobubobubobubo/Dough-Samples/main", undefined, {
|
||||||
|
tag: "Pack",
|
||||||
|
}),
|
||||||
|
samples("github:Bubobubobubobubo/Dough-Amiga/main", undefined, {
|
||||||
|
tag: "Amiga",
|
||||||
|
}),
|
||||||
|
samples("github:Bubobubobubobubo/Dough-Juj/main", undefined, {
|
||||||
|
tag: "Juliette",
|
||||||
|
}),
|
||||||
|
samples("github:Bubobubobubobubo/Dough-Amen/main", undefined, {
|
||||||
|
tag: "Amen",
|
||||||
|
}),
|
||||||
|
samples("github:Bubobubobubobubo/Dough-Waveforms/main", undefined, {
|
||||||
|
tag: "Waveforms",
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UserAPI {
|
||||||
|
/**
|
||||||
|
* The UserAPI class is the interface between the user's code and the backend. It provides
|
||||||
|
* access to the AudioContext, to the MIDI Interface, to internal variables, mouse position,
|
||||||
|
* useful functions, etc... This class is exposed to the user's action and any function
|
||||||
|
* destined to the user should be placed here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public codeExamples: { [key: string]: string } = {};
|
||||||
|
public counters: { [key: string]: any } = {};
|
||||||
|
//@ts-ignore
|
||||||
|
public _drunk: DrunkWalk = new DrunkWalk(-100, 100, false);
|
||||||
|
public randomGen = Math.random;
|
||||||
|
public currentSeed: string | undefined = undefined;
|
||||||
|
public localSeeds = new Map<string, Function>();
|
||||||
|
public patternCache = new LRUCache({ max: 10000, ttl: 10000 * 60 * 5 });
|
||||||
|
public invalidPatterns: { [key: string]: boolean } = {};
|
||||||
|
public cueTimes: { [key: string]: number } = {};
|
||||||
|
private errorTimeoutID: number = 0;
|
||||||
|
private printTimeoutID: number = 0;
|
||||||
|
public MidiConnection: MidiConnection;
|
||||||
|
public scale_aid: string | number | undefined = undefined;
|
||||||
|
public hydra: any;
|
||||||
|
public onceEvaluator: boolean = true;
|
||||||
|
public forceEvaluator: boolean = false;
|
||||||
|
|
||||||
|
load: samples;
|
||||||
|
public global: { [key: string]: any };
|
||||||
|
time: () => number;
|
||||||
|
play: () => void;
|
||||||
|
pause: () => void;
|
||||||
|
stop: () => void;
|
||||||
|
silence: () => void;
|
||||||
|
mouseX: () => number;
|
||||||
|
mouseY: () => number;
|
||||||
|
noteX: () => number;
|
||||||
|
noteY: () => number;
|
||||||
|
tempo: (n?: number | undefined) => number;
|
||||||
|
ppqn: (n?: number | undefined) => number;
|
||||||
|
time_signature: (numerator: number, denominator: number) => void;
|
||||||
|
theme: (color_scheme: string) => void;
|
||||||
|
themeName: () => string;
|
||||||
|
randomTheme: () => void;
|
||||||
|
nextTheme: () => void;
|
||||||
|
getThemes: () => string[];
|
||||||
|
pulseLocation: () => number;
|
||||||
|
clear: () => boolean;
|
||||||
|
loadHydra: () => void;
|
||||||
|
w: () => number;
|
||||||
|
h: () => number;
|
||||||
|
hc: () => number;
|
||||||
|
wc: () => number;
|
||||||
|
background: (color: string | number, ...gb: number[]) => boolean;
|
||||||
|
linearGradient: (x1: number, y1: number, x2: number, y2: number, ...stops: (number | string)[]) => CanvasGradient;
|
||||||
|
radialGradient: (x1: number, y1: number, r1: number, x2: number, y2: number, r2: number, ...stops: (number | string)[]) => CanvasGradient;
|
||||||
|
conicGradient: (x: number, y: number, angle: number, ...stops: (number | string)[]) => CanvasGradient;
|
||||||
|
draw: (func: Function) => boolean;
|
||||||
|
balloid: (curves: number | ShapeObject, radius: number, curve: number, fillStyle: string, secondary: string, x: number, y: number) => boolean;
|
||||||
|
equilateral: (radius: number | ShapeObject, fillStyle: string, rotation: number, x: number, y: number) => boolean;
|
||||||
|
triangular: (width: number | ShapeObject, height: number, fillStyle: string, rotation: number, x: number, y: number) => boolean;
|
||||||
|
ball: (radius: number | ShapeObject, fillStyle: string, x: number, y: number) => boolean;
|
||||||
|
circle: (radius: number | ShapeObject, fillStyle: string, x: number, y: number) => boolean;
|
||||||
|
donut: (slices: number | ShapeObject, eaten: number, radius: number, hole: number, fillStyle: string, secondary: string, stroke: string, rotation: number, x: number, y: number) => boolean;
|
||||||
|
pie: (slices: number | ShapeObject, eaten: number, radius: number, fillStyle: string, secondary: string, stroke: string, rotation: number, x: number, y: number) => boolean;
|
||||||
|
star: (points: number | ShapeObject, radius: number, fillStyle: string, rotation: number, outerRadius: number, x: number, y: number) => boolean;
|
||||||
|
stroke: (width: number | ShapeObject, strokeStyle: string, rotation: number, x1: number, y1: number, x2: number, y2: number) => boolean;
|
||||||
|
box: (width: number | ShapeObject, height: number, fillStyle: string, rotation: number, x: number, y: number) => boolean;
|
||||||
|
smiley: (happiness: number | ShapeObject, radius: number, eyeSize: number, fillStyle: string, rotation: number, x: number, y: number) => boolean;
|
||||||
|
text: (text: string | ShapeObject, fontSize: number, rotation: number, font: string, x: number, y: number, fillStyle: string, filter: string) => boolean;
|
||||||
|
image: (url: string | ShapeObject, width: number, height: number, rotation: number, x: number, y: number, filter: string) => boolean;
|
||||||
|
randomChar: (length: number, min: number, max: number) => string;
|
||||||
|
randomFromRange: (min: number, max: number) => string;
|
||||||
|
emoji: (n: number) => string;
|
||||||
|
food: (n: number) => string;
|
||||||
|
animals: (n: number) => string;
|
||||||
|
expressions: (n: number) => string;
|
||||||
|
generateCacheKey: (...args: any[]) => string;
|
||||||
|
resetAllFromCache: () => void;
|
||||||
|
clearPatternCache: () => void;
|
||||||
|
removePatternFromCache: (id: string) => void;
|
||||||
|
script: (...args: number[]) => void;
|
||||||
|
s: (...args: number[]) => void;
|
||||||
|
delete_script: (script: number) => void;
|
||||||
|
cs: (script: number) => void;
|
||||||
|
copy_script: (from: number, to: number) => void;
|
||||||
|
cps: (from: number, to: number) => void;
|
||||||
|
copy_universe: (from: string, to: string) => void;
|
||||||
|
delete_universe: (universe: string) => void;
|
||||||
|
big_bang: () => void;
|
||||||
|
drunk: (n?: number | undefined) => number;
|
||||||
|
drunk_max: (max: number) => void;
|
||||||
|
drunk_min: (min: number) => void;
|
||||||
|
drunk_wrap: (wrap: boolean) => void;
|
||||||
|
warp: (n: number) => void;
|
||||||
|
beat_warp: (beat: number) => void;
|
||||||
|
min: (...values: number[]) => number;
|
||||||
|
max: (...values: number[]) => number;
|
||||||
|
mean: (...values: number[]) => number;
|
||||||
|
limit: (value: number, min: number, max: number) => number;
|
||||||
|
abs: (value: number) => number;
|
||||||
|
z: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
fullseq: (sequence: string, duration: number) => boolean | boolean[];
|
||||||
|
seq: (expr: string, duration?: number) => boolean;
|
||||||
|
beat: (n?: number | number[], nudge?: number) => boolean;
|
||||||
|
bar: (n?: number | number[], nudge?: number) => boolean;
|
||||||
|
pulse: (n?: number | number[], nudge?: number) => boolean;
|
||||||
|
tick: (tick: number | number[], offset?: number) => boolean;
|
||||||
|
dur: (n: number | number[]) => boolean;
|
||||||
|
flip: (chunk: number, ratio?: number) => boolean;
|
||||||
|
flipbar: (chunk?: number) => boolean;
|
||||||
|
onbar: (bars: number | number[], n?: number) => boolean;
|
||||||
|
onbeat: (...beat: number[]) => boolean;
|
||||||
|
oncount: (beats: number | number[], count: number) => boolean;
|
||||||
|
oneuclid: (pulses: number, length: number, rotate?: number) => boolean;
|
||||||
|
euclid: (iterator: number, pulses: number, length: number, rotate?: number) => boolean;
|
||||||
|
ec: any;
|
||||||
|
rhythm: (div: number, pulses: number, length: number, rotate?: number) => boolean;
|
||||||
|
ry: any;
|
||||||
|
nrhythm: (div: number, pulses: number, length: number, rotate?: number) => boolean;
|
||||||
|
nry: any;
|
||||||
|
bin: (iterator: number, n: number) => boolean;
|
||||||
|
binrhythm: (div: number, n: number) => boolean;
|
||||||
|
bry: any;
|
||||||
|
line: any;
|
||||||
|
sine: any;
|
||||||
|
usine: any;
|
||||||
|
saw: any;
|
||||||
|
usaw: any;
|
||||||
|
triangle: any;
|
||||||
|
utriangle: any;
|
||||||
|
square: any;
|
||||||
|
usquare: any;
|
||||||
|
noise: any;
|
||||||
|
unoise: any;
|
||||||
|
prob: (p: number) => boolean;
|
||||||
|
toss: () => boolean;
|
||||||
|
odds: (n: number, beats?: number) => boolean;
|
||||||
|
never: (beats?: number) => boolean;
|
||||||
|
almostNever: (beats?: number) => boolean;
|
||||||
|
rarely: (beats?: number) => boolean;
|
||||||
|
scarcely: (beats?: number) => boolean;
|
||||||
|
sometimes: (beats?: number) => boolean;
|
||||||
|
often: (beats?: number) => boolean;
|
||||||
|
frequently: (beats?: number) => boolean;
|
||||||
|
almostAlways: (beats?: number) => boolean;
|
||||||
|
always: (beats?: number) => boolean;
|
||||||
|
dice: (sides: number) => number;
|
||||||
|
osc: (address: string, port: number, ...args: any[]) => void;
|
||||||
|
getOSC: (address?: string | undefined) => any[];
|
||||||
|
gif: (options: any) => void;
|
||||||
|
scope: (config: OscilloscopeConfig) => void;
|
||||||
|
randI: any;
|
||||||
|
rand: any;
|
||||||
|
ir: any;
|
||||||
|
irand: any;
|
||||||
|
r: any;
|
||||||
|
seed: any;
|
||||||
|
localSeededRandom: any;
|
||||||
|
clearLocalSeed: any;
|
||||||
|
once: () => boolean;
|
||||||
|
counter: (name: string | number, limit?: number | undefined, step?: number | undefined) => number;
|
||||||
|
$: any;
|
||||||
|
count: any;
|
||||||
|
i: (n?: number | undefined) => any;
|
||||||
|
sound: (sound: string | string[] | null | undefined) => SoundEvent | SkipEvent;
|
||||||
|
snd: any;
|
||||||
|
log: (message: any) => void;
|
||||||
|
logOnce: (message: any) => void;
|
||||||
|
speak: (text: string, lang?: string, voiceIndex?: number, rate?: number, pitch?: number) => void;
|
||||||
|
cbar: () => number;
|
||||||
|
ctick: () => number;
|
||||||
|
cpulse: () => number;
|
||||||
|
cbeat: () => number;
|
||||||
|
ebeat: () => number;
|
||||||
|
epulse: () => number;
|
||||||
|
nominator: () => number;
|
||||||
|
meter: () => number;
|
||||||
|
denominator: () => number;
|
||||||
|
pulsesForBar: () => number;
|
||||||
|
z0!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z1!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z2!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z3!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z4!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z5!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z6!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z7!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z8!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z9!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z10!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z11!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z12!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z13!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z14!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z15!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
z16!: (input: string | Generator<number>, options: InputOptions, id: number | string) => Player;
|
||||||
|
|
||||||
|
constructor(public app: Editor) {
|
||||||
|
this.MidiConnection = new MidiConnection(this, app.settings);
|
||||||
|
this.global = {};
|
||||||
|
this.g = this.global;
|
||||||
|
this.time = Transport.time(this);
|
||||||
|
this.play = Transport.play(this);
|
||||||
|
this.pause = Transport.pause(this);
|
||||||
|
this.stop = Transport.stop(this);
|
||||||
|
this.silence = Transport.silence(this);
|
||||||
|
this.tempo = Transport.tempo(this.app);
|
||||||
|
this.ppqn = Transport.ppqn(this.app);
|
||||||
|
this.time_signature = Transport.time_signature(this.app);
|
||||||
|
this.mouseX = Mouse.mouseX(this.app);
|
||||||
|
this.mouseY = Mouse.mouseY(this.app);
|
||||||
|
this.noteX = Mouse.noteX(this.app);
|
||||||
|
this.noteY = Mouse.noteY(this.app);
|
||||||
|
this.theme = Theme.theme(this.app);
|
||||||
|
this.themeName = Theme.themeName(this.app);
|
||||||
|
this.randomTheme = Theme.randomTheme(this.app);
|
||||||
|
this.nextTheme = Theme.nextTheme(this.app);
|
||||||
|
this.getThemes = Theme.getThemes();
|
||||||
|
this.pulseLocation = Canvas.pulseLocation(this.app);
|
||||||
|
this.loadHydra = Canvas.loadHydra(this.app);
|
||||||
|
this.clear = Canvas.clear(this.app);
|
||||||
|
this.w = Canvas.w(this.app);
|
||||||
|
this.h = Canvas.h(this.app);
|
||||||
|
this.hc = Canvas.hc(this.app);
|
||||||
|
this.wc = Canvas.wc(this.app);
|
||||||
|
this.background = Canvas.background(this.app);
|
||||||
|
this.linearGradient = Canvas.linearGradient(this.app);
|
||||||
|
this.radialGradient = Canvas.radialGradient(this.app);
|
||||||
|
this.conicGradient = Canvas.conicGradient(this.app);
|
||||||
|
this.draw = Canvas.draw(this.app);
|
||||||
|
this.balloid = Canvas.balloid(this.app);
|
||||||
|
this.equilateral = Canvas.equilateral(this.app);
|
||||||
|
this.triangular = Canvas.triangular(this.app);
|
||||||
|
this.ball = Canvas.ball(this.app);
|
||||||
|
this.circle = Canvas.circle(this.app);
|
||||||
|
this.donut = Canvas.donut(this.app);
|
||||||
|
this.pie = Canvas.pie(this.app);
|
||||||
|
this.star = Canvas.star(this.app);
|
||||||
|
this.stroke = Canvas.stroke(this.app);
|
||||||
|
this.box = Canvas.box(this.app);
|
||||||
|
this.smiley = Canvas.smiley(this.app);
|
||||||
|
this.text = Canvas.text(this.app);
|
||||||
|
this.image = Canvas.image(this.app);
|
||||||
|
this.randomChar = Canvas.randomChar();
|
||||||
|
this.randomFromRange = Canvas.randomFromRange();
|
||||||
|
this.emoji = Canvas.emoji();
|
||||||
|
this.food = Canvas.food();
|
||||||
|
this.animals = Canvas.animals();
|
||||||
|
this.expressions = Canvas.expressions();
|
||||||
|
this.generateCacheKey = Cache.generateCacheKey();
|
||||||
|
this.resetAllFromCache = Cache.resetAllFromCache(this);
|
||||||
|
this.clearPatternCache = Cache.clearPatternCache(this);
|
||||||
|
this.removePatternFromCache = Cache.removePatternFromCache(this);
|
||||||
|
this.script = Script.script(this.app);
|
||||||
|
this.s = this.script;
|
||||||
|
this.delete_script = Script.delete_script(this.app);
|
||||||
|
this.cs = this.delete_script;
|
||||||
|
this.copy_script = Script.copy_script(this.app);
|
||||||
|
this.cps = this.copy_script;
|
||||||
|
this.copy_universe = Script.copy_universe(this.app);
|
||||||
|
this.delete_universe = Script.delete_universe(this.app);
|
||||||
|
this.big_bang = Script.big_bang(this.app);
|
||||||
|
this.drunk = Drunk.drunk(this);
|
||||||
|
this.drunk_max = Drunk.drunk_max(this);
|
||||||
|
this.drunk_min = Drunk.drunk_min(this);
|
||||||
|
this.drunk_wrap = Drunk.drunk_wrap(this);
|
||||||
|
this.warp = Warp.warp(this.app);
|
||||||
|
this.beat_warp = Warp.beat_warp(this.app);
|
||||||
|
this.min = Mathematics.min();
|
||||||
|
this.max = Mathematics.max();
|
||||||
|
this.mean = Mathematics.mean();
|
||||||
|
this.limit = Mathematics.limit();
|
||||||
|
this.abs = Mathematics.abs();
|
||||||
|
this.z = Ziffers.z(this);
|
||||||
|
Object.assign(this, Ziffers.generateZFunctions(this));
|
||||||
|
this.fullseq = Filters.fullseq();
|
||||||
|
this.seq = Filters.seq(this.app);
|
||||||
|
this.beat = Filters.beat(this.app);
|
||||||
|
this.bar = Filters.bar(this.app);
|
||||||
|
this.pulse = Filters.pulse(this.app);
|
||||||
|
this.tick = Filters.tick(this.app);
|
||||||
|
this.dur = Filters.dur(this.app);
|
||||||
|
this.flip = Filters.flip(this.app);
|
||||||
|
this.flipbar = Filters.flipbar(this.app);
|
||||||
|
this.onbar = Filters.onbar(this.app);
|
||||||
|
this.onbeat = Filters.onbeat(this);
|
||||||
|
this.oncount = Filters.oncount(this.app);
|
||||||
|
this.oneuclid = Filters.oneuclid(this.app);
|
||||||
|
this.euclid = Filters.euclid();
|
||||||
|
this.ec = this.euclid;
|
||||||
|
this.rhythm = Filters.rhythm(this.app);
|
||||||
|
this.ry = this.rhythm;
|
||||||
|
this.nrhythm = Filters.nrhythm(this.app);
|
||||||
|
this.nry = this.nrhythm;
|
||||||
|
this.bin = Filters.bin();
|
||||||
|
this.binrhythm = Filters.binrhythm(this.app);
|
||||||
|
this.bry = this.binrhythm;
|
||||||
|
this.line = LFO.line();
|
||||||
|
this.sine = LFO.sine(this.app);
|
||||||
|
this.usine = LFO.usine(this.app);
|
||||||
|
this.saw = LFO.saw(this.app);
|
||||||
|
this.usaw = LFO.usaw(this.app);
|
||||||
|
this.triangle = LFO.triangle(this.app);
|
||||||
|
this.utriangle = LFO.utriangle(this.app);
|
||||||
|
this.square = LFO.square(this.app);
|
||||||
|
this.usquare = LFO.usquare(this.app);
|
||||||
|
this.noise = LFO.noise(this);
|
||||||
|
this.unoise = LFO.unoise(this);
|
||||||
|
this.prob = Probability.prob(this);
|
||||||
|
this.toss = Probability.toss(this);
|
||||||
|
this.odds = Probability.odds(this);
|
||||||
|
this.never = Probability.never();
|
||||||
|
this.almostNever = Probability.almostNever(this);
|
||||||
|
this.rarely = Probability.rarely(this);
|
||||||
|
this.scarcely = Probability.scarcely(this);
|
||||||
|
this.sometimes = Probability.sometimes(this);
|
||||||
|
this.often = Probability.often(this);
|
||||||
|
this.frequently = Probability.frequently(this);
|
||||||
|
this.almostAlways = Probability.almostAlways(this);
|
||||||
|
this.always = Probability.always();
|
||||||
|
this.dice = Probability.dice(this);
|
||||||
|
this.osc = OSC.osc(this.app);
|
||||||
|
this.getOSC = OSC.getOSC();
|
||||||
|
this.gif = Canvas.gif(this.app);
|
||||||
|
this.scope = Canvas.scope(this.app);
|
||||||
|
this.randI = Randomness.randI(this);
|
||||||
|
this.ir = this.randI;
|
||||||
|
this.irand = this.randI;
|
||||||
|
this.rand = Randomness.rand(this);
|
||||||
|
this.r = this.rand;
|
||||||
|
this.seed = Randomness.seed(this);
|
||||||
|
this.localSeededRandom = Randomness.localSeededRandom(this);
|
||||||
|
this.clearLocalSeed = Randomness.clearLocalSeed(this);
|
||||||
|
this.once = Counter.once(this);
|
||||||
|
this.counter = Counter.counter(this);
|
||||||
|
this.$ = this.counter;
|
||||||
|
this.count = this.counter;
|
||||||
|
this.i = Counter.i(this.app);
|
||||||
|
this.sound = Sound.sound(this.app);
|
||||||
|
this.snd = this.sound;
|
||||||
|
this.speak = Sound.speak();
|
||||||
|
this.log = Console.log(this);
|
||||||
|
this.logOnce = Console.logOnce(this);
|
||||||
|
this.cbar = Transport.cbar(this.app);
|
||||||
|
this.ctick = Transport.ctick(this.app);
|
||||||
|
this.cpulse = Transport.cpulse(this.app);
|
||||||
|
this.cbeat = Transport.cbeat(this.app);
|
||||||
|
this.ebeat = Transport.ebeat(this.app);
|
||||||
|
this.epulse = Transport.epulse(this.app);
|
||||||
|
this.nominator = Transport.nominator(this.app);
|
||||||
|
this.meter = Transport.meter(this.app);
|
||||||
|
this.denominator = Transport.denominator(this.app);
|
||||||
|
this.pulsesForBar = Transport.pulsesForBar(this.app);
|
||||||
|
}
|
||||||
|
|
||||||
|
public g: any;
|
||||||
|
|
||||||
|
_loadUniverseFromInterface = (universe: string) => {
|
||||||
|
this.app.selected_universe = universe.trim();
|
||||||
|
this.app.settings.selected_universe = universe.trim();
|
||||||
|
loadUniverse(this.app, universe as string);
|
||||||
|
openUniverseModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
_deleteUniverseFromInterface = (universe: string) => {
|
||||||
|
delete this.app.universes[universe];
|
||||||
|
if (this.app.settings.selected_universe === universe) {
|
||||||
|
this.app.settings.selected_universe = "Welcome";
|
||||||
|
this.app.selected_universe = "Welcome";
|
||||||
|
}
|
||||||
|
this.app.settings.saveApplicationToLocalStorage(
|
||||||
|
this.app.universes,
|
||||||
|
this.app.settings,
|
||||||
|
);
|
||||||
|
this.app.updateKnownUniversesView();
|
||||||
|
};
|
||||||
|
|
||||||
|
_playDocExample = (code?: string) => {
|
||||||
|
/**
|
||||||
|
* Play an example from the documentation. The example is going
|
||||||
|
* to be stored in the example buffer belonging to the universe.
|
||||||
|
* This buffer is going to be cleaned everytime the user press
|
||||||
|
* pause or leaves the documentation window.
|
||||||
|
*
|
||||||
|
* @param code - The code example to play (identifier)
|
||||||
|
*/
|
||||||
|
let current_universe = this.app.universes[this.app.selected_universe]!;
|
||||||
|
this.app.exampleIsPlaying = true;
|
||||||
|
if (!current_universe.example) {
|
||||||
|
current_universe.example = {
|
||||||
|
candidate: "",
|
||||||
|
committed: "",
|
||||||
|
evaluations: 0,
|
||||||
|
};
|
||||||
|
current_universe.example.candidate! = code
|
||||||
|
? code
|
||||||
|
: (this.app.selectedExample as string);
|
||||||
|
} else {
|
||||||
|
current_universe.example.candidate! = code
|
||||||
|
? code
|
||||||
|
: (this.app.selectedExample as string);
|
||||||
|
}
|
||||||
|
this.patternCache.clear();
|
||||||
|
if (this.app.isPlaying) {
|
||||||
|
} else {
|
||||||
|
this.app.clock.resume();
|
||||||
|
updatePlayPauseIcon(this.app, "play");
|
||||||
|
this.app.api.MidiConnection.sendStartMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// this.app.clock.start();
|
||||||
|
};
|
||||||
|
|
||||||
|
_stopDocExample = () => {
|
||||||
|
let current_universe = this.app.universes[this.app.selected_universe];
|
||||||
|
if (current_universe?.example !== undefined) {
|
||||||
|
this.app.exampleIsPlaying = false;
|
||||||
|
current_universe.example.candidate! = "";
|
||||||
|
current_universe.example.committed! = "";
|
||||||
|
}
|
||||||
|
this.clearPatternCache();
|
||||||
|
this.stop();
|
||||||
|
};
|
||||||
|
|
||||||
|
_playDocExampleOnce = (code?: string) => {
|
||||||
|
let current_universe = this.app.universes[this.app.selected_universe];
|
||||||
|
if (current_universe?.example !== undefined) {
|
||||||
|
current_universe.example.candidate! = "";
|
||||||
|
current_universe.example.committed! = "";
|
||||||
|
}
|
||||||
|
this.clearPatternCache();
|
||||||
|
this.stop();
|
||||||
|
this.play();
|
||||||
|
this.app.exampleIsPlaying = true;
|
||||||
|
evaluateOnce(this.app, code as string);
|
||||||
|
};
|
||||||
|
|
||||||
|
_all_samples = (): object => {
|
||||||
|
return soundMap.get();
|
||||||
|
};
|
||||||
|
|
||||||
|
_reportError = (error: any): void => {
|
||||||
|
const extractLineAndColumn = (error: Error) => {
|
||||||
|
const stackLines = error.stack?.split("\n");
|
||||||
|
if (stackLines) {
|
||||||
|
for (const line of stackLines) {
|
||||||
|
if (line.includes("<anonymous>")) {
|
||||||
|
const match = line.match(/<anonymous>:(\d+):(\d+)/);
|
||||||
|
if (match as RegExpMatchArray)
|
||||||
|
return {
|
||||||
|
// @ts-ignore
|
||||||
|
line: parseInt(match[1], 10),
|
||||||
|
// @ts-ignore
|
||||||
|
column: parseInt(match[2]!, 10),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { line: null, column: null };
|
||||||
|
};
|
||||||
|
|
||||||
|
const { line, column } = extractLineAndColumn(error);
|
||||||
|
const errorMessage =
|
||||||
|
line && column
|
||||||
|
? `${error.message} (Line: ${line - 2}, Column: ${column})`
|
||||||
|
: error.message;
|
||||||
|
|
||||||
|
clearTimeout(this.errorTimeoutID);
|
||||||
|
clearTimeout(this.printTimeoutID);
|
||||||
|
this.app.interface.error_line.innerHTML = errorMessage;
|
||||||
|
this.app.interface.error_line.style.color = "red";
|
||||||
|
this.app.interface.error_line.classList.remove("hidden");
|
||||||
|
// @ts-ignore
|
||||||
|
this.errorTimeoutID = setTimeout(
|
||||||
|
() => this.app.interface.error_line.classList.add("hidden"),
|
||||||
|
2000,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
_logMessage = (message: any, error: boolean = false): void => {
|
||||||
|
console.log(message);
|
||||||
|
clearTimeout(this.printTimeoutID);
|
||||||
|
clearTimeout(this.errorTimeoutID);
|
||||||
|
this.app.interface.error_line.innerHTML = message as string;
|
||||||
|
this.app.interface.error_line.style.color = error ? "red" : "white";
|
||||||
|
this.app.interface.error_line.classList.remove("hidden");
|
||||||
|
// @ts-ignore
|
||||||
|
this.printTimeoutID = setTimeout(
|
||||||
|
() => this.app.interface.error_line.classList.add("hidden"),
|
||||||
|
4000,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// =============================================================
|
||||||
|
// Quantification functions
|
||||||
|
// =============================================================
|
||||||
|
|
||||||
|
public quantize = (value: number, quantization: number[]): number => {
|
||||||
|
/**
|
||||||
|
* Returns the closest value in an array to a given value.
|
||||||
|
*
|
||||||
|
* @param value - The value to quantize
|
||||||
|
* @param quantization - The array of values to quantize to
|
||||||
|
* @returns The closest value in the array to the given value
|
||||||
|
*/
|
||||||
|
if (quantization.length === 0) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
let closest: number | undefined = quantization[0];
|
||||||
|
quantization.forEach((q) => {
|
||||||
|
if (Math.abs(q - value) < Math.abs(closest! - value)) {
|
||||||
|
closest = q;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return closest!;
|
||||||
|
};
|
||||||
|
quant = this.quantize;
|
||||||
|
|
||||||
|
public clamp = (value: number, min: number, max: number): number => {
|
||||||
|
/**
|
||||||
|
* Returns a value clamped between min and max.
|
||||||
|
*
|
||||||
|
* @param value - The value to clamp
|
||||||
|
* @param min - The minimum value of the clamped value
|
||||||
|
* @param max - The maximum value of the clamped value
|
||||||
|
* @returns A value clamped between min and max
|
||||||
|
*/
|
||||||
|
return Math.min(Math.max(value, min), max);
|
||||||
|
};
|
||||||
|
cmp = this.clamp;
|
||||||
|
|
||||||
|
// =============================================================
|
||||||
|
// Time markers
|
||||||
|
// =============================================================
|
||||||
|
|
||||||
|
// =============================================================
|
||||||
|
// Fill
|
||||||
|
// =============================================================
|
||||||
|
|
||||||
|
public fill = (): boolean => this.app.fill;
|
||||||
|
|
||||||
|
scale = getScaleNotes;
|
||||||
|
nearScales = nearScales;
|
||||||
|
|
||||||
|
public cue = (functionName: string | Function): void => {
|
||||||
|
functionName = typeof functionName === "function" ? functionName.name : functionName;
|
||||||
|
this.cueTimes[functionName] = this.app.clock.grain;
|
||||||
|
};
|
||||||
|
|
||||||
|
onmousemove = (e: MouseEvent) => {
|
||||||
|
this.app._mouseX = e.pageX;
|
||||||
|
this.app._mouseY = e.pageY;
|
||||||
|
};
|
||||||
|
}
|
||||||
61
src/API/Cache.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { isGenerator, isGeneratorFunction, maybeToNumber } from "../Utils/Generic";
|
||||||
|
import { type Player } from "../Classes/ZPlayer";
|
||||||
|
import { type UserAPI } from "./API";
|
||||||
|
|
||||||
|
|
||||||
|
export const generateCacheKey = () => (...args: any[]): string => {
|
||||||
|
return args.map((arg) => JSON.stringify(arg)).join(",");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resetAllFromCache = (api: UserAPI) => (): void => {
|
||||||
|
api.patternCache.forEach((player) => (player as Player).reset());
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clearPatternCache = (api: UserAPI) => (): void => {
|
||||||
|
api.patternCache.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removePatternFromCache = (api: UserAPI) => (id: string): void => {
|
||||||
|
api.patternCache.delete(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const cache = (api: UserAPI) => (key: string, value: any) => {
|
||||||
|
if (value !== undefined) {
|
||||||
|
if (isGenerator(value)) {
|
||||||
|
if (api.patternCache.has(key)) {
|
||||||
|
const cachedValue = (api.patternCache.get(key) as Generator<any>).next().value;
|
||||||
|
if (cachedValue !== 0 && !cachedValue) {
|
||||||
|
const generator = value as unknown as Generator<any>;
|
||||||
|
api.patternCache.set(key, generator);
|
||||||
|
return maybeToNumber(generator.next().value);
|
||||||
|
}
|
||||||
|
return maybeToNumber(cachedValue);
|
||||||
|
} else {
|
||||||
|
const generator = value as unknown as Generator<any>;
|
||||||
|
api.patternCache.set(key, generator);
|
||||||
|
return maybeToNumber(generator.next().value);
|
||||||
|
}
|
||||||
|
} else if (isGeneratorFunction(value)) {
|
||||||
|
if (api.patternCache.has(key)) {
|
||||||
|
const cachedValue = (api.patternCache.get(key) as Generator<any>).next().value;
|
||||||
|
if (cachedValue || cachedValue === 0 || cachedValue === 0n) {
|
||||||
|
return maybeToNumber(cachedValue);
|
||||||
|
} else {
|
||||||
|
const generator = value();
|
||||||
|
api.patternCache.set(key, generator);
|
||||||
|
return maybeToNumber(generator.next().value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const generator = value();
|
||||||
|
api.patternCache.set(key, generator);
|
||||||
|
return maybeToNumber(generator.next().value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
api.patternCache.set(key, value);
|
||||||
|
return maybeToNumber(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return maybeToNumber(api.patternCache.get(key));
|
||||||
|
}
|
||||||
|
};
|
||||||
43
src/API/Counter.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { type UserAPI } from "./API";
|
||||||
|
import { type Editor } from "../main";
|
||||||
|
|
||||||
|
export const once = (api: UserAPI) => (): boolean => {
|
||||||
|
const firstTime = api.onceEvaluator;
|
||||||
|
api.onceEvaluator = false;
|
||||||
|
return firstTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const counter = (api: UserAPI) => (name: string | number, limit?: number, step?: number): number => {
|
||||||
|
if (!(name in api.counters)) {
|
||||||
|
api.counters[name] = {
|
||||||
|
value: 0,
|
||||||
|
step: step ?? 1,
|
||||||
|
limit,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if (api.counters[name].limit !== limit) {
|
||||||
|
api.counters[name].value = 0;
|
||||||
|
api.counters[name].limit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (api.counters[name].step !== step) {
|
||||||
|
api.counters[name].step = step ?? api.counters[name].step;
|
||||||
|
}
|
||||||
|
|
||||||
|
api.counters[name].value += api.counters[name].step;
|
||||||
|
|
||||||
|
if (api.counters[name].limit !== undefined && api.counters[name].value > api.counters[name].limit) {
|
||||||
|
api.counters[name].value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.counters[name].value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const i = (app: Editor) => (n?: number) => {
|
||||||
|
if (n !== undefined) {
|
||||||
|
app.universes[app.selected_universe]!.global.evaluations = n;
|
||||||
|
return app.universes[app.selected_universe];
|
||||||
|
}
|
||||||
|
return app.universes[app.selected_universe]!.global.evaluations as number;
|
||||||
|
};
|
||||||
452
src/API/DOM/Canvas.ts
Normal file
@ -0,0 +1,452 @@
|
|||||||
|
import { OscilloscopeConfig } from "../../DOM/Visuals/Oscilloscope";
|
||||||
|
import { createConicGradient, createLinearGradient, createRadialGradient, drawBackground, drawBox, drawBall, drawBalloid, drawDonut, drawEquilateral, drawImage, drawPie, drawSmiley, drawStar, drawStroke, drawText, drawTriangular } from "../../DOM/Visuals/CanvasVisuals";
|
||||||
|
import { Editor } from "../../main";
|
||||||
|
|
||||||
|
export type ShapeObject = {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
x1: number;
|
||||||
|
y1: number;
|
||||||
|
x2: number;
|
||||||
|
y2: number;
|
||||||
|
radius: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
fillStyle: string;
|
||||||
|
secondary: string;
|
||||||
|
strokeStyle: string;
|
||||||
|
rotation: number;
|
||||||
|
points: number;
|
||||||
|
outerRadius: number;
|
||||||
|
eyeSize: number;
|
||||||
|
happiness: number;
|
||||||
|
slices: number;
|
||||||
|
gap: number;
|
||||||
|
font: string;
|
||||||
|
fontSize: number;
|
||||||
|
text: string;
|
||||||
|
filter: string;
|
||||||
|
url: string;
|
||||||
|
curve: number;
|
||||||
|
curves: number;
|
||||||
|
stroke: string;
|
||||||
|
eaten: number;
|
||||||
|
hole: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const loadHydra = (app: Editor) => (): void => {
|
||||||
|
app.api.log("Hydra is now loaded!")
|
||||||
|
app.ensureHydraLoaded()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const w = (app: Editor) => (): number => {
|
||||||
|
const canvas: HTMLCanvasElement = app.interface["feedback"] as HTMLCanvasElement;
|
||||||
|
return canvas.clientWidth;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pulseLocation = (app: Editor) => (): number => {
|
||||||
|
return ((app.api.epulse() / app.api.pulsesForBar()) * w(app)()) % w(app)();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clear = (app: Editor) => (): boolean => {
|
||||||
|
const canvas: HTMLCanvasElement = app.interface["feedback"] as HTMLCanvasElement;
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const h = (app: Editor) => (): number => {
|
||||||
|
const canvas: HTMLCanvasElement = app.interface["feedback"] as HTMLCanvasElement;
|
||||||
|
return canvas.clientHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hc = (app: Editor) => (): number => {
|
||||||
|
return h(app)() / 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const wc = (app: Editor) => (): number => {
|
||||||
|
return w(app)() / 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const background = (app: Editor) => (color: string | number, ...gb: number[]): boolean => {
|
||||||
|
drawBackground(app.interface["feedback"] as HTMLCanvasElement, color, ...gb);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
export const bg = background;
|
||||||
|
|
||||||
|
export const linearGradient = (app: Editor) => (x1: number, y1: number, x2: number, y2: number, ...stops: (number | string)[]): CanvasGradient => {
|
||||||
|
return createLinearGradient(app.interface["feedback"] as HTMLCanvasElement, x1, y1, x2, y2, ...stops);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const radialGradient = (app: Editor) => (x1: number, y1: number, r1: number, x2: number, y2: number, r2: number, ...stops: (number | string)[]) => {
|
||||||
|
return createRadialGradient(app.interface["feedback"] as HTMLCanvasElement, x1, y1, r1, x2, y2, r2, ...stops);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const conicGradient = (app: Editor) => (x: number, y: number, angle: number, ...stops: (number | string)[]) => {
|
||||||
|
return createConicGradient(app.interface["feedback"] as HTMLCanvasElement, x, y, angle, ...stops);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const draw = (app: Editor) => (func: Function): boolean => {
|
||||||
|
if (typeof func === "string") {
|
||||||
|
drawText(app.interface["feedback"] as HTMLCanvasElement, func, 24, 0, "Arial", wc(app)(), hc(app)(), "white", "none");
|
||||||
|
} else {
|
||||||
|
const canvas: HTMLCanvasElement = app.interface["feedback"] as HTMLCanvasElement;
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
func(ctx);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Additional drawing and utility functions in canvas.ts
|
||||||
|
export const balloid = (app: Editor) => (
|
||||||
|
curves: number | ShapeObject = 6,
|
||||||
|
radius: number = hc(app)() / 2,
|
||||||
|
curve: number = 1.5,
|
||||||
|
fillStyle: string = "white",
|
||||||
|
secondary: string = "black",
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
): boolean => {
|
||||||
|
if (typeof curves === "object") {
|
||||||
|
fillStyle = curves.fillStyle || "white";
|
||||||
|
x = curves.x || wc(app)();
|
||||||
|
y = curves.y || hc(app)();
|
||||||
|
curve = curves.curve || 1.5;
|
||||||
|
radius = curves.radius || hc(app)() / 2;
|
||||||
|
curves = curves.curves || 6;
|
||||||
|
}
|
||||||
|
drawBalloid(app.interface["feedback"] as HTMLCanvasElement, curves, radius, curve, fillStyle, secondary, x, y);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const equilateral = (app: Editor) => (
|
||||||
|
radius: number | ShapeObject = hc(app)() / 3,
|
||||||
|
fillStyle: string = "white",
|
||||||
|
rotation: number = 0,
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
): boolean => {
|
||||||
|
if (typeof radius === "object") {
|
||||||
|
fillStyle = radius.fillStyle || "white";
|
||||||
|
x = radius.x || wc(app)();
|
||||||
|
y = radius.y || hc(app)();
|
||||||
|
rotation = radius.rotation || 0;
|
||||||
|
radius = radius.radius || hc(app)() / 3;
|
||||||
|
}
|
||||||
|
drawEquilateral(app.interface["feedback"] as HTMLCanvasElement, radius, fillStyle, rotation, x, y);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const triangular = (app: Editor) => (
|
||||||
|
width: number | ShapeObject = hc(app)() / 3,
|
||||||
|
height: number = hc(app)() / 3,
|
||||||
|
fillStyle: string = "white",
|
||||||
|
rotation: number = 0,
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
): boolean => {
|
||||||
|
if (typeof width === "object") {
|
||||||
|
fillStyle = width.fillStyle || "white";
|
||||||
|
x = width.x || wc(app)();
|
||||||
|
y = width.y || hc(app)();
|
||||||
|
rotation = width.rotation || 0;
|
||||||
|
height = width.height || hc(app)() / 3;
|
||||||
|
width = width.width || hc(app)() / 3;
|
||||||
|
}
|
||||||
|
drawTriangular(app.interface['feedback'] as HTMLCanvasElement, width, height, fillStyle, rotation, x, y);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
export const pointy = triangular;
|
||||||
|
|
||||||
|
export const ball = (app: Editor) => (
|
||||||
|
radius: number | ShapeObject = hc(app)() / 3,
|
||||||
|
fillStyle: string = "white",
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
): boolean => {
|
||||||
|
if (typeof radius === "object") {
|
||||||
|
fillStyle = radius.fillStyle || "white";
|
||||||
|
x = radius.x || wc(app)();
|
||||||
|
y = radius.y || hc(app)();
|
||||||
|
radius = radius.radius || hc(app)() / 3;
|
||||||
|
}
|
||||||
|
drawBall(app.interface['feedback'] as HTMLCanvasElement, radius, fillStyle, x, y);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
export const circle = ball;
|
||||||
|
|
||||||
|
export const donut = (app: Editor) => (
|
||||||
|
slices: number | ShapeObject = 3,
|
||||||
|
eaten: number = 0,
|
||||||
|
radius: number = hc(app)() / 3,
|
||||||
|
hole: number = hc(app)() / 12,
|
||||||
|
fillStyle: string = "white",
|
||||||
|
secondary: string = "black",
|
||||||
|
stroke: string = "black",
|
||||||
|
rotation: number = 0,
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
): boolean => {
|
||||||
|
if (typeof slices === "object") {
|
||||||
|
fillStyle = slices.fillStyle || "white";
|
||||||
|
x = slices.x || wc(app)();
|
||||||
|
y = slices.y || hc(app)();
|
||||||
|
rotation = slices.rotation || 0;
|
||||||
|
radius = slices.radius || hc(app)() / 3;
|
||||||
|
eaten = slices.eaten || 0;
|
||||||
|
hole = slices.hole || hc(app)() / 12;
|
||||||
|
secondary = slices.secondary || "black";
|
||||||
|
stroke = slices.stroke || "black";
|
||||||
|
slices = slices.slices || 3;
|
||||||
|
}
|
||||||
|
drawDonut(app.interface['feedback'] as HTMLCanvasElement, slices, eaten, radius, hole, fillStyle, secondary, stroke, rotation, x, y);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pie = (app: Editor) => (
|
||||||
|
slices: number | ShapeObject = 3,
|
||||||
|
eaten: number = 0,
|
||||||
|
radius: number = hc(app)() / 3,
|
||||||
|
fillStyle: string = "white",
|
||||||
|
secondary: string = "black",
|
||||||
|
stroke: string = "black",
|
||||||
|
rotation: number = 0,
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
): boolean => {
|
||||||
|
if (typeof slices === "object") {
|
||||||
|
fillStyle = slices.fillStyle || "white";
|
||||||
|
x = slices.x || wc(app)();
|
||||||
|
y = slices.y || hc(app)();
|
||||||
|
rotation = slices.rotation || 0;
|
||||||
|
radius = slices.radius || hc(app)() / 3;
|
||||||
|
secondary = slices.secondary || "black";
|
||||||
|
stroke = slices.stroke || "black";
|
||||||
|
eaten = slices.eaten || 0;
|
||||||
|
slices = slices.slices || 3;
|
||||||
|
}
|
||||||
|
drawPie(app.interface['feedback'] as HTMLCanvasElement, slices, eaten, radius, fillStyle, secondary, stroke, rotation, x, y);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const star = (app: Editor) => (
|
||||||
|
points: number | ShapeObject = 5,
|
||||||
|
radius: number = hc(app)() / 3,
|
||||||
|
fillStyle: string = "white",
|
||||||
|
rotation: number = 0,
|
||||||
|
outerRadius: number = radius / 100,
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
): boolean => {
|
||||||
|
if (typeof points === "object") {
|
||||||
|
radius = points.radius || hc(app)() / 3;
|
||||||
|
fillStyle = points.fillStyle || "white";
|
||||||
|
x = points.x || wc(app)();
|
||||||
|
y = points.y || hc(app)();
|
||||||
|
rotation = points.rotation || 0;
|
||||||
|
outerRadius = points.outerRadius || radius / 100;
|
||||||
|
points = points.points || 5;
|
||||||
|
}
|
||||||
|
drawStar(app.interface['feedback'] as HTMLCanvasElement, points, radius, fillStyle, rotation, outerRadius, x, y);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stroke = (app: Editor) => (
|
||||||
|
width: number | ShapeObject = 1,
|
||||||
|
strokeStyle: string = "white",
|
||||||
|
rotation: number = 0,
|
||||||
|
x1: number = wc(app)() - wc(app)() / 10,
|
||||||
|
y1: number = hc(app)(),
|
||||||
|
x2: number = wc(app)() + wc(app)() / 5,
|
||||||
|
y2: number = hc(app)(),
|
||||||
|
): boolean => {
|
||||||
|
if (typeof width === "object") {
|
||||||
|
strokeStyle = width.strokeStyle || "white";
|
||||||
|
x1 = width.x1 || wc(app)() - wc(app)() / 10;
|
||||||
|
y1 = width.y1 || hc(app)();
|
||||||
|
x2 = width.x2 || wc(app)() + wc(app)() / 5;
|
||||||
|
y2 = width.y2 || hc(app)();
|
||||||
|
rotation = width.rotation || 0;
|
||||||
|
width = width.width || 1;
|
||||||
|
}
|
||||||
|
drawStroke(app.interface['feedback'] as HTMLCanvasElement, width, strokeStyle, rotation, x1, y1, x2, y2);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const box = (app: Editor) => (
|
||||||
|
width: number | ShapeObject = wc(app)() / 4,
|
||||||
|
height: number = wc(app)() / 4,
|
||||||
|
fillStyle: string = "white",
|
||||||
|
rotation: number = 0,
|
||||||
|
x: number = wc(app)() - wc(app)() / 8,
|
||||||
|
y: number = hc(app)() - hc(app)() / 8,
|
||||||
|
): boolean => {
|
||||||
|
if (typeof width === "object") {
|
||||||
|
fillStyle = width.fillStyle || "white";
|
||||||
|
x = width.x || wc(app)() - wc(app)() / 4;
|
||||||
|
y = width.y || hc(app)() - hc(app)() / 2;
|
||||||
|
rotation = width.rotation || 0;
|
||||||
|
height = width.height || wc(app)() / 4;
|
||||||
|
width = width.width || wc(app)() / 4;
|
||||||
|
}
|
||||||
|
drawBox(app.interface['feedback'] as HTMLCanvasElement, width, height, fillStyle, rotation, x, y);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const smiley = (app: Editor) => (
|
||||||
|
happiness: number | ShapeObject = 0,
|
||||||
|
radius: number = hc(app)() / 3,
|
||||||
|
eyeSize: number = 3.0,
|
||||||
|
fillStyle: string = "yellow",
|
||||||
|
rotation: number = 0,
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
): boolean => {
|
||||||
|
if (typeof happiness === "object") {
|
||||||
|
fillStyle = happiness.fillStyle || "yellow";
|
||||||
|
x = happiness.x || wc(app)();
|
||||||
|
y = happiness.y || hc(app)();
|
||||||
|
rotation = happiness.rotation || 0;
|
||||||
|
eyeSize = happiness.eyeSize || 3.0;
|
||||||
|
radius = happiness.radius || hc(app)() / 3;
|
||||||
|
happiness = happiness.happiness || 0;
|
||||||
|
}
|
||||||
|
drawSmiley(app.interface['feedback'] as HTMLCanvasElement, happiness, radius, eyeSize, fillStyle, rotation, x, y);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const text = (app: Editor) => (
|
||||||
|
text: string | ShapeObject,
|
||||||
|
fontSize: number = 24,
|
||||||
|
rotation: number = 0,
|
||||||
|
font: string = "Arial",
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
fillStyle: string = "white",
|
||||||
|
filter: string = "none",
|
||||||
|
): boolean => {
|
||||||
|
if (typeof text === "object") {
|
||||||
|
fillStyle = text.fillStyle || "white";
|
||||||
|
x = text.x || wc(app)();
|
||||||
|
y = text.y || hc(app)();
|
||||||
|
rotation = text.rotation || 0;
|
||||||
|
font = text.font || "Arial";
|
||||||
|
fontSize = text.fontSize || 24;
|
||||||
|
filter = text.filter || "none";
|
||||||
|
text = text.text || "";
|
||||||
|
}
|
||||||
|
drawText(app.interface['feedback'] as HTMLCanvasElement, text, fontSize, rotation, font, x, y, fillStyle, filter);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const image = (app: Editor) => (
|
||||||
|
url: string | ShapeObject,
|
||||||
|
width: number = wc(app)() / 2,
|
||||||
|
height: number = hc(app)() / 2,
|
||||||
|
rotation: number = 0,
|
||||||
|
x: number = wc(app)(),
|
||||||
|
y: number = hc(app)(),
|
||||||
|
filter: string = "none",
|
||||||
|
): boolean => {
|
||||||
|
if (typeof url === "object") {
|
||||||
|
if (!url.url) return true;
|
||||||
|
x = url.x || wc(app)();
|
||||||
|
y = url.y || hc(app)();
|
||||||
|
rotation = url.rotation || 0;
|
||||||
|
width = url.width || 100;
|
||||||
|
height = url.height || 100;
|
||||||
|
filter = url.filter || "none";
|
||||||
|
url = url.url || "";
|
||||||
|
}
|
||||||
|
drawImage(app.interface['feedback'] as HTMLCanvasElement, url, width, height, rotation, x, y, filter);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const randomChar = () => (length: number = 1, min: number = 0, max: number = 65536): string => {
|
||||||
|
return Array.from(
|
||||||
|
{ length }, () => String.fromCodePoint(Math.floor(Math.random() * (max - min) + min))
|
||||||
|
).join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const randomFromRange = () => (min: number, max: number): string => {
|
||||||
|
const codePoint = Math.floor(Math.random() * (max - min) + min);
|
||||||
|
return String.fromCodePoint(codePoint);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const emoji = () => (n: number = 1): string => {
|
||||||
|
return randomChar()(n, 0x1f600, 0x1f64f);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const food = () => (n: number = 1): string => {
|
||||||
|
return randomChar()(n, 0x1f32d, 0x1f37f);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const animals = () => (n: number = 1): string => {
|
||||||
|
return randomChar()(n, 0x1f400, 0x1f4d3);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const expressions = () => (n: number = 1): string => {
|
||||||
|
return randomChar()(n, 0x1f910, 0x1f92f);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const gif = (app: Editor) => (options: any): void => {
|
||||||
|
const {
|
||||||
|
url,
|
||||||
|
posX = 0,
|
||||||
|
posY = 0,
|
||||||
|
opacity = 1,
|
||||||
|
size = "auto",
|
||||||
|
center = false,
|
||||||
|
rotation = 0,
|
||||||
|
filter = 'none',
|
||||||
|
duration = 10
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
let real_duration = duration * app.clock.time_position.tick_duration * app.clock.ppqn;
|
||||||
|
let fadeOutDuration = real_duration * 0.1;
|
||||||
|
let visibilityDuration = real_duration - fadeOutDuration;
|
||||||
|
const gifElement = document.createElement("img");
|
||||||
|
gifElement.src = url;
|
||||||
|
gifElement.style.position = "fixed";
|
||||||
|
gifElement.style.left = center ? "50%" : `${posX}px`;
|
||||||
|
gifElement.style.top = center ? "50%" : `${posY}px`;
|
||||||
|
gifElement.style.opacity = `${opacity}`;
|
||||||
|
gifElement.style.zIndex = "1000"; // Ensure it's on top, fixed zIndex
|
||||||
|
if (size !== "auto") {
|
||||||
|
gifElement.style.width = size;
|
||||||
|
gifElement.style.height = size;
|
||||||
|
}
|
||||||
|
const transformRules = [`rotate(${rotation}deg)`];
|
||||||
|
if (center) {
|
||||||
|
transformRules.unshift("translate(-50%, -50%)");
|
||||||
|
}
|
||||||
|
gifElement.style.transform = transformRules.join(" ");
|
||||||
|
gifElement.style.filter = filter;
|
||||||
|
gifElement.style.transition = `opacity ${fadeOutDuration}s ease`;
|
||||||
|
document.body.appendChild(gifElement);
|
||||||
|
|
||||||
|
// Start the fade-out at the end of the visibility duration
|
||||||
|
setTimeout(() => {
|
||||||
|
gifElement.style.opacity = "0";
|
||||||
|
}, visibilityDuration * 1000);
|
||||||
|
|
||||||
|
// Remove the GIF from the DOM after the fade-out duration
|
||||||
|
setTimeout(() => {
|
||||||
|
if (document.body.contains(gifElement)) {
|
||||||
|
document.body.removeChild(gifElement);
|
||||||
|
}
|
||||||
|
}, real_duration * 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const scope = (app: Editor) => (config: OscilloscopeConfig): void => {
|
||||||
|
/**
|
||||||
|
* Configures the oscilloscope.
|
||||||
|
* @param config - The configuration object for the oscilloscope.
|
||||||
|
*/
|
||||||
|
app.osc = {
|
||||||
|
...app.osc,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
};
|
||||||
22
src/API/DOM/Console.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { type UserAPI } from "../API";
|
||||||
|
|
||||||
|
export const log = (api: UserAPI) => (message: any) => {
|
||||||
|
/**
|
||||||
|
* Logs a message to the console and app-specific logger.
|
||||||
|
* @param message - The message to log.
|
||||||
|
*/
|
||||||
|
console.log(message);
|
||||||
|
api._logMessage(message, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logOnce = (api: UserAPI) => (message: any) => {
|
||||||
|
/**
|
||||||
|
* Logs a message to the console and app-specific logger, but only once.
|
||||||
|
* @param message - The message to log.
|
||||||
|
*/
|
||||||
|
if (api.onceEvaluator) {
|
||||||
|
console.log(message);
|
||||||
|
api._logMessage(message, false);
|
||||||
|
api.onceEvaluator = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
29
src/API/DOM/Mouse.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Editor } from "../../main";
|
||||||
|
|
||||||
|
export const mouseX = (app: Editor) => (): number => {
|
||||||
|
/**
|
||||||
|
* @returns The current x position of the mouse
|
||||||
|
*/
|
||||||
|
return app._mouseX;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mouseY = (app: Editor) => (): number => {
|
||||||
|
/**
|
||||||
|
* @returns The current y position of the mouse
|
||||||
|
*/
|
||||||
|
return app._mouseY;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const noteX = (app: Editor) => (): number => {
|
||||||
|
/**
|
||||||
|
* @returns The current x position scaled to 0-127 using screen width
|
||||||
|
*/
|
||||||
|
return Math.floor((app._mouseX / document.body.clientWidth) * 127);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const noteY = (app: Editor) => (): number => {
|
||||||
|
/**
|
||||||
|
* @returns The current y position scaled to 0-127 using screen height
|
||||||
|
*/
|
||||||
|
return Math.floor((app._mouseY / document.body.clientHeight) * 127);
|
||||||
|
};
|
||||||
32
src/API/DOM/Theme.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { type Editor } from '../../main';
|
||||||
|
import colorschemes from "../../Editor/colors.json";
|
||||||
|
|
||||||
|
export const theme = (app: Editor) => (color_scheme: string): void => {
|
||||||
|
app.readTheme(color_scheme);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const themeName = (app: Editor) => (): string => {
|
||||||
|
return app.currentThemeName;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const randomTheme = (app: Editor) => (): void => {
|
||||||
|
let theme_names = getThemes()();
|
||||||
|
let selected_theme = theme_names[Math.floor(Math.random() * theme_names.length)];
|
||||||
|
if (selected_theme) {
|
||||||
|
app.readTheme(selected_theme);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const nextTheme = (app: Editor) => (): void => {
|
||||||
|
let theme_names = getThemes()();
|
||||||
|
let current_theme = themeName(app)();
|
||||||
|
let current_theme_idx = theme_names.indexOf(current_theme);
|
||||||
|
let next_theme_idx = (current_theme_idx + 1) % theme_names.length;
|
||||||
|
let next_theme = theme_names[next_theme_idx];
|
||||||
|
app.readTheme(next_theme!);
|
||||||
|
app.api.log(next_theme);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getThemes = () => (): string[] => {
|
||||||
|
return Object.keys(colorschemes);
|
||||||
|
};
|
||||||
39
src/API/Drunk.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { type UserAPI } from './API';
|
||||||
|
|
||||||
|
export const drunk = (api: UserAPI) => (n?: number): number => {
|
||||||
|
/**
|
||||||
|
* This function sets or returns the current drunk mechanism's value.
|
||||||
|
* @param n - [optional] The value to set the drunk mechanism to
|
||||||
|
* @returns The current value of the drunk mechanism
|
||||||
|
*/
|
||||||
|
if (n !== undefined) {
|
||||||
|
api._drunk.position = n;
|
||||||
|
return api._drunk.getPosition();
|
||||||
|
}
|
||||||
|
api._drunk.step();
|
||||||
|
return api._drunk.getPosition();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drunk_max = (api: UserAPI) => (max: number): void => {
|
||||||
|
/**
|
||||||
|
* Sets the maximum value of the drunk mechanism.
|
||||||
|
* @param max - The maximum value of the drunk mechanism
|
||||||
|
*/
|
||||||
|
api._drunk.max = max;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drunk_min = (api: UserAPI) => (min: number): void => {
|
||||||
|
/**
|
||||||
|
* Sets the minimum value of the drunk mechanism.
|
||||||
|
* @param min - The minimum value of the drunk mechanism
|
||||||
|
*/
|
||||||
|
api._drunk.min = min;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drunk_wrap = (api: UserAPI) => (wrap: boolean): void => {
|
||||||
|
/**
|
||||||
|
* Sets whether the drunk mechanism should wrap around
|
||||||
|
* @param wrap - Whether the drunk mechanism should wrap around
|
||||||
|
*/
|
||||||
|
api._drunk.toggleWrap(wrap);
|
||||||
|
};
|
||||||
198
src/API/IO/MIDI.ts
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
import { getAllScaleNotes } from 'zifferjs';
|
||||||
|
import {
|
||||||
|
MidiCCEvent,
|
||||||
|
MidiNoteEvent,
|
||||||
|
} from "../../IO/MidiConnection";
|
||||||
|
import { MidiEvent, MidiParams } from "../../Classes/MidiEvent";
|
||||||
|
import { UserAPI } from '../API';
|
||||||
|
import { Editor } from '../../main';
|
||||||
|
|
||||||
|
interface ControlChange {
|
||||||
|
channel: number;
|
||||||
|
control: number;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const midi_outputs = (api: UserAPI) => (): void => {
|
||||||
|
api._logMessage(api.MidiConnection.listMidiOutputs(), false);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const midi_output = (api: UserAPI) => (outputName: string): void => {
|
||||||
|
if (!outputName) {
|
||||||
|
console.log(api.MidiConnection.getCurrentMidiPort());
|
||||||
|
} else {
|
||||||
|
api.MidiConnection.switchMidiOutput(outputName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const midi = (app: Editor) => (
|
||||||
|
value: number | number[] = 60,
|
||||||
|
velocity?: number | number[],
|
||||||
|
channel?: number | number[],
|
||||||
|
port?: number | string | number[] | string[],
|
||||||
|
): MidiEvent => {
|
||||||
|
const event = { note: value, velocity, channel, port } as MidiParams;
|
||||||
|
return new MidiEvent(event, app);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sysex = (api: UserAPI) => (data: Array<number>): void => {
|
||||||
|
api.MidiConnection.sendSysExMessage(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pitch_bend = (api: UserAPI) => (value: number, channel: number): void => {
|
||||||
|
api.MidiConnection.sendPitchBend(value, channel);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const program_change = (api: UserAPI) => (program: number, channel: number): void => {
|
||||||
|
api.MidiConnection.sendProgramChange(program, channel);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const midi_clock = (api: UserAPI) => (): void => {
|
||||||
|
api.MidiConnection.sendMidiClock();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const control_change = (api: UserAPI) => ({
|
||||||
|
control = 20,
|
||||||
|
value = 0,
|
||||||
|
channel = 0,
|
||||||
|
}: ControlChange): void => {
|
||||||
|
api.MidiConnection.sendMidiControlChange(control, value, channel);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cc = control_change;
|
||||||
|
|
||||||
|
export const midi_panic = (api: UserAPI) => (): void => {
|
||||||
|
api.MidiConnection.panic();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const active_note_events = (api: UserAPI) => (
|
||||||
|
channel?: number,
|
||||||
|
): MidiNoteEvent[] | undefined => {
|
||||||
|
let events;
|
||||||
|
if (channel) {
|
||||||
|
events = api.MidiConnection.activeNotesFromChannel(channel);
|
||||||
|
} else {
|
||||||
|
events = api.MidiConnection.activeNotes;
|
||||||
|
}
|
||||||
|
if (events.length > 0) return events;
|
||||||
|
else return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const transmission = (api: UserAPI) => (): boolean => {
|
||||||
|
return api.MidiConnection.activeNotes.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const active_notes = (api: UserAPI) => (channel?: number): number[] | undefined => {
|
||||||
|
const events = active_note_events(api)(channel);
|
||||||
|
if (events && events.length > 0) return events.map((e) => e.note);
|
||||||
|
else return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const kill_active_notes = (api: UserAPI) => (): void => {
|
||||||
|
api.MidiConnection.activeNotes = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sticky_notes = (api: UserAPI) => (channel?: number): number[] | undefined => {
|
||||||
|
let notes;
|
||||||
|
if (channel) notes = api.MidiConnection.stickyNotesFromChannel(channel);
|
||||||
|
else notes = api.MidiConnection.stickyNotes;
|
||||||
|
if (notes.length > 0) return notes.map((e: any) => e.note);
|
||||||
|
else return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const kill_sticky_notes = (api: UserAPI) => (): void => {
|
||||||
|
api.MidiConnection.stickyNotes = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buffer = (api: UserAPI) => (channel?: number): boolean => {
|
||||||
|
if (channel)
|
||||||
|
return (
|
||||||
|
api.MidiConnection.findNoteFromBufferInChannel(channel) !== undefined
|
||||||
|
);
|
||||||
|
else return api.MidiConnection.noteInputBuffer.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buffer_event = (api: UserAPI) => (channel?: number): MidiNoteEvent | undefined => {
|
||||||
|
if (channel)
|
||||||
|
return api.MidiConnection.findNoteFromBufferInChannel(channel);
|
||||||
|
else return api.MidiConnection.noteInputBuffer.shift();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buffer_note = (api: UserAPI) => (channel?: number): number | undefined => {
|
||||||
|
const note = buffer_event(api)(channel);
|
||||||
|
return note ? note.note : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const last_note_event = (api: UserAPI) => (channel?: number): MidiNoteEvent | undefined => {
|
||||||
|
if (channel) return api.MidiConnection.lastNoteInChannel[channel];
|
||||||
|
else return api.MidiConnection.lastNote;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const last_note = (api: UserAPI) => (channel?: number): number => {
|
||||||
|
const note = last_note_event(api)(channel);
|
||||||
|
return note ? note.note : 60;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ccIn = (api: UserAPI) => (control: number, channel?: number): number => {
|
||||||
|
if (channel) {
|
||||||
|
if (api.MidiConnection.lastCCInChannel[channel]) {
|
||||||
|
return api.MidiConnection.lastCCInChannel[channel]?.[control] ?? 0;
|
||||||
|
} else return 0;
|
||||||
|
} else return api.MidiConnection.lastCC[control] ?? 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const has_cc = (api: UserAPI) => (channel?: number): boolean => {
|
||||||
|
if (channel)
|
||||||
|
return (
|
||||||
|
api.MidiConnection.findCCFromBufferInChannel(channel) !== undefined
|
||||||
|
);
|
||||||
|
else return api.MidiConnection.ccInputBuffer.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buffer_cc = (api: UserAPI) => (channel?: number): MidiCCEvent | undefined => {
|
||||||
|
if (channel) return api.MidiConnection.findCCFromBufferInChannel(channel);
|
||||||
|
else return api.MidiConnection.ccInputBuffer.shift();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const show_scale = (api: UserAPI) => (
|
||||||
|
root: number | string,
|
||||||
|
scale: number | string,
|
||||||
|
channel: number = 0,
|
||||||
|
port: number | string = api.MidiConnection.currentOutputIndex || 0,
|
||||||
|
soundOff: boolean = false,
|
||||||
|
): void => {
|
||||||
|
if (!api.scale_aid || scale !== api.scale_aid) {
|
||||||
|
hide_scale(api)(channel, port);
|
||||||
|
const scaleNotes = getAllScaleNotes(scale, root);
|
||||||
|
scaleNotes.forEach((note) => {
|
||||||
|
api.MidiConnection.sendMidiOn(note, channel, 1, port);
|
||||||
|
if (soundOff) api.MidiConnection.sendAllSoundOff(channel, port);
|
||||||
|
});
|
||||||
|
api.scale_aid = scale;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hide_scale = (api: UserAPI) => (
|
||||||
|
channel: number = 0,
|
||||||
|
port: number | string = api.MidiConnection.currentOutputIndex || 0,
|
||||||
|
): void => {
|
||||||
|
const allNotes = Array.from(Array(128).keys());
|
||||||
|
allNotes.forEach((note) => {
|
||||||
|
api.MidiConnection.sendMidiOff(note, channel, port);
|
||||||
|
});
|
||||||
|
api.scale_aid = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const midi_notes_off = (api: UserAPI) => (
|
||||||
|
channel: number = 0,
|
||||||
|
port: number | string = api.MidiConnection.currentOutputIndex || 0,
|
||||||
|
): void => {
|
||||||
|
api.MidiConnection.sendAllNotesOff(channel, port);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const midi_sound_off = (api: UserAPI) => (
|
||||||
|
channel: number = 0,
|
||||||
|
port: number | string = api.MidiConnection.currentOutputIndex || 0,
|
||||||
|
): void => {
|
||||||
|
api.MidiConnection.sendAllSoundOff(channel, port);
|
||||||
|
};
|
||||||
28
src/API/IO/OSC.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { sendToServer, type OSCMessage } from "../../IO/OSC";
|
||||||
|
import { oscMessages } from "../../IO/OSC";
|
||||||
|
import { type Editor } from "../../main";
|
||||||
|
|
||||||
|
export const osc = (app: Editor) => (address: string, port: number, ...args: any[]): void => {
|
||||||
|
/**
|
||||||
|
* Sends an OSC message to the server.
|
||||||
|
*/
|
||||||
|
sendToServer({
|
||||||
|
address: address,
|
||||||
|
port: port,
|
||||||
|
args: args,
|
||||||
|
timetag: Math.round(Date.now() - app.clock.getTimeDeviation()),
|
||||||
|
} as OSCMessage);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getOSC = () => (address?: string): any[] => {
|
||||||
|
/**
|
||||||
|
* Retrieves incoming OSC messages. Filters by address if provided.
|
||||||
|
*/
|
||||||
|
if (address) {
|
||||||
|
let messages = oscMessages.filter((msg: { address: string; }) => msg.address === address);
|
||||||
|
messages = messages.map((msg: { data: any; }) => msg.data);
|
||||||
|
return messages;
|
||||||
|
} else {
|
||||||
|
return oscMessages;
|
||||||
|
}
|
||||||
|
};
|
||||||
68
src/API/LFO.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { Editor } from "../main";
|
||||||
|
import { UserAPI } from "./API";
|
||||||
|
|
||||||
|
export const line = () => (start: number, end: number, step: number = 1): number[] => {
|
||||||
|
const countPlaces = (num: number) => {
|
||||||
|
var text = num.toString();
|
||||||
|
var index = text.indexOf(".");
|
||||||
|
return index == -1 ? 0 : (text.length - index - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const result: number[] = [];
|
||||||
|
|
||||||
|
if ((end > start && step > 0) || (end < start && step < 0)) {
|
||||||
|
for (let value = start; value <= end; value += step) {
|
||||||
|
result.push(value);
|
||||||
|
}
|
||||||
|
} else if ((end > start && step < 0) || (end < start && step > 0)) {
|
||||||
|
for (let value = start; value >= end; value -= step) {
|
||||||
|
result.push(parseFloat(value.toFixed(countPlaces(step))));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("Invalid range or step provided.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sine = (app: Editor) => (freq: number = 1, phase: number = 0): number => {
|
||||||
|
return Math.sin(2 * Math.PI * freq * (app.clock.ctx.currentTime - phase));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usine = (app: Editor) => (freq: number = 1, phase: number = 0): number => {
|
||||||
|
return ((sine(app)(freq, phase) + 1) / 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const saw = (app: Editor) => (freq: number = 1, phase: number = 0): number => {
|
||||||
|
return (((app.clock.ctx.currentTime * freq + phase) % 1) * 2 - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usaw = (app: Editor) => (freq: number = 1, phase: number = 0): number => {
|
||||||
|
return ((saw(app)(freq, phase) + 1) / 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const triangle = (app: Editor) => (freq: number = 1, phase: number = 0): number => {
|
||||||
|
return (Math.abs(saw(app)(freq, phase)) * 2 - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const utriangle = (app: Editor) => (freq: number = 1, phase: number = 0): number => {
|
||||||
|
return ((triangle(app)(freq, phase) + 1) / 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const square = (app: Editor) => (freq: number = 1, duty: number = 0.5): number => {
|
||||||
|
const period = 1 / freq;
|
||||||
|
const t = (app.clock.ctx.currentTime % period);
|
||||||
|
return (t / period < duty ? 1 : -1);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usquare = (app: Editor) => (freq: number = 1, duty: number = 0.5): number => {
|
||||||
|
return ((square(app)(freq, duty) + 1) / 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const noise = (api: UserAPI) => (): number => {
|
||||||
|
return (api.randomGen() * 2 - 1); // Assuming randomGen() is defined in the app context
|
||||||
|
};
|
||||||
|
|
||||||
|
export const unoise = (api: UserAPI) => (): number => {
|
||||||
|
return ((noise(api)() + 1) / 2);
|
||||||
|
};
|
||||||
36
src/API/Math.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// mathFunctions.ts
|
||||||
|
export const min = () => (...values: number[]): number => {
|
||||||
|
/**
|
||||||
|
* Returns the minimum value of a list of numbers.
|
||||||
|
*/
|
||||||
|
return Math.min(...values);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const max = () => (...values: number[]): number => {
|
||||||
|
/**
|
||||||
|
* Returns the maximum value of a list of numbers.
|
||||||
|
*/
|
||||||
|
return Math.max(...values);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mean = () => (...values: number[]): number => {
|
||||||
|
/**
|
||||||
|
* Returns the mean of a list of numbers.
|
||||||
|
*/
|
||||||
|
const sum = values.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
|
||||||
|
return values.length > 0 ? sum / values.length : 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const limit = () => (value: number, min: number, max: number): number => {
|
||||||
|
/**
|
||||||
|
* Limits a value between a minimum and a maximum.
|
||||||
|
*/
|
||||||
|
return Math.min(Math.max(value, min), max);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const abs = () => (value: number): number => {
|
||||||
|
/**
|
||||||
|
* Returns the absolute value of a number.
|
||||||
|
*/
|
||||||
|
return Math.abs(value);
|
||||||
|
};
|
||||||
53
src/API/Probabilities.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { type UserAPI } from "./API";
|
||||||
|
|
||||||
|
export const prob = (api: UserAPI) => (p: number): boolean => {
|
||||||
|
return api.randomGen() * 100 < p;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toss = (api: UserAPI) => (): boolean => {
|
||||||
|
return api.randomGen() > 0.5;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const odds = (api: UserAPI) => (n: number, beats: number = 1): boolean => {
|
||||||
|
return api.randomGen() < (n * api.ppqn()) / (api.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const never = () => (): boolean => {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const almostNever = (api: UserAPI) => (beats: number = 1): boolean => {
|
||||||
|
return api.randomGen() < (0.025 * api.ppqn()) / (api.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rarely = (api: UserAPI) => (beats: number = 1): boolean => {
|
||||||
|
return api.randomGen() < (0.1 * api.ppqn()) / (api.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const scarcely = (api: UserAPI) => (beats: number = 1): boolean => {
|
||||||
|
return api.randomGen() < (0.25 * api.ppqn()) / (api.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sometimes = (api: UserAPI) => (beats: number = 1): boolean => {
|
||||||
|
return api.randomGen() < (0.5 * api.ppqn()) / (api.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const often = (api: UserAPI) => (beats: number = 1): boolean => {
|
||||||
|
return api.randomGen() < (0.75 * api.ppqn()) / (api.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const frequently = (api: UserAPI) => (beats: number = 1): boolean => {
|
||||||
|
return api.randomGen() < (0.9 * api.ppqn()) / (api.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const almostAlways = (api: UserAPI) => (beats: number = 1): boolean => {
|
||||||
|
return api.randomGen() < (0.985 * api.ppqn()) / (api.ppqn() * beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const always = () => (): boolean => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dice = (api: UserAPI) => (sides: number): number => {
|
||||||
|
return Math.floor(api.randomGen() * sides) + 1;
|
||||||
|
};
|
||||||
35
src/API/Randomness.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { seededRandom } from "zifferjs";
|
||||||
|
import { UserAPI } from "./API";
|
||||||
|
|
||||||
|
export const randI = (api: UserAPI) => (min: number, max: number): number => {
|
||||||
|
return Math.floor(api.randomGen() * (max - min + 1)) + min;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rand = (api: UserAPI) => (min: number, max: number): number => {
|
||||||
|
return api.randomGen() * (max - min) + min;
|
||||||
|
};
|
||||||
|
export const r = rand
|
||||||
|
|
||||||
|
export const seed = (api: UserAPI) => (seed: string | number): void => {
|
||||||
|
if (typeof seed === "number") seed = seed.toString();
|
||||||
|
if (api.currentSeed !== seed) {
|
||||||
|
api.currentSeed = seed;
|
||||||
|
api.randomGen = seededRandom(seed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const localSeededRandom = (api: UserAPI) => (seed: string | number): Function => {
|
||||||
|
if (typeof seed === "number") seed = seed.toString();
|
||||||
|
if (api.localSeeds.has(seed)) return api.localSeeds.get(seed) as Function;
|
||||||
|
const newSeededRandom = seededRandom(seed);
|
||||||
|
api.localSeeds.set(seed, newSeededRandom);
|
||||||
|
return newSeededRandom;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clearLocalSeed = (api: UserAPI) => (seed: string | number | undefined = undefined): void => {
|
||||||
|
if (seed) {
|
||||||
|
api.localSeeds.delete(seed.toString());
|
||||||
|
} else {
|
||||||
|
api.localSeeds.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
64
src/API/Script.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { tryEvaluate } from "../Evaluator";
|
||||||
|
import { blinkScript } from "../DOM/Visuals/Blinkers";
|
||||||
|
import { template_universes } from "../Editor/FileManagement";
|
||||||
|
import { Editor } from "../main";
|
||||||
|
|
||||||
|
export const script = (app: Editor) => (...args: number[]): void => {
|
||||||
|
args.forEach((arg) => {
|
||||||
|
if (arg >= 1 && arg <= 9) {
|
||||||
|
blinkScript(app, "local", arg);
|
||||||
|
tryEvaluate(
|
||||||
|
app,
|
||||||
|
app.universes[app.selected_universe]!.locals[arg]!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const s = script;
|
||||||
|
|
||||||
|
export const delete_script = (app: Editor) => (script: number): void => {
|
||||||
|
app.universes[app.selected_universe]!.locals[script] = {
|
||||||
|
candidate: "",
|
||||||
|
committed: "",
|
||||||
|
evaluations: 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const copy_script = (app: Editor) => (from: number, to: number): void => {
|
||||||
|
//@ts-ignore
|
||||||
|
app.universes[app.selected_universe].locals[to] = {
|
||||||
|
...app.universes[app.selected_universe]!.locals[from],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const copy_universe = (app: Editor) => (from: string, to: string): void => {
|
||||||
|
//@ts-ignore
|
||||||
|
app.universes[to] = { ...app.universes[from], };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const delete_universe = (app: Editor) => (universe: string): void => {
|
||||||
|
if (app.selected_universe === universe) {
|
||||||
|
app.selected_universe = "Default";
|
||||||
|
}
|
||||||
|
delete app.universes[universe];
|
||||||
|
app.settings.saveApplicationToLocalStorage(
|
||||||
|
app.universes,
|
||||||
|
app.settings,
|
||||||
|
);
|
||||||
|
app.updateKnownUniversesView();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const big_bang = (app: Editor) => (): void => {
|
||||||
|
if (confirm("Are you sure you want to delete all universes?")) {
|
||||||
|
app.universes = {
|
||||||
|
...template_universes, // Assuming template_universes is defined elsewhere
|
||||||
|
};
|
||||||
|
app.settings.saveApplicationToLocalStorage(
|
||||||
|
app.universes,
|
||||||
|
app.settings,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
app.selected_universe = "Default";
|
||||||
|
app.updateKnownUniversesView();
|
||||||
|
};
|
||||||
44
src/API/Sound.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { SoundEvent } from "../Classes/SoundEvent";
|
||||||
|
import { SkipEvent } from "../Classes/SkipEvent";
|
||||||
|
import { Editor } from "../main";
|
||||||
|
|
||||||
|
export const sound = (app: Editor) => (sound: string | string[] | null | undefined) => {
|
||||||
|
/**
|
||||||
|
* Creates a sound event if a sound is specified, otherwise returns a skip event.
|
||||||
|
* @param sound - The sound identifier or array of identifiers to play.
|
||||||
|
* @returns SoundEvent if sound is defined, otherwise SkipEvent.
|
||||||
|
*/
|
||||||
|
if (sound) return new SoundEvent(sound, app);
|
||||||
|
else return new SkipEvent();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const snd = sound;
|
||||||
|
|
||||||
|
export const speak = () => (text: string, lang: string = "en-US", voiceIndex: number = 0, rate: number = 1, pitch: number = 1): void => {
|
||||||
|
/**
|
||||||
|
* Speaks the given text using the browser's speech synthesis API.
|
||||||
|
* @param text - The text to speak.
|
||||||
|
* @param lang - The language code (e.g., "en-US").
|
||||||
|
* @param voiceIndex - The index of the voice to use from the speechSynthesis voice list.
|
||||||
|
* @param rate - The rate at which to speak the text.
|
||||||
|
* @param pitch - The pitch at which to speak the text.
|
||||||
|
*/
|
||||||
|
const msg = new SpeechSynthesisUtterance(text);
|
||||||
|
msg.lang = lang;
|
||||||
|
msg.rate = rate;
|
||||||
|
msg.pitch = pitch;
|
||||||
|
|
||||||
|
// Set the voice using a provided index
|
||||||
|
const voices = window.speechSynthesis.getVoices();
|
||||||
|
msg.voice = voices[voiceIndex] || null;
|
||||||
|
|
||||||
|
window.speechSynthesis.speak(msg);
|
||||||
|
|
||||||
|
msg.onend = () => {
|
||||||
|
console.log("Finished speaking:", text);
|
||||||
|
};
|
||||||
|
|
||||||
|
msg.onerror = (event) => {
|
||||||
|
console.error("Speech synthesis error:", event);
|
||||||
|
};
|
||||||
|
};
|
||||||
215
src/API/Time/Filters.ts
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
import { type Editor } from "../../main";
|
||||||
|
import { UserAPI } from "../API";
|
||||||
|
|
||||||
|
const _euclidean_cycle = (
|
||||||
|
pulses: number,
|
||||||
|
length: number,
|
||||||
|
rotate: number = 0,
|
||||||
|
): boolean[] => {
|
||||||
|
if (pulses == length) return Array.from({ length }, () => true);
|
||||||
|
function startsDescent(list: number[], i: number): boolean {
|
||||||
|
const length = list.length;
|
||||||
|
const nextIndex = (i + 1) % length;
|
||||||
|
return list[i]! > list[nextIndex]!? true : false;
|
||||||
|
}
|
||||||
|
if (pulses >= length) return [true];
|
||||||
|
const resList = Array.from(
|
||||||
|
{ length },
|
||||||
|
(_, i) => (((pulses * (i - 1)) % length) + length) % length,
|
||||||
|
);
|
||||||
|
let cycle = resList.map((_, i) => startsDescent(resList, i));
|
||||||
|
if (rotate != 0) {
|
||||||
|
cycle = cycle.slice(rotate).concat(cycle.slice(0, rotate));
|
||||||
|
}
|
||||||
|
return cycle;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fullseq = () => (sequence: string, duration: number): boolean | Array<boolean> => {
|
||||||
|
if (sequence.split("").every((c) => c === "x" || c === "o")) {
|
||||||
|
return [...sequence].map((c) => c === "x").beat(duration);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const seq = (app: any) => (expr: string, duration: number = 0.5): boolean => {
|
||||||
|
let len = expr.length * duration;
|
||||||
|
let output: number[] = [];
|
||||||
|
|
||||||
|
for (let i = 1; i <= len + 1; i += duration) {
|
||||||
|
output.push(Math.floor(i * 10) / 10);
|
||||||
|
}
|
||||||
|
output.pop();
|
||||||
|
|
||||||
|
output = output.filter((_, idx) => {
|
||||||
|
const exprIdx = idx % expr.length;
|
||||||
|
return expr[exprIdx] === "x";
|
||||||
|
});
|
||||||
|
|
||||||
|
return oncount(app)(output, len);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const beat = (app: Editor) => (n: number | number[] = 1, nudge: number = 0): boolean => {
|
||||||
|
const nArray = Array.isArray(n) ? n : [n];
|
||||||
|
const results: boolean[] = nArray.map(
|
||||||
|
(value) =>
|
||||||
|
(app.clock.time_position.grain - Math.round(nudge * app.clock.time_position.ppqn)) %
|
||||||
|
Math.round(value * app.clock.time_position.ppqn) === 0,
|
||||||
|
);
|
||||||
|
return results.some((value) => value === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// export const beat = (app: Editor) => (n: number | number[] = 1, nudge: number = 0): boolean => {
|
||||||
|
// const nArray = !Array.isArray(n) ? [n] : n;
|
||||||
|
// return nArray.some(
|
||||||
|
// (value) =>
|
||||||
|
// !((app.clock.time_position.grain - nudge * app.clock.time_position.ppqn) % Math.floor(value * app.clock.time_position.ppqn))
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
export const bar = (app: Editor) => (n: number | number[] = 1, nudge: number = 0): boolean => {
|
||||||
|
const nArray = Array.isArray(n) ? n : [n];
|
||||||
|
const barLength = app.clock.time_position.num * app.clock.ppqn;
|
||||||
|
const nudgeInPulses = Math.floor(nudge * barLength);
|
||||||
|
const results: boolean[] = nArray.map(
|
||||||
|
(value) =>
|
||||||
|
(app.clock.grain - nudgeInPulses) %
|
||||||
|
Math.floor(value * barLength) === 0,
|
||||||
|
);
|
||||||
|
return results.some((value) => value === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pulse = (app: Editor) => (n: number | number[] = 1, nudge: number = 0): boolean => {
|
||||||
|
const nArray = Array.isArray(n) ? n : [n];
|
||||||
|
const results: boolean[] = nArray.map(
|
||||||
|
(value) => (app.clock.grain - nudge) % value === 0,
|
||||||
|
);
|
||||||
|
return results.some((value) => value === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tick = (app: Editor) => (tick: number | number[], offset: number = 0): boolean => {
|
||||||
|
const nArray = Array.isArray(tick) ? tick : [tick];
|
||||||
|
const results: boolean[] = nArray.map(
|
||||||
|
(value) => app.clock.time_position.tick === value + offset,
|
||||||
|
);
|
||||||
|
return results.some((value) => value === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dur = (app: Editor) => (n: number | number[]): boolean => {
|
||||||
|
let nums: number[] = Array.isArray(n) ? n : [n];
|
||||||
|
return beat(app)(nums.dur(...nums));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const flip = (app: Editor) => (chunk: number, ratio: number = 50): boolean => {
|
||||||
|
let realChunk = chunk * 2;
|
||||||
|
const time_pos = app.clock.grain;
|
||||||
|
const full_chunk = Math.floor(realChunk * app.clock.ppqn);
|
||||||
|
const threshold = Math.floor((ratio / 100) * full_chunk);
|
||||||
|
const pos_within_chunk = time_pos % full_chunk;
|
||||||
|
return pos_within_chunk < threshold;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const flipbar = (app: Editor) => (chunk: number = 1): boolean => {
|
||||||
|
let realFlip = chunk;
|
||||||
|
const time_pos = app.clock.time_position.bar;
|
||||||
|
const current_chunk = Math.floor(time_pos / realFlip);
|
||||||
|
return current_chunk % 2 === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onbar = (app: Editor) => (
|
||||||
|
bars: number[] | number,
|
||||||
|
n: number = app.clock.time_position.num,
|
||||||
|
): boolean => {
|
||||||
|
let current_bar = (app.clock.time_position.bar % n) + 1;
|
||||||
|
return typeof bars === "number"
|
||||||
|
? bars === current_bar
|
||||||
|
: bars.some((b) => b === current_bar);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onbeat = (api: UserAPI) => (...beat: number[]): boolean => {
|
||||||
|
let final_pulses: boolean[] = [];
|
||||||
|
beat.forEach((b) => {
|
||||||
|
let beatNumber = b % api.nominator() || api.nominator();
|
||||||
|
let integral_part = Math.floor(beatNumber);
|
||||||
|
integral_part = integral_part === 0 ? api.nominator() : integral_part;
|
||||||
|
let decimal_part = Math.floor((beatNumber - integral_part) * api.app.clock.ppqn + 1);
|
||||||
|
if (decimal_part <= 0)
|
||||||
|
decimal_part += api.app.clock.ppqn * api.nominator();
|
||||||
|
final_pulses.push(
|
||||||
|
integral_part === api.cbeat() && api.cpulse() === decimal_part,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return final_pulses.some((p) => p === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const oncount = (app: Editor) => (beats: number[] | number, count: number): boolean => {
|
||||||
|
if (typeof beats === "number") beats = [beats];
|
||||||
|
const origin = app.clock.grain;
|
||||||
|
let final_pulses: boolean[] = [];
|
||||||
|
beats.forEach((b) => {
|
||||||
|
b = b < 1 ? 0 : b - 1;
|
||||||
|
const beatInTicks = Math.ceil(b * app.clock.ppqn);
|
||||||
|
const meterPosition = origin % (app.clock.ppqn * count);
|
||||||
|
final_pulses.push(meterPosition === beatInTicks);
|
||||||
|
});
|
||||||
|
return final_pulses.some((p) => p === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const oneuclid = (app: Editor) => (pulses: number, length: number, rotate: number = 0): boolean => {
|
||||||
|
const cycle = _euclidean_cycle(pulses, length, rotate);
|
||||||
|
const beats = cycle.reduce((acc: number[], x: boolean, i: number) => {
|
||||||
|
if (x) acc.push(i + 1);
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
return oncount(app)(beats, length);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const euclid = () => (iterator: number, pulses: number, length: number, rotate: number = 0): boolean => {
|
||||||
|
/**
|
||||||
|
* Returns a Euclidean cycle of size length, with n pulses, rotated or not.
|
||||||
|
*/
|
||||||
|
const cycle = _euclidean_cycle(pulses, length, rotate);
|
||||||
|
return cycle && cycle[iterator % length] === true;
|
||||||
|
};
|
||||||
|
export const ec = euclid;
|
||||||
|
|
||||||
|
export const rhythm = (app: Editor) => (div: number, pulses: number, length: number, rotate: number = 0): boolean => {
|
||||||
|
/**
|
||||||
|
* Returns a rhythm based on Euclidean cycle.
|
||||||
|
*/
|
||||||
|
return (
|
||||||
|
beat(app)(div) && _euclidean_cycle(pulses, length, rotate).beat(div)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const ry = rhythm;
|
||||||
|
|
||||||
|
export const nrhythm = (app: Editor) => (div: number, pulses: number, length: number, rotate: number = 0): boolean => {
|
||||||
|
/**
|
||||||
|
* Returns a negated rhythm based on Euclidean cycle.
|
||||||
|
*/
|
||||||
|
let rhythm = _euclidean_cycle(pulses, length, rotate).map((n: any) => !n);
|
||||||
|
return (
|
||||||
|
beat(app)(div) && rhythm.beat(div)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const nry = nrhythm;
|
||||||
|
|
||||||
|
export const bin = () => (iterator: number, n: number): boolean => {
|
||||||
|
/**
|
||||||
|
* Returns a binary cycle of size n.
|
||||||
|
*/
|
||||||
|
let convert: string = n.toString(2);
|
||||||
|
let tobin: boolean[] = convert.split("").map((x: string) => x === "1");
|
||||||
|
return tobin[iterator % tobin.length] || false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const binrhythm = (app: Editor) => (div: number, n: number): boolean => {
|
||||||
|
/**
|
||||||
|
* Returns a binary rhythm based on division and binary cycle.
|
||||||
|
*/
|
||||||
|
let convert: string = n.toString(2);
|
||||||
|
let tobin: boolean[] = convert.split("").map((x: string) => x === "1");
|
||||||
|
return beat(app)(div) && tobin.beat(div);
|
||||||
|
};
|
||||||
|
export const bry = binrhythm;
|
||||||
103
src/API/Time/Transport.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import { type UserAPI } from "../API";
|
||||||
|
import { type Editor } from "../../main";
|
||||||
|
|
||||||
|
export const time = (api: UserAPI) => (): number => {
|
||||||
|
return api.app.audioContext.currentTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const play = (api: UserAPI) => (): void => {
|
||||||
|
api.app.setButtonHighlighting("play", true);
|
||||||
|
api.MidiConnection.sendStartMessage();
|
||||||
|
api.app.clock.start();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pause = (api: UserAPI) => (): void => {
|
||||||
|
api.app.setButtonHighlighting("pause", true);
|
||||||
|
api.app.clock.pause();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stop = (api: UserAPI) => (): void => {
|
||||||
|
api.app.setButtonHighlighting("stop", true);
|
||||||
|
api.app.clock.stop();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const silence = (api: UserAPI) => (): void => {
|
||||||
|
return stop(api)();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tempo = (app: Editor) => (n?: number): number => {
|
||||||
|
/**
|
||||||
|
* Sets or returns the current bpm.
|
||||||
|
*/
|
||||||
|
if (n === undefined) return app.clock.bpm;
|
||||||
|
|
||||||
|
if (n >= 1 && n <= 500) {
|
||||||
|
app.clock.bpm = n;
|
||||||
|
} else {
|
||||||
|
console.error("BPM out of acceptable range (1-500).");
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ppqn = (app: Editor) => (n?: number): number => {
|
||||||
|
/**
|
||||||
|
* Sets or returns the number of pulses per quarter note.
|
||||||
|
*/
|
||||||
|
if (n === undefined) return app.clock.ppqn;
|
||||||
|
|
||||||
|
if (n >= 1) {
|
||||||
|
app.clock.ppqn = n;
|
||||||
|
} else {
|
||||||
|
console.error("Pulses per quarter note must be at least 1.");
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const time_signature = (app: Editor) => (numerator: number, denominator: number): void => {
|
||||||
|
/**
|
||||||
|
* Sets the time signature.
|
||||||
|
*/
|
||||||
|
if (numerator < 1 || denominator < 1) {
|
||||||
|
console.error("Time signature values must be at least 1.");
|
||||||
|
} else {
|
||||||
|
app.clock.setSignature(numerator, denominator);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cbar = (app: Editor) => (): number => {
|
||||||
|
return app.clock.time_position.bar + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ctick = (app: Editor) => (): number => {
|
||||||
|
return app.clock.grain + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cpulse = (app: Editor) => (): number => {
|
||||||
|
return app.clock.time_position.tick + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cbeat = (app: Editor) => (): number => {
|
||||||
|
return app.clock.time_position.beat + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ebeat = (app: Editor) => (): number => {
|
||||||
|
return app.clock.beats_since_origin + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const epulse = (app: Editor) => (): number => {
|
||||||
|
return app.clock.grain + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const nominator = (app: Editor) => (): number => {
|
||||||
|
return app.clock.time_position.num;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const meter = (app: Editor) => (): number => {
|
||||||
|
return app.clock.time_position.den;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const denominator = meter;
|
||||||
|
|
||||||
|
export const pulsesForBar = (app: Editor) => (): number => {
|
||||||
|
return (app.clock.bpm * app.clock.ppqn * nominator(app)()) / 60;
|
||||||
|
};
|
||||||
18
src/API/Time/Warp.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Editor } from "../../main";
|
||||||
|
|
||||||
|
export const warp = (app: Editor) => (n: number): void => {
|
||||||
|
/**
|
||||||
|
* Time-warp the clock by using the tick you wish to jump to.
|
||||||
|
*/
|
||||||
|
app.clock.time_position.tick = n;
|
||||||
|
app.clock.time_position = app.clock.convertTicksToTimeposition(n);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const beat_warp = (app: Editor) => (beat: number): void => {
|
||||||
|
/**
|
||||||
|
* Time-warp the clock by using the tick you wish to jump to.
|
||||||
|
*/
|
||||||
|
const ticks = beat * app.clock.ppqn;
|
||||||
|
app.clock.time_position.tick = ticks;
|
||||||
|
app.clock.time_position = app.clock.convertTicksToTimeposition(ticks);
|
||||||
|
};
|
||||||
72
src/API/Ziffers.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { InputOptions, Player } from "../Classes/ZPlayer";
|
||||||
|
import { UserAPI } from "./API";
|
||||||
|
import { generateCacheKey, removePatternFromCache } from "./Cache"
|
||||||
|
|
||||||
|
export const z = (api: UserAPI) => (input: string | Generator<number>, options: InputOptions = {}, id: number | string = ""): Player => {
|
||||||
|
const zid = "z" + id.toString();
|
||||||
|
const key = id === "" ? generateCacheKey()(input, options) : zid;
|
||||||
|
|
||||||
|
const validSyntax = typeof input === "string" && !api.invalidPatterns[input]
|
||||||
|
|
||||||
|
let player;
|
||||||
|
let replace = false;
|
||||||
|
|
||||||
|
if (api.patternCache.has(key)) {
|
||||||
|
player = api.patternCache.get(key) as Player;
|
||||||
|
|
||||||
|
if (typeof input === "string" &&
|
||||||
|
player.input !== input &&
|
||||||
|
(player.atTheBeginning() || api.forceEvaluator)) {
|
||||||
|
replace = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((typeof input !== "string" || validSyntax) && (!player || replace)) {
|
||||||
|
if (typeof input === "string" && player && api.forceEvaluator) {
|
||||||
|
if (!player.updatePattern(input, options)) {
|
||||||
|
api.logOnce(`Invalid syntax: ${input}`);
|
||||||
|
};
|
||||||
|
api.forceEvaluator = false;
|
||||||
|
} else {
|
||||||
|
const newPlayer = player ? new Player(input, options, api.app, zid, player.nextEndTime()) : new Player(input, options, api.app, zid);
|
||||||
|
if (newPlayer.isValid()) {
|
||||||
|
player = newPlayer;
|
||||||
|
api.patternCache.set(key, player);
|
||||||
|
} else if (typeof input === "string") {
|
||||||
|
api.invalidPatterns[input] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player) {
|
||||||
|
if (player.atTheBeginning()) {
|
||||||
|
if (typeof input === "string" && !validSyntax) api.log(`Invalid syntax: ${input}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.ziffers.generator && player.ziffers.generatorDone) {
|
||||||
|
removePatternFromCache(api)(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof id === "number") player.zid = zid;
|
||||||
|
|
||||||
|
player.updateLastCallTime();
|
||||||
|
|
||||||
|
if (id !== "" && zid !== "z0") {
|
||||||
|
// Sync named patterns to z0 by default
|
||||||
|
player.sync("z0", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return player;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Invalid syntax: ${input}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generating numbered functions dynamically
|
||||||
|
export const generateZFunctions = (api: UserAPI) => {
|
||||||
|
const zFunctions: { [key: string]: (input: string, opts: InputOptions) => Player } = {};
|
||||||
|
for (let i = 0; i <= 16; i++) {
|
||||||
|
zFunctions[`z${i}`] = (input: string, opts: InputOptions = {}) => z(api)(input, opts, i);
|
||||||
|
}
|
||||||
|
return zFunctions;
|
||||||
|
};
|
||||||
250
src/Clock.ts
@ -1,250 +0,0 @@
|
|||||||
// @ts-ignore
|
|
||||||
import { TransportNode } from "./TransportNode";
|
|
||||||
import TransportProcessor from "./TransportProcessor?worker&url";
|
|
||||||
import { Editor } from "./main";
|
|
||||||
|
|
||||||
export interface TimePosition {
|
|
||||||
/**
|
|
||||||
* A position in time.
|
|
||||||
*
|
|
||||||
* @param bar - The bar number
|
|
||||||
* @param beat - The beat number
|
|
||||||
* @param pulse - The pulse number
|
|
||||||
*/
|
|
||||||
bar: number;
|
|
||||||
beat: number;
|
|
||||||
pulse: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Clock {
|
|
||||||
/**
|
|
||||||
* The Clock Class is responsible for keeping track of the current time.
|
|
||||||
* It is also responsible for starting and stopping the Clock TransportNode.
|
|
||||||
*
|
|
||||||
* @param app - The main application instance
|
|
||||||
* @param ctx - The current AudioContext used by app
|
|
||||||
* @param transportNode - The TransportNode helper
|
|
||||||
* @param bpm - The current beats per minute value
|
|
||||||
* @param time_signature - The time signature
|
|
||||||
* @param time_position - The current time position
|
|
||||||
* @param ppqn - The pulses per quarter note
|
|
||||||
* @param tick - The current tick since origin
|
|
||||||
* @param running - Is the clock running?
|
|
||||||
* @param lastPauseTime - The last time the clock was paused
|
|
||||||
* @param lastPlayPressTime - The last time the clock was started
|
|
||||||
* @param totalPauseTime - The total time the clock has been paused / stopped
|
|
||||||
*/
|
|
||||||
|
|
||||||
ctx: AudioContext;
|
|
||||||
logicalTime: number;
|
|
||||||
transportNode: TransportNode | null;
|
|
||||||
private _bpm: number;
|
|
||||||
time_signature: number[];
|
|
||||||
time_position: TimePosition;
|
|
||||||
private _ppqn: number;
|
|
||||||
tick: number;
|
|
||||||
running: boolean;
|
|
||||||
lastPauseTime: number;
|
|
||||||
lastPlayPressTime: number;
|
|
||||||
totalPauseTime: number;
|
|
||||||
|
|
||||||
constructor(public app: Editor, ctx: AudioContext) {
|
|
||||||
this.time_position = { bar: 0, beat: 0, pulse: 0 };
|
|
||||||
this.time_signature = [4, 4];
|
|
||||||
this.logicalTime = 0;
|
|
||||||
this.tick = 0;
|
|
||||||
this._bpm = 120;
|
|
||||||
this._ppqn = 48;
|
|
||||||
this.transportNode = null;
|
|
||||||
this.ctx = ctx;
|
|
||||||
this.running = true;
|
|
||||||
this.lastPauseTime = 0;
|
|
||||||
this.lastPlayPressTime = 0;
|
|
||||||
this.totalPauseTime = 0;
|
|
||||||
ctx.audioWorklet
|
|
||||||
.addModule(TransportProcessor)
|
|
||||||
.then((e) => {
|
|
||||||
this.transportNode = new TransportNode(ctx, {}, this.app);
|
|
||||||
this.transportNode.connect(ctx.destination);
|
|
||||||
return e;
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.log("Error loading TransportProcessor.js:", e);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
convertTicksToTimeposition(ticks: number): TimePosition {
|
|
||||||
const beatsPerBar = this.app.clock.time_signature[0];
|
|
||||||
const ppqnPosition = ticks % this.app.clock.ppqn;
|
|
||||||
const beatNumber = Math.floor(ticks / this.app.clock.ppqn);
|
|
||||||
const barNumber = Math.floor(beatNumber / beatsPerBar);
|
|
||||||
const beatWithinBar = Math.floor(beatNumber % beatsPerBar);
|
|
||||||
return { bar: barNumber, beat: beatWithinBar, pulse: ppqnPosition };
|
|
||||||
}
|
|
||||||
|
|
||||||
get ticks_before_new_bar(): number {
|
|
||||||
/**
|
|
||||||
* This function returns the number of ticks separating the current moment
|
|
||||||
* from the beginning of the next bar.
|
|
||||||
*
|
|
||||||
* @returns number of ticks until next bar
|
|
||||||
*/
|
|
||||||
const ticskMissingFromBeat = this.ppqn - this.time_position.pulse;
|
|
||||||
const beatsMissingFromBar = this.beats_per_bar - this.time_position.beat;
|
|
||||||
return beatsMissingFromBar * this.ppqn + ticskMissingFromBeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
get next_beat_in_ticks(): number {
|
|
||||||
/**
|
|
||||||
* This function returns the number of ticks separating the current moment
|
|
||||||
* from the beginning of the next beat.
|
|
||||||
*
|
|
||||||
* @returns number of ticks until next beat
|
|
||||||
*/
|
|
||||||
return this.app.clock.pulses_since_origin + this.time_position.pulse;
|
|
||||||
}
|
|
||||||
|
|
||||||
get beats_per_bar(): number {
|
|
||||||
/**
|
|
||||||
* Returns the number of beats per bar.
|
|
||||||
*/
|
|
||||||
return this.time_signature[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
get beats_since_origin(): number {
|
|
||||||
/**
|
|
||||||
* Returns the number of beats since the origin.
|
|
||||||
*
|
|
||||||
* @returns number of beats since origin
|
|
||||||
*/
|
|
||||||
return Math.floor(this.tick / this.ppqn);
|
|
||||||
}
|
|
||||||
|
|
||||||
get pulses_since_origin(): number {
|
|
||||||
/**
|
|
||||||
* Returns the number of pulses since the origin.
|
|
||||||
*
|
|
||||||
* @returns number of pulses since origin
|
|
||||||
*/
|
|
||||||
return this.tick;
|
|
||||||
}
|
|
||||||
|
|
||||||
get pulse_duration(): number {
|
|
||||||
/**
|
|
||||||
* Returns the duration of a pulse in seconds.
|
|
||||||
*/
|
|
||||||
return 60 / this.bpm / this.ppqn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public pulse_duration_at_bpm(bpm: number = this.bpm): number {
|
|
||||||
/**
|
|
||||||
* Returns the duration of a pulse in seconds at a specific bpm.
|
|
||||||
*/
|
|
||||||
return 60 / bpm / this.ppqn;
|
|
||||||
}
|
|
||||||
|
|
||||||
get bpm(): number {
|
|
||||||
return this._bpm;
|
|
||||||
}
|
|
||||||
|
|
||||||
set nudge(nudge: number) {
|
|
||||||
this.transportNode?.setNudge(nudge);
|
|
||||||
}
|
|
||||||
|
|
||||||
set bpm(bpm: number) {
|
|
||||||
if (bpm > 0 && this._bpm !== bpm) {
|
|
||||||
this.transportNode?.setBPM(bpm);
|
|
||||||
this._bpm = bpm;
|
|
||||||
this.logicalTime = this.realTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get ppqn(): number {
|
|
||||||
return this._ppqn;
|
|
||||||
}
|
|
||||||
|
|
||||||
get realTime(): number {
|
|
||||||
return this.app.audioContext.currentTime - this.totalPauseTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
get deviation(): number {
|
|
||||||
return Math.abs(this.logicalTime - this.realTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
set ppqn(ppqn: number) {
|
|
||||||
if (ppqn > 0 && this._ppqn !== ppqn) {
|
|
||||||
this._ppqn = ppqn;
|
|
||||||
this.transportNode?.setPPQN(ppqn);
|
|
||||||
this.logicalTime = this.realTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public incrementTick(bpm: number) {
|
|
||||||
this.tick++;
|
|
||||||
this.logicalTime += this.pulse_duration_at_bpm(bpm);
|
|
||||||
}
|
|
||||||
|
|
||||||
public nextTickFrom(time: number, nudge: number): number {
|
|
||||||
/**
|
|
||||||
* Compute the time remaining before the next clock tick.
|
|
||||||
* @param time - audio context currentTime
|
|
||||||
* @param nudge - nudge in the future (in seconds)
|
|
||||||
* @returns remainingTime
|
|
||||||
*/
|
|
||||||
const pulseDuration = this.pulse_duration;
|
|
||||||
const nudgedTime = time + nudge;
|
|
||||||
const nextTickTime = Math.ceil(nudgedTime / pulseDuration) * pulseDuration;
|
|
||||||
const remainingTime = nextTickTime - nudgedTime;
|
|
||||||
|
|
||||||
return remainingTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public convertPulseToSecond(n: number): number {
|
|
||||||
/**
|
|
||||||
* Converts a pulse to a second.
|
|
||||||
*/
|
|
||||||
return n * this.pulse_duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public start(): void {
|
|
||||||
/**
|
|
||||||
* Starts the TransportNode (starts the clock).
|
|
||||||
*
|
|
||||||
* @remark also sends a MIDI message if a port is declared
|
|
||||||
*/
|
|
||||||
this.app.audioContext.resume();
|
|
||||||
this.running = true;
|
|
||||||
this.app.api.MidiConnection.sendStartMessage();
|
|
||||||
this.lastPlayPressTime = this.app.audioContext.currentTime;
|
|
||||||
this.totalPauseTime += this.lastPlayPressTime - this.lastPauseTime;
|
|
||||||
this.transportNode?.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public pause(): void {
|
|
||||||
/**
|
|
||||||
* Pauses the TransportNode (pauses the clock).
|
|
||||||
*
|
|
||||||
* @remark also sends a MIDI message if a port is declared
|
|
||||||
*/
|
|
||||||
this.running = false;
|
|
||||||
this.transportNode?.pause();
|
|
||||||
this.app.api.MidiConnection.sendStopMessage();
|
|
||||||
this.lastPauseTime = this.app.audioContext.currentTime;
|
|
||||||
this.logicalTime = this.realTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public stop(): void {
|
|
||||||
/**
|
|
||||||
* Stops the TransportNode (stops the clock).
|
|
||||||
*
|
|
||||||
* @remark also sends a MIDI message if a port is declared
|
|
||||||
*/
|
|
||||||
this.running = false;
|
|
||||||
this.tick = 0;
|
|
||||||
this.lastPauseTime = this.app.audioContext.currentTime;
|
|
||||||
this.logicalTime = this.realTime;
|
|
||||||
this.time_position = { bar: 0, beat: 0, pulse: 0 };
|
|
||||||
this.app.api.MidiConnection.sendStopMessage();
|
|
||||||
this.transportNode?.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,25 +1,13 @@
|
|||||||
import { type Editor } from "./main";
|
import { type Editor } from "../main";
|
||||||
|
|
||||||
|
|
||||||
export type ElementMap = {
|
|
||||||
[key: string]:
|
|
||||||
| HTMLElement
|
|
||||||
| HTMLButtonElement
|
|
||||||
| HTMLDivElement
|
|
||||||
| HTMLInputElement
|
|
||||||
| HTMLSelectElement
|
|
||||||
| HTMLCanvasElement
|
|
||||||
| HTMLFormElement
|
|
||||||
| HTMLInputElement
|
|
||||||
;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const singleElements = {
|
export const singleElements = {
|
||||||
topos_logo: "topos-logo",
|
logo: "topos_logo",
|
||||||
fill_viewer: "fillviewer",
|
fill_viewer: "fillviewer",
|
||||||
load_universe_button: "load-universe-button",
|
load_universe_button: "load-universe-button",
|
||||||
download_universe_button: "download-universes",
|
download_universe_button: "download-universes",
|
||||||
upload_universe_button: "upload-universes",
|
upload_universe_button: "upload-universes",
|
||||||
|
upload_samples_button: "upload-samples",
|
||||||
|
sample_indicator: "sample-indicator",
|
||||||
destroy_universes_button: "destroy-universes",
|
destroy_universes_button: "destroy-universes",
|
||||||
documentation_button: "doc-button-1",
|
documentation_button: "doc-button-1",
|
||||||
eval_button: "eval-button-1",
|
eval_button: "eval-button-1",
|
||||||
@ -41,10 +29,12 @@ export const singleElements = {
|
|||||||
line_numbers_checkbox: "show-line-numbers",
|
line_numbers_checkbox: "show-line-numbers",
|
||||||
time_position_checkbox: "show-time-position",
|
time_position_checkbox: "show-time-position",
|
||||||
tips_checkbox: "show-tips",
|
tips_checkbox: "show-tips",
|
||||||
|
transport_viewer: "transport_viewer",
|
||||||
completion_checkbox: "show-completions",
|
completion_checkbox: "show-completions",
|
||||||
midi_clock_checkbox: "send-midi-clock",
|
midi_clock_checkbox: "send-midi-clock",
|
||||||
midi_channels_scripts: "midi-channels-scripts",
|
midi_channels_scripts: "midi-channels-scripts",
|
||||||
midi_clock_ppqn: "midi-clock-ppqn-input",
|
midi_clock_ppqn: "midi-clock-ppqn-input",
|
||||||
|
theme_selector: "theme-selector",
|
||||||
load_demo_songs: "load-demo-songs",
|
load_demo_songs: "load-demo-songs",
|
||||||
normal_mode_button: "normal-mode",
|
normal_mode_button: "normal-mode",
|
||||||
vim_mode_button: "vim-mode",
|
vim_mode_button: "vim-mode",
|
||||||
@ -55,43 +45,60 @@ export const singleElements = {
|
|||||||
hydra_canvas: "hydra-bg",
|
hydra_canvas: "hydra-bg",
|
||||||
feedback: "feedback",
|
feedback: "feedback",
|
||||||
scope: "scope",
|
scope: "scope",
|
||||||
};
|
play_button: "play-button",
|
||||||
|
play_label: "play-label",
|
||||||
|
stop_button: "stop-button",
|
||||||
|
play_icon: "play-icon",
|
||||||
|
pause_icon: "pause-icon",
|
||||||
|
} as const;
|
||||||
|
|
||||||
export const buttonGroups = {
|
export type SingleElementsKeys = keyof typeof singleElements;
|
||||||
play_buttons: ["play-button-1"],
|
|
||||||
stop_buttons: ["stop-button-1"],
|
export type ElementMap = {
|
||||||
clear_buttons: ["clear-button-1"],
|
[K in SingleElementsKeys]:
|
||||||
|
| HTMLElement
|
||||||
|
| HTMLButtonElement
|
||||||
|
| HTMLDivElement
|
||||||
|
| HTMLInputElement
|
||||||
|
| HTMLSelectElement
|
||||||
|
| HTMLCanvasElement
|
||||||
|
| HTMLFormElement
|
||||||
|
| HTMLInputElement;
|
||||||
};
|
};
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
export const createDocumentationStyle = (app: Editor) => {
|
export const createDocumentationStyle = (app: Editor) => {
|
||||||
|
/**
|
||||||
|
* Creates a documentation style object.
|
||||||
|
* @param {Editor} app - The editor object.
|
||||||
|
* @returns {Object} - The documentation style object.
|
||||||
|
*/
|
||||||
|
|
||||||
return {
|
return {
|
||||||
h1: "text-white lg:text-4xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-4 mb-4 border-b-4 pt-4 pb-3 px-2",
|
h1: "text-brightwhite lg:text-4xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-4 mb-4 border-b-4 pt-4 pb-3 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-4 mb-4 border-b-2 pt-12 pb-3 px-2",
|
h2: "text-brightwhite lg:text-3xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-4 mb-4 border-b-2 pt-12 pb-3 px-2",
|
||||||
h3: "text-white lg:text-2xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 border-l-2 border-b-2 lg:mb-4 mb-4 pb-2 px-2 lg:mt-16",
|
h3: "text-brightwhite lg:text-2xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 border-l-2 border-b-2 lg:mb-4 mb-4 pb-2 px-2 lg:mt-16",
|
||||||
ul: "text-underline ml-12",
|
ul: "text-underline ml-12",
|
||||||
li: "list-disc lg:text-2xl text-base text-white lg:mx-4 mx-2 my-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-6 mx-2 my-4 leading-normal",
|
p: "lg:text-2xl text-base text-white lg:mx-6 mx-2 my-4 leading-normal",
|
||||||
warning:
|
warning:
|
||||||
"animate-pulse lg:text-2xl font-bold text-rose-600 lg:mx-6 mx-2 my-4 leading-normal",
|
"animate-pulse lg:text-2xl font-bold text-brightred lg:mx-6 mx-2 my-4 leading-normal",
|
||||||
a: "lg:text-2xl text-base text-orange-300",
|
a: "lg:text-2xl text-base text-brightred",
|
||||||
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`,
|
||||||
icode:
|
icode:
|
||||||
"lg:my-1 my-1 lg:text-xl sm:text-xs text-white font-mono bg-neutral-600",
|
"lg:my-1 my-1 lg:text-xl sm:text-xs text-brightwhite font-mono bg-brightblack",
|
||||||
ic: "lg:my-1 my-1 lg:text-xl sm:text-xs text-white font-mono bg-neutral-600",
|
ic: "lg:my-1 my-1 lg:text-xl sm:text-xs text-brightwhite font-mono bg-brightblack",
|
||||||
blockquote: "text-neutral-200 border-l-4 border-neutral-500 pl-4 my-4 mx-4",
|
blockquote: "text-brightwhite border-l-4 border-white pl-4 my-4 mx-4",
|
||||||
details:
|
details:
|
||||||
"lg:mx-20 py-2 px-6 lg:text-2xl text-white border-l-8 box-border bg-neutral-900",
|
"lg:mx-20 py-2 px-6 lg:text-2xl text-white border-l-8 box-border bg-selection_foreground",
|
||||||
summary: "font-semibold text-xl",
|
summary: "font-semibold text-xl",
|
||||||
table:
|
table:
|
||||||
"justify-center lg:my-12 my-2 lg:mx-12 mx-2 lg:text-2xl text-base w-full text-left text-white border-collapse",
|
"justify-center lg:my-12 my-2 lg:mx-12 mx-2 lg:text-2xl text-base w-full text-left text-white border-collapse",
|
||||||
thead:
|
thead:
|
||||||
"text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400",
|
"text-xs text-gray-700 uppercase",
|
||||||
th: "",
|
th: "",
|
||||||
td: "",
|
td: "",
|
||||||
tr: "",
|
tr: "",
|
||||||
box: "border bg-red-500",
|
box: "border bg-red",
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -1,8 +1,9 @@
|
|||||||
import { type Editor } from "./main";
|
import { type Editor } from "../main";
|
||||||
import { vim } from "@replit/codemirror-vim";
|
import { vim } from "@replit/codemirror-vim";
|
||||||
import { tryEvaluate } from "./Evaluator";
|
import { tryEvaluate } from "../Evaluator";
|
||||||
import { hideDocumentation, showDocumentation } from "./Documentation";
|
import { hideDocumentation, showDocumentation } from "../Docs/Documentation";
|
||||||
import { openSettingsModal, openUniverseModal } from "./FileManagement";
|
import { openSettingsModal, openUniverseModal } from "../Editor/FileManagement";
|
||||||
|
import { resetTransportView, updatePlayButton } from "./UILogic";
|
||||||
|
|
||||||
export const registerFillKeys = (app: Editor) => {
|
export const registerFillKeys = (app: Editor) => {
|
||||||
document.addEventListener("keydown", (event) => {
|
document.addEventListener("keydown", (event) => {
|
||||||
@ -26,23 +27,48 @@ export const registerOnKeyDown = (app: Editor) => {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.ctrlKey && event.key === "m") {
|
||||||
|
event.preventDefault();
|
||||||
|
let topbar = document.getElementById("topbar");
|
||||||
|
let sidebar = document.getElementById("sidebar");
|
||||||
|
console.log("oui ok");
|
||||||
|
if (app.hidden_interface) {
|
||||||
|
// Sidebar
|
||||||
|
sidebar?.classList.remove("flex");
|
||||||
|
sidebar?.classList.remove("flex-col");
|
||||||
|
sidebar?.classList.add("hidden");
|
||||||
|
// Topbar
|
||||||
|
topbar?.classList.add("hidden");
|
||||||
|
topbar?.classList.remove("flex");
|
||||||
|
} else {
|
||||||
|
// Sidebar
|
||||||
|
sidebar?.classList.remove("hidden");
|
||||||
|
sidebar?.classList.add("flex");
|
||||||
|
sidebar?.classList.add("flex-col");
|
||||||
|
// Topbar
|
||||||
|
topbar?.classList.remove("hidden");
|
||||||
|
topbar?.classList.add("flex");
|
||||||
|
}
|
||||||
|
app.hidden_interface = !app.hidden_interface;
|
||||||
|
}
|
||||||
|
|
||||||
if (event.ctrlKey && event.key === "s") {
|
if (event.ctrlKey && event.key === "s") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
app.setButtonHighlighting("stop", true);
|
app.flashBackground("#404040", 200);
|
||||||
app.clock.stop();
|
requestAnimationFrame (() => {
|
||||||
|
updatePlayButton(app);
|
||||||
|
resetTransportView(app);
|
||||||
|
});
|
||||||
|
app.clock.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.ctrlKey && event.key === "p") {
|
if (event.ctrlKey && event.key === "p") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (app.isPlaying) {
|
app.flashBackground("#404040", 200);
|
||||||
app.isPlaying = false;
|
requestAnimationFrame(() => {
|
||||||
app.setButtonHighlighting("pause", true);
|
updatePlayButton(app);
|
||||||
app.clock.pause();
|
});
|
||||||
} else {
|
app.clock.resume()
|
||||||
app.isPlaying = true;
|
|
||||||
app.setButtonHighlighting("play", true);
|
|
||||||
app.clock.start();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl + Shift + V: Vim Mode
|
// Ctrl + Shift + V: Vim Mode
|
||||||
@ -80,6 +106,18 @@ export const registerOnKeyDown = (app: Editor) => {
|
|||||||
if (event.key === "Enter" && event.shiftKey && event.ctrlKey) {
|
if (event.key === "Enter" && event.shiftKey && event.ctrlKey) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
app.currentFile().candidate = app.view.state.doc.toString();
|
app.currentFile().candidate = app.view.state.doc.toString();
|
||||||
|
app.api.onceEvaluator = true;
|
||||||
|
app.api.forceEvaluator = true;
|
||||||
|
tryEvaluate(app, app.currentFile());
|
||||||
|
app.flashBackground("#404040", 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force eval with clearing cache
|
||||||
|
if (event.ctrlKey && event.shiftKey && (event.key === "Backspace" || event.key === "Delete")) {
|
||||||
|
event.preventDefault();
|
||||||
|
app.api.clearPatternCache();
|
||||||
|
app.currentFile().candidate = app.view.state.doc.toString();
|
||||||
|
app.api.forceEvaluator = true;
|
||||||
tryEvaluate(app, app.currentFile());
|
tryEvaluate(app, app.currentFile());
|
||||||
app.flashBackground("#404040", 200);
|
app.flashBackground("#404040", 200);
|
||||||
}
|
}
|
||||||
@ -1,12 +1,14 @@
|
|||||||
import { EditorView } from "codemirror";
|
import { EditorView } from "codemirror";
|
||||||
import { vim } from "@replit/codemirror-vim";
|
import { vim } from "@replit/codemirror-vim";
|
||||||
import { type Editor } from "./main";
|
import { type Editor } from "../main";
|
||||||
|
import colors from "../Editor/colors.json";
|
||||||
import {
|
import {
|
||||||
documentation_factory,
|
documentation_factory,
|
||||||
|
documentation_pages,
|
||||||
hideDocumentation,
|
hideDocumentation,
|
||||||
showDocumentation,
|
showDocumentation,
|
||||||
updateDocumentationContent,
|
updateDocumentationContent,
|
||||||
} from "./Documentation";
|
} from "../Docs/Documentation";
|
||||||
import {
|
import {
|
||||||
type Universe,
|
type Universe,
|
||||||
template_universe,
|
template_universe,
|
||||||
@ -16,24 +18,17 @@ import {
|
|||||||
share,
|
share,
|
||||||
closeUniverseModal,
|
closeUniverseModal,
|
||||||
openUniverseModal,
|
openUniverseModal,
|
||||||
} from "./FileManagement";
|
} from "../Editor/FileManagement";
|
||||||
import { loadSamples } from "./API";
|
import { loadSamples } from "../API/API";
|
||||||
import { tryEvaluate } from "./Evaluator";
|
import { tryEvaluate } from "../Evaluator";
|
||||||
import { inlineHoveringTips } from "./documentation/inlineHelp";
|
import { inlineHoveringTips } from "../Docs/inlineHelp";
|
||||||
import { lineNumbers } from "@codemirror/view";
|
import { lineNumbers } from "@codemirror/view";
|
||||||
import { jsCompletions } from "./EditorSetup";
|
import { jsCompletions } from "../Editor/EditorSetup";
|
||||||
import { createDocumentationStyle } from "./DomElements";
|
|
||||||
import { saveState } from "./WindowBehavior";
|
import { saveState } from "./WindowBehavior";
|
||||||
|
import { registerSamplesFromDB, samplesDBConfig, uploadSamplesToDB } from "../IO/SampleLoading";
|
||||||
|
|
||||||
export const installInterfaceLogic = (app: Editor) => {
|
export const installInterfaceLogic = (app: Editor) => {
|
||||||
// Initialize style
|
// Initialize style
|
||||||
const documentationStyle = createDocumentationStyle(app);
|
|
||||||
const bindings = Object.keys(documentationStyle).map((key) => ({
|
|
||||||
type: "output",
|
|
||||||
regex: new RegExp(`<${key}([^>]*)>`, "g"),
|
|
||||||
//@ts-ignore
|
|
||||||
replace: (match, p1) => `<${key} class="${documentationStyle[key]}" ${p1}>`,
|
|
||||||
}));
|
|
||||||
|
|
||||||
(app.interface.line_numbers_checkbox as HTMLInputElement).checked =
|
(app.interface.line_numbers_checkbox as HTMLInputElement).checked =
|
||||||
app.settings.line_numbers;
|
app.settings.line_numbers;
|
||||||
@ -49,60 +44,51 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
app.settings.midi_channels_scripts;
|
app.settings.midi_channels_scripts;
|
||||||
(app.interface.midi_clock_ppqn as HTMLInputElement).value =
|
(app.interface.midi_clock_ppqn as HTMLInputElement).value =
|
||||||
app.settings.midi_clock_ppqn.toString();
|
app.settings.midi_clock_ppqn.toString();
|
||||||
(app.interface.load_demo_songs as HTMLInputElement).checked =
|
// (app.interface.load_demo_songs as HTMLInputElement).checked =
|
||||||
app.settings.load_demo_songs;
|
// app.settings.load_demo_songs;
|
||||||
|
|
||||||
const tabs = document.querySelectorAll('[id^="tab-"]');
|
const tabs = document.querySelectorAll('[id^="tab-"]');
|
||||||
// Iterate over the tabs with an index
|
// Iterate over the tabs with an index
|
||||||
for (let i = 0; i < tabs.length; i++) {
|
for (let i = 0; i < tabs.length; i++) {
|
||||||
tabs[i].addEventListener("click", (event) => {
|
tabs[i]!.addEventListener("click", (event) => {
|
||||||
// Updating the CSS accordingly
|
// Updating the CSS accordingly
|
||||||
tabs[i].classList.add("bg-orange-300");
|
tabs[i]!.classList.add("bg-foreground");
|
||||||
|
tabs[i]!.classList.add("text-selection_foreground");
|
||||||
for (let j = 0; j < tabs.length; j++) {
|
for (let j = 0; j < tabs.length; j++) {
|
||||||
if (j != i) tabs[j].classList.remove("bg-orange-300");
|
if (j != i) tabs[j]!.classList.remove("bg-foreground");
|
||||||
|
if (j != i) tabs[j]!.classList.remove("text-selection_foreground");
|
||||||
}
|
}
|
||||||
app.currentFile().candidate = app.view.state.doc.toString();
|
app.currentFile().candidate = app.view.state.doc.toString();
|
||||||
|
|
||||||
let tab = event.target as HTMLElement;
|
let tab = event.target as HTMLElement;
|
||||||
let tab_id = tab.id.split("-")[1];
|
let tab_id = tab.id.split("-")[1];
|
||||||
app.local_index = parseInt(tab_id);
|
app.local_index = parseInt(tab_id!);
|
||||||
app.updateEditorView();
|
app.updateEditorView();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
app.interface.topos_logo.addEventListener("click", () => {
|
app.interface['logo'].addEventListener("click", () => {
|
||||||
hideDocumentation();
|
hideDocumentation();
|
||||||
app.updateKnownUniversesView();
|
app.updateKnownUniversesView();
|
||||||
openUniverseModal();
|
openUniverseModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.buttonElements.play_buttons.forEach((button) => {
|
app.interface['play_button'].addEventListener("click", () => {
|
||||||
button.addEventListener("click", () => {
|
|
||||||
if (app.isPlaying) {
|
if (app.isPlaying) {
|
||||||
app.setButtonHighlighting("pause", true);
|
|
||||||
app.isPlaying = !app.isPlaying;
|
|
||||||
app.clock.pause();
|
app.clock.pause();
|
||||||
app.api.MidiConnection.sendStopMessage();
|
|
||||||
} else {
|
} else {
|
||||||
app.setButtonHighlighting("play", true);
|
app.clock.resume()
|
||||||
app.isPlaying = !app.isPlaying;
|
|
||||||
app.clock.start();
|
|
||||||
app.api.MidiConnection.sendStartMessage();
|
|
||||||
}
|
}
|
||||||
});
|
updatePlayButton(app);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.buttonElements.clear_buttons.forEach((button) => {
|
app.interface['stop_button'].addEventListener("click", () => {
|
||||||
button.addEventListener("click", () => {
|
app.isPlaying = false;
|
||||||
app.setButtonHighlighting("clear", true);
|
app.clock.stop();
|
||||||
if (confirm("Do you want to reset the current universe?")) {
|
updatePlayButton(app);
|
||||||
app.universes[app.selected_universe] =
|
|
||||||
structuredClone(template_universe);
|
|
||||||
app.updateEditorView();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
app.interface.documentation_button.addEventListener("click", () => {
|
app.interface.documentation_button.addEventListener("click", () => {
|
||||||
showDocumentation(app);
|
showDocumentation(app);
|
||||||
});
|
});
|
||||||
@ -118,35 +104,52 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
|
|
||||||
app.interface.universe_viewer.addEventListener("keydown", (event: any) => {
|
app.interface.universe_viewer.addEventListener("keydown", (event: any) => {
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
let content = (app.interface.universe_viewer as HTMLInputElement).value.trim();
|
let content = (
|
||||||
|
app.interface.universe_viewer as HTMLInputElement
|
||||||
|
).value.trim();
|
||||||
if (content.length > 2 && content.length < 40) {
|
if (content.length > 2 && content.length < 40) {
|
||||||
if (content !== app.selected_universe) {
|
if (content !== app.selected_universe) {
|
||||||
Object.defineProperty(app.universes, content,
|
Object.defineProperty(
|
||||||
|
app.universes,
|
||||||
|
content,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
Object.getOwnPropertyDescriptor(app.universes, app.selected_universe));
|
Object.getOwnPropertyDescriptor(
|
||||||
|
app.universes,
|
||||||
|
app.selected_universe,
|
||||||
|
),
|
||||||
|
);
|
||||||
delete app.universes[app.selected_universe];
|
delete app.universes[app.selected_universe];
|
||||||
}
|
}
|
||||||
app.selected_universe = content;
|
app.selected_universe = content;
|
||||||
loadUniverse(app, app.selected_universe);
|
loadUniverse(app, app.selected_universe);
|
||||||
(app.interface.universe_viewer as HTMLInputElement).placeholder = content;
|
(app.interface.universe_viewer as HTMLInputElement).placeholder =
|
||||||
(app.interface.universe_viewer as HTMLInputElement).value = '';
|
content;
|
||||||
|
(app.interface.universe_viewer as HTMLInputElement).value = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
app.interface.audio_nudge_range.addEventListener("input", () => {
|
|
||||||
app.clock.nudge = parseInt(
|
|
||||||
(app.interface.audio_nudge_range as HTMLInputElement).value
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.interface.dough_nudge_range.addEventListener("input", () => {
|
app.interface.dough_nudge_range.addEventListener("input", () => {
|
||||||
app.dough_nudge = parseInt(
|
app.dough_nudge = parseInt(
|
||||||
(app.interface.dough_nudge_range as HTMLInputElement).value
|
(app.interface.dough_nudge_range as HTMLInputElement).value,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.interface.upload_samples_button.addEventListener("input", async (event: Event) => {
|
||||||
|
let fileInput = event.target as HTMLInputElement;
|
||||||
|
if (!fileInput.files?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
app.interface.sample_indicator.innerText = "Loading...";
|
||||||
|
app.interface.sample_indicator.classList.add("animate-pulse");
|
||||||
|
await uploadSamplesToDB(samplesDBConfig, fileInput.files).then(() => {
|
||||||
|
registerSamplesFromDB(samplesDBConfig, () => {
|
||||||
|
app.interface.sample_indicator.innerText = "Import samples";
|
||||||
|
app.interface.sample_indicator.classList.remove("animate-pulse");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.interface.upload_universe_button.addEventListener("click", () => {
|
app.interface.upload_universe_button.addEventListener("click", () => {
|
||||||
const fileInput = document.createElement("input");
|
const fileInput = document.createElement("input");
|
||||||
fileInput.type = "file";
|
fileInput.type = "file";
|
||||||
@ -218,25 +221,17 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
app.flashBackground("#404040", 200);
|
app.flashBackground("#404040", 200);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.buttonElements.stop_buttons.forEach((button) => {
|
|
||||||
button.addEventListener("click", () => {
|
|
||||||
app.setButtonHighlighting("stop", true);
|
|
||||||
app.isPlaying = false;
|
|
||||||
app.clock.stop();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.interface.local_button.addEventListener("click", () =>
|
app.interface.local_button.addEventListener("click", () =>
|
||||||
app.changeModeFromInterface("local")
|
app.changeModeFromInterface("local"),
|
||||||
);
|
);
|
||||||
app.interface.global_button.addEventListener("click", () =>
|
app.interface.global_button.addEventListener("click", () =>
|
||||||
app.changeModeFromInterface("global")
|
app.changeModeFromInterface("global"),
|
||||||
);
|
);
|
||||||
app.interface.init_button.addEventListener("click", () =>
|
app.interface.init_button.addEventListener("click", () =>
|
||||||
app.changeModeFromInterface("init")
|
app.changeModeFromInterface("init"),
|
||||||
);
|
);
|
||||||
app.interface.note_button.addEventListener("click", () =>
|
app.interface.note_button.addEventListener("click", () =>
|
||||||
app.changeModeFromInterface("notes")
|
app.changeModeFromInterface("notes"),
|
||||||
);
|
);
|
||||||
|
|
||||||
app.interface.font_family_selector.addEventListener("change", () => {
|
app.interface.font_family_selector.addEventListener("change", () => {
|
||||||
@ -255,7 +250,7 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
fontSize: app.settings.font_size + "px",
|
fontSize: app.settings.font_size + "px",
|
||||||
},
|
},
|
||||||
".cm-gutters": { fontSize: app.settings.font_size + "px" },
|
".cm-gutters": { fontSize: app.settings.font_size + "px" },
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -275,26 +270,51 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
fontSize: app.settings.font_size + "px",
|
fontSize: app.settings.font_size + "px",
|
||||||
},
|
},
|
||||||
".cm-gutters": { fontSize: app.settings.font_size + "px" },
|
".cm-gutters": { fontSize: app.settings.font_size + "px" },
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.interface.theme_selector.addEventListener("change", () => {
|
||||||
|
app.settings.theme = (app.interface.theme_selector as HTMLSelectElement).value;
|
||||||
|
app.readTheme(app.settings.theme);
|
||||||
|
// @ts-ignore
|
||||||
|
let selected_theme = colors[app.settings.theme as string];
|
||||||
|
let theme_preview = "";
|
||||||
|
for (const [key, _] of Object.entries(selected_theme)) {
|
||||||
|
theme_preview += `<p class="inline text-${key} bg-${key}">█</div>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.interface.settings_button.addEventListener("click", () => {
|
app.interface.settings_button.addEventListener("click", () => {
|
||||||
// Populate the font selector
|
// Populate the font selector
|
||||||
const fontFamilySelect = document.getElementById(
|
const fontFamilySelect = document.getElementById(
|
||||||
"font-family"
|
"font-family",
|
||||||
) as HTMLSelectElement | null;
|
) as HTMLSelectElement | null;
|
||||||
if (fontFamilySelect) {
|
if (fontFamilySelect) {
|
||||||
fontFamilySelect.value = app.settings.font;
|
fontFamilySelect.value = app.settings.font;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.interface.theme_selector.innerHTML = "";
|
||||||
|
let all_themes = Object.keys(colors);
|
||||||
|
all_themes.sort((a, b) => {
|
||||||
|
return a.toLowerCase().localeCompare(b.toLowerCase());
|
||||||
|
});
|
||||||
|
app.interface.theme_selector.innerHTML = all_themes.map((color) => {
|
||||||
|
return `<option value="${color}">${color}</option>`
|
||||||
|
}).join("");
|
||||||
|
// Set the selected theme in the selector to app.settings.theme
|
||||||
|
// @ts-ignore
|
||||||
|
app.interface.theme_selector.value = app.settings.theme;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
let selected_theme = colors[app.settings.theme as string];
|
||||||
// Populate the font family selector
|
// Populate the font family selector
|
||||||
const doughNudgeRange = app.interface.dough_nudge_range as HTMLInputElement;
|
const doughNudgeRange = app.interface.dough_nudge_range as HTMLInputElement;
|
||||||
doughNudgeRange.value = app.dough_nudge.toString();
|
doughNudgeRange.value = app.dough_nudge.toString();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const doughNumber = document.getElementById(
|
const doughNumber = document.getElementById(
|
||||||
"doughnumber"
|
"doughnumber",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
doughNumber.value = app.dough_nudge.toString();
|
doughNumber.value = app.dough_nudge.toString();
|
||||||
if (app.settings.font_size === null) {
|
if (app.settings.font_size === null) {
|
||||||
@ -320,8 +340,8 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
midiChannelsScripts.checked = app.settings.midi_channels_scripts;
|
midiChannelsScripts.checked = app.settings.midi_channels_scripts;
|
||||||
const midiClockPpqn = app.interface.midi_clock_ppqn as HTMLInputElement;
|
const midiClockPpqn = app.interface.midi_clock_ppqn as HTMLInputElement;
|
||||||
midiClockPpqn.value = app.settings.midi_clock_ppqn.toString();
|
midiClockPpqn.value = app.settings.midi_clock_ppqn.toString();
|
||||||
const loadDemoSongs = app.interface.load_demo_songs as HTMLInputElement;
|
// const loadDemoSongs = app.interface.load_demo_songs as HTMLInputElement;
|
||||||
loadDemoSongs.checked = app.settings.load_demo_songs;
|
// loadDemoSongs.checked = app.settings.load_demo_songs;
|
||||||
const vimModeCheckbox = app.interface.vim_mode_checkbox as HTMLInputElement;
|
const vimModeCheckbox = app.interface.vim_mode_checkbox as HTMLInputElement;
|
||||||
vimModeCheckbox.checked = app.settings.vimMode;
|
vimModeCheckbox.checked = app.settings.vimMode;
|
||||||
|
|
||||||
@ -350,7 +370,7 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
fontSize: app.settings.font_size + "px",
|
fontSize: app.settings.font_size + "px",
|
||||||
},
|
},
|
||||||
".cm-gutters": { fontSize: app.settings.font_size + "px" },
|
".cm-gutters": { fontSize: app.settings.font_size + "px" },
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -389,18 +409,6 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.interface.time_position_checkbox.addEventListener("change", () => {
|
|
||||||
let timeviewer = document.getElementById("timeviewer") as HTMLElement;
|
|
||||||
let checked = (app.interface.time_position_checkbox as HTMLInputElement)
|
|
||||||
.checked
|
|
||||||
? true
|
|
||||||
: false;
|
|
||||||
app.settings.time_position = checked;
|
|
||||||
checked
|
|
||||||
? timeviewer.classList.remove("hidden")
|
|
||||||
: timeviewer.classList.add("hidden");
|
|
||||||
});
|
|
||||||
|
|
||||||
app.interface.tips_checkbox.addEventListener("change", () => {
|
app.interface.tips_checkbox.addEventListener("change", () => {
|
||||||
let checked = (app.interface.tips_checkbox as HTMLInputElement).checked
|
let checked = (app.interface.tips_checkbox as HTMLInputElement).checked
|
||||||
? true
|
? true
|
||||||
@ -408,7 +416,7 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
app.settings.tips = checked;
|
app.settings.tips = checked;
|
||||||
app.view.dispatch({
|
app.view.dispatch({
|
||||||
effects: app.hoveringCompartment.reconfigure(
|
effects: app.hoveringCompartment.reconfigure(
|
||||||
checked ? inlineHoveringTips : []
|
checked ? inlineHoveringTips : [],
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -421,7 +429,7 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
app.settings.completions = checked;
|
app.settings.completions = checked;
|
||||||
app.view.dispatch({
|
app.view.dispatch({
|
||||||
effects: app.completionsCompartment.reconfigure(
|
effects: app.completionsCompartment.reconfigure(
|
||||||
checked ? jsCompletions : []
|
checked ? jsCompletions : [],
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -444,19 +452,19 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
|
|
||||||
app.interface.midi_clock_ppqn.addEventListener("change", () => {
|
app.interface.midi_clock_ppqn.addEventListener("change", () => {
|
||||||
let value = parseInt(
|
let value = parseInt(
|
||||||
(app.interface.midi_clock_ppqn as HTMLInputElement).value
|
(app.interface.midi_clock_ppqn as HTMLInputElement).value,
|
||||||
);
|
);
|
||||||
app.settings.midi_clock_ppqn = value;
|
app.settings.midi_clock_ppqn = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
app.interface.load_demo_songs.addEventListener("change", () => {
|
// app.interface.load_demo_songs.addEventListener("change", () => {
|
||||||
let checked = (app.interface.load_demo_songs as HTMLInputElement).checked
|
// let checked = (app.interface.load_demo_songs as HTMLInputElement).checked
|
||||||
? true
|
// ? true
|
||||||
: false;
|
// : false;
|
||||||
app.settings.load_demo_songs = checked;
|
// app.settings.load_demo_songs = checked;
|
||||||
});
|
// });
|
||||||
|
|
||||||
app.interface.universe_creator.addEventListener("submit", (event) => {
|
app.interface.universe_creator.addEventListener("submit", (event: Event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
let data = new FormData(app.interface.universe_creator as HTMLFormElement);
|
let data = new FormData(app.interface.universe_creator as HTMLFormElement);
|
||||||
@ -475,53 +483,71 @@ export const installInterfaceLogic = (app: Editor) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tryEvaluate(app, app.universes[app.selected_universe.toString()].init);
|
tryEvaluate(app, app.universes[app.selected_universe.toString()]!.init);
|
||||||
|
|
||||||
[
|
documentation_pages.forEach((e) => {
|
||||||
"introduction",
|
|
||||||
"sampler",
|
|
||||||
"amplitude",
|
|
||||||
"audio_basics",
|
|
||||||
"reverb_delay",
|
|
||||||
"interface",
|
|
||||||
"interaction",
|
|
||||||
"code",
|
|
||||||
"time",
|
|
||||||
"linear",
|
|
||||||
"cyclic",
|
|
||||||
"longform",
|
|
||||||
// "sound",
|
|
||||||
"synths",
|
|
||||||
"chaining",
|
|
||||||
"patterns",
|
|
||||||
"ziffers",
|
|
||||||
"midi",
|
|
||||||
"functions",
|
|
||||||
"lfos",
|
|
||||||
"probabilities",
|
|
||||||
"variables",
|
|
||||||
"synchronisation",
|
|
||||||
"mouse",
|
|
||||||
"shortcuts",
|
|
||||||
"about",
|
|
||||||
"bonus",
|
|
||||||
"oscilloscope",
|
|
||||||
"sample_list",
|
|
||||||
"loading_samples",
|
|
||||||
].forEach((e) => {
|
|
||||||
let name = `docs_` + e;
|
let name = `docs_` + e;
|
||||||
document.getElementById(name)!.addEventListener("click", async () => {
|
|
||||||
if (name !== "docs_samples") {
|
// Check if the element exists
|
||||||
|
let element = document.getElementById(name);
|
||||||
|
if (element) {
|
||||||
|
element.addEventListener("click", async () => {
|
||||||
|
// Clear query params & set id as hash paremeter for uri
|
||||||
|
window.history.replaceState({}, "", window.location.pathname);
|
||||||
|
window.location.hash = e;
|
||||||
|
app.docs = documentation_factory(app);
|
||||||
app.currentDocumentationPane = e;
|
app.currentDocumentationPane = e;
|
||||||
updateDocumentationContent(app, bindings);
|
if (name !== "docs_sample_list") {
|
||||||
|
updateDocumentationContent(app, app.bindings);
|
||||||
} else {
|
} else {
|
||||||
console.log("Loading samples!");
|
console.log("Loading samples!");
|
||||||
await loadSamples().then(() => {
|
await loadSamples().then(() => {
|
||||||
app.docs = documentation_factory(app);
|
updateDocumentationContent(app, app.bindings);
|
||||||
app.currentDocumentationPane = e;
|
|
||||||
updateDocumentationContent(app, bindings);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
console.log("Could not find element " + name);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updatePlayButton = (app: Editor) => {
|
||||||
|
switch (app.clock.state) {
|
||||||
|
case 'stopped':
|
||||||
|
app.interface.play_label.innerText = "Play";
|
||||||
|
updatePlayPauseIcon(app, "play");
|
||||||
|
break;
|
||||||
|
case 'paused':
|
||||||
|
app.interface.play_label.innerText = "Resume";
|
||||||
|
updatePlayPauseIcon(app, "play");
|
||||||
|
break;
|
||||||
|
case 'running':
|
||||||
|
app.interface.play_label.innerText = "Pause";
|
||||||
|
updatePlayPauseIcon(app, "pause");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updatePlayPauseIcon = (app: Editor, state: "play" | "pause"): void => {
|
||||||
|
const { play_icon, pause_icon } = app.interface;
|
||||||
|
|
||||||
|
const isPlayIconHidden = play_icon.classList.contains("hidden");
|
||||||
|
const isPauseIconHidden = pause_icon.classList.contains("hidden");
|
||||||
|
|
||||||
|
if (state === "play" && isPlayIconHidden) {
|
||||||
|
play_icon.classList.remove("hidden");
|
||||||
|
pause_icon.classList.add("hidden");
|
||||||
|
} else if (state === "pause" && isPauseIconHidden) {
|
||||||
|
play_icon.classList.add("hidden");
|
||||||
|
pause_icon.classList.remove("hidden");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const resetTransportView = (app: Editor) => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
app.interface.transport_viewer.innerHTML = `<span class="text-xl text-neutral">00:00:00</span>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
122
src/DOM/Visuals/Blinkers.ts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { type Editor } from "../../main";
|
||||||
|
|
||||||
|
const HORIZONTALOFFSETPERCENT = 0.025;
|
||||||
|
const VERTICALOFFSETPERCENT = 0.025;
|
||||||
|
const RADIUSPERCENT = 0.010;
|
||||||
|
const SHIFTPERCENT = 0.025;
|
||||||
|
|
||||||
|
export const drawCircle = (
|
||||||
|
/**
|
||||||
|
* Draw a circle at a specific position on the canvas.
|
||||||
|
* @param {number} x - The x-coordinate of the circle's center.
|
||||||
|
* @param {number} y - The y-coordinate of the circle's center.
|
||||||
|
* @param {number} radius - The radius of the circle.
|
||||||
|
* @param {string} color - The fill color of the circle.
|
||||||
|
*/
|
||||||
|
app: Editor,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
radius: number,
|
||||||
|
color: string,
|
||||||
|
): void => {
|
||||||
|
// @ts-ignore
|
||||||
|
const canvas: HTMLCanvasElement = app.interface.feedback;
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
console.log(`Canvas size: ${canvas.width}x${canvas.height}`);
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
ctx.fill();
|
||||||
|
ctx.closePath();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const blinkScript = (
|
||||||
|
/**
|
||||||
|
* Blinks a script indicator circle.
|
||||||
|
* @param script - The type of script.
|
||||||
|
* @param no - The shift amount multiplier.
|
||||||
|
*/
|
||||||
|
app: Editor,
|
||||||
|
script: "local" | "global" | "init",
|
||||||
|
no?: number,
|
||||||
|
) => {
|
||||||
|
if (no !== undefined && no < 1 && no > 9) return;
|
||||||
|
const blinkDuration =
|
||||||
|
(app.clock.bpm / 60 / app.clock.time_position.num) * 200;
|
||||||
|
// @ts-ignore
|
||||||
|
const ctx = app.interface.feedback.getContext("2d");
|
||||||
|
|
||||||
|
const _drawBlinker = (shift: number) => {
|
||||||
|
const horizontalOffsetPercent = HORIZONTALOFFSETPERCENT;
|
||||||
|
const verticalOffsetPercent = VERTICALOFFSETPERCENT;
|
||||||
|
const radiusPercent = RADIUSPERCENT;
|
||||||
|
drawCircle(
|
||||||
|
app,
|
||||||
|
(app.interface.feedback as HTMLCanvasElement).width * horizontalOffsetPercent + shift,
|
||||||
|
(app.interface.feedback as HTMLCanvasElement).height * (1 - verticalOffsetPercent),
|
||||||
|
(app.interface.feedback as HTMLCanvasElement).width * radiusPercent,
|
||||||
|
"#fdba74",
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _clearBlinker = (shift: number) => {
|
||||||
|
const horizontalOffsetPercent = HORIZONTALOFFSETPERCENT;
|
||||||
|
const verticalOffsetPercent = VERTICALOFFSETPERCENT;
|
||||||
|
const radiusPercent = RADIUSPERCENT;
|
||||||
|
const x = (app.interface.feedback as HTMLCanvasElement).width * horizontalOffsetPercent + shift;
|
||||||
|
const y = (app.interface.feedback as HTMLCanvasElement).height * (1 - verticalOffsetPercent);
|
||||||
|
const radius = (app.interface.feedback as HTMLCanvasElement).width * radiusPercent;
|
||||||
|
ctx.clearRect(x - radius, y - radius, radius * 2, radius * 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (script === "local" && no !== undefined) {
|
||||||
|
const shiftPercent = SHIFTPERCENT;
|
||||||
|
const shiftAmount = no * (app.interface.feedback as HTMLCanvasElement).width * shiftPercent;
|
||||||
|
|
||||||
|
// Clear existing timeout if any
|
||||||
|
if (app.blinkTimeouts[shiftAmount]) {
|
||||||
|
clearTimeout(app.blinkTimeouts[shiftAmount]);
|
||||||
|
}
|
||||||
|
|
||||||
|
_drawBlinker(shiftAmount);
|
||||||
|
|
||||||
|
// Save timeout ID for later clearing
|
||||||
|
// @ts-ignore
|
||||||
|
app.blinkTimeouts[shiftAmount] = setTimeout(() => {
|
||||||
|
_clearBlinker(shiftAmount);
|
||||||
|
// Clear the canvas before drawing new blinkers
|
||||||
|
(app.interface.feedback as HTMLCanvasElement)
|
||||||
|
.getContext("2d")!
|
||||||
|
.clearRect(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
(app.interface.feedback as HTMLCanvasElement).width,
|
||||||
|
(app.interface.feedback as HTMLCanvasElement).height,
|
||||||
|
);
|
||||||
|
}, blinkDuration);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const scriptBlinkers = () => {
|
||||||
|
/**
|
||||||
|
* Manages animation updates using requestAnimationFrame.
|
||||||
|
* @param app - The Editor application context.
|
||||||
|
*/
|
||||||
|
|
||||||
|
let lastFrameTime = Date.now();
|
||||||
|
const frameRate = 10;
|
||||||
|
const minFrameDelay = 1000 / frameRate;
|
||||||
|
|
||||||
|
const update = () => {
|
||||||
|
const now = Date.now();
|
||||||
|
const timeSinceLastFrame = now - lastFrameTime;
|
||||||
|
|
||||||
|
if (timeSinceLastFrame >= minFrameDelay) {
|
||||||
|
lastFrameTime = now;
|
||||||
|
}
|
||||||
|
requestAnimationFrame(update);
|
||||||
|
};
|
||||||
|
requestAnimationFrame(update);
|
||||||
|
};
|
||||||
562
src/DOM/Visuals/CanvasVisuals.ts
Normal file
@ -0,0 +1,562 @@
|
|||||||
|
export const drawBackground = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
color: string | number,
|
||||||
|
...gb: number[]
|
||||||
|
): void => {
|
||||||
|
/**
|
||||||
|
* Set background color of the canvas.
|
||||||
|
* @param color - The color to set. String or 3 numbers representing RGB values.
|
||||||
|
*/
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
if (typeof color === "number") color = `rgb(${color},${gb[0]},${gb[1]})`;
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createLinearGradient = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
x1: number,
|
||||||
|
y1: number,
|
||||||
|
x2: number,
|
||||||
|
y2: number,
|
||||||
|
...stops: (number | string)[]
|
||||||
|
): CanvasGradient => {
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
const gradient = ctx.createLinearGradient(x1, y1, x2, y2);
|
||||||
|
// Parse pairs of values from stops
|
||||||
|
for (let i = 0; i < stops.length; i += 2) {
|
||||||
|
let color = stops[i + 1];
|
||||||
|
if (typeof color === "number")
|
||||||
|
color = `rgb(${color},${stops[i + 2]},${stops[i + 3]})`;
|
||||||
|
gradient.addColorStop(stops[i] as number, color as string);
|
||||||
|
}
|
||||||
|
return gradient;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createRadialGradient = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
x1: number,
|
||||||
|
y1: number,
|
||||||
|
r1: number,
|
||||||
|
x2: number,
|
||||||
|
y2: number,
|
||||||
|
r2: number,
|
||||||
|
...stops: (number | string)[]
|
||||||
|
) => {
|
||||||
|
/**
|
||||||
|
* Set radial gradient on the canvas.
|
||||||
|
* @param x1 - The x-coordinate of the start circle
|
||||||
|
* @param y1 - The y-coordinate of the start circle
|
||||||
|
* @param r1 - The radius of the start circle
|
||||||
|
* @param x2 - The x-coordinate of the end circle
|
||||||
|
* @param y2 - The y-coordinate of the end circle
|
||||||
|
* @param r2 - The radius of the end circle
|
||||||
|
* @param stops - The stops to set. Pairs of numbers representing the position and color of the stop.
|
||||||
|
*/
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
const gradient = ctx.createRadialGradient(x1, y1, r1, x2, y2, r2);
|
||||||
|
for (let i = 0; i < stops.length; i += 2) {
|
||||||
|
let color = stops[i + 1];
|
||||||
|
if (typeof color === "number")
|
||||||
|
color = `rgb(${color},${stops[i + 2]},${stops[i + 3]})`;
|
||||||
|
gradient.addColorStop(stops[i] as number, color as string);
|
||||||
|
}
|
||||||
|
return gradient;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createConicGradient = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
angle: number,
|
||||||
|
...stops: (number | string)[]
|
||||||
|
) => {
|
||||||
|
/**
|
||||||
|
* Set conic gradient on the canvas.
|
||||||
|
* @param x - The x-coordinate of the center of the gradient
|
||||||
|
* @param y - The y-coordinate of the center of the gradient
|
||||||
|
* @param angle - The angle of the gradient, in radians
|
||||||
|
* @param stops - The stops to set. Pairs of numbers representing the position and color of the stop.
|
||||||
|
*/
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
const gradient = ctx.createConicGradient(x, y, angle);
|
||||||
|
for (let i = 0; i < stops.length; i += 2) {
|
||||||
|
let color = stops[i + 1];
|
||||||
|
if (typeof color === "number")
|
||||||
|
color = `rgb(${color},${stops[i + 2]},${stops[i + 3]})`;
|
||||||
|
gradient.addColorStop(stops[i] as number, color as string);
|
||||||
|
}
|
||||||
|
return gradient;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawGradientImage = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
time: number = 666
|
||||||
|
) => {
|
||||||
|
/* TODO: This works but is really resource heavy. Should do method for requestAnimationFrame? */
|
||||||
|
const context = canvas.getContext("2d")!;
|
||||||
|
const { width, height } = context.canvas;
|
||||||
|
const imageData = context.getImageData(0, 0, width, height);
|
||||||
|
|
||||||
|
for (let p = 0; p < imageData.data.length; p += 4) {
|
||||||
|
const i = p / 4;
|
||||||
|
const x = i % width;
|
||||||
|
const y = (i / width) >>> 0;
|
||||||
|
|
||||||
|
const red = 64 + (128 * x) / width + 64 * Math.sin(time / 1000);
|
||||||
|
const green = 64 + (128 * y) / height + 64 * Math.cos(time / 1000);
|
||||||
|
const blue = 128;
|
||||||
|
|
||||||
|
imageData.data[p + 0] = red;
|
||||||
|
imageData.data[p + 1] = green;
|
||||||
|
imageData.data[p + 2] = blue;
|
||||||
|
imageData.data[p + 3] = 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.putImageData(imageData, 0, 0);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawBalloid = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
curves: number,
|
||||||
|
radius: number,
|
||||||
|
curve: number,
|
||||||
|
fillStyle: string,
|
||||||
|
secondary: string,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
): void => {
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
|
||||||
|
// Draw the shape using quadratic Bézier curves
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.fillStyle = fillStyle;
|
||||||
|
|
||||||
|
if (curves === 0) {
|
||||||
|
// Draw a circle if curves = 0
|
||||||
|
ctx.arc(x, y, radius, 0, 2 * Math.PI);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
} else if (curves === 1) {
|
||||||
|
// Draw a single curve (ellipse) if curves = 1
|
||||||
|
ctx.ellipse(x, y, radius * 0.8, radius * curve * 0.7, 0, 0, 2 * Math.PI);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
} else if (curves === 2) {
|
||||||
|
// Draw a shape with two symmetric curves starting from the top and meeting at the bottom
|
||||||
|
ctx.moveTo(x, y - radius);
|
||||||
|
|
||||||
|
// First curve
|
||||||
|
ctx.quadraticCurveTo(x + radius * curve, y, x, y + radius);
|
||||||
|
|
||||||
|
// Second symmetric curve
|
||||||
|
ctx.quadraticCurveTo(x - radius * curve, y, x, y - radius);
|
||||||
|
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
} else {
|
||||||
|
// Draw the curved shape with the specified number of curves
|
||||||
|
ctx.moveTo(x, y - radius);
|
||||||
|
let points = [];
|
||||||
|
for (let i = 0; i < curves; i++) {
|
||||||
|
const startAngle = (i / curves) * 2 * Math.PI;
|
||||||
|
const endAngle = startAngle + (2 * Math.PI) / curves;
|
||||||
|
|
||||||
|
const controlX =
|
||||||
|
x + radius * curve * Math.cos(startAngle + Math.PI / curves);
|
||||||
|
const controlY =
|
||||||
|
y + radius * curve * Math.sin(startAngle + Math.PI / curves);
|
||||||
|
points.push([
|
||||||
|
x + radius * Math.cos(startAngle),
|
||||||
|
y + radius * Math.sin(startAngle),
|
||||||
|
]);
|
||||||
|
ctx.moveTo(
|
||||||
|
x + radius * Math.cos(startAngle),
|
||||||
|
y + radius * Math.sin(startAngle)
|
||||||
|
);
|
||||||
|
ctx.quadraticCurveTo(
|
||||||
|
controlX,
|
||||||
|
controlY,
|
||||||
|
x + radius * Math.cos(endAngle),
|
||||||
|
y + radius * Math.sin(endAngle)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.fillStyle = secondary;
|
||||||
|
// Form the shape from points with straight lines and fill it
|
||||||
|
if (points[0]) {
|
||||||
|
ctx.moveTo(points[0][0] as number, points[0][1] as number);
|
||||||
|
for (let point of points) ctx.lineTo(point[0] as number, point[1] as number);
|
||||||
|
}
|
||||||
|
// Close and fill
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawEquilateral = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
radius: number,
|
||||||
|
fillStyle: string,
|
||||||
|
rotation: number,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
): void => {
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(x, y);
|
||||||
|
ctx.rotate((rotation * Math.PI) / 180);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, -radius);
|
||||||
|
ctx.lineTo(radius, radius);
|
||||||
|
ctx.lineTo(-radius, radius);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fillStyle = fillStyle;
|
||||||
|
ctx.fill();
|
||||||
|
ctx.restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawTriangular = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
fillStyle: string,
|
||||||
|
rotation: number,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
): void => {
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(x, y);
|
||||||
|
ctx.rotate((rotation * Math.PI) / 180);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, -height);
|
||||||
|
ctx.lineTo(width, height);
|
||||||
|
ctx.lineTo(-width, height);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fillStyle = fillStyle;
|
||||||
|
ctx.fill();
|
||||||
|
ctx.restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawBall = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
radius: number,
|
||||||
|
fillStyle: string,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
): void => {
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, y, radius, 0, 2 * Math.PI);
|
||||||
|
ctx.fillStyle = fillStyle;
|
||||||
|
ctx.fill();
|
||||||
|
ctx.closePath();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawDonut = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
slices: number,
|
||||||
|
eaten: number,
|
||||||
|
radius: number,
|
||||||
|
hole: number,
|
||||||
|
fillStyle: string,
|
||||||
|
secondary: string,
|
||||||
|
stroke: string,
|
||||||
|
rotation: number,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
): void => {
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(x, y);
|
||||||
|
ctx.rotate((rotation * Math.PI) / 180);
|
||||||
|
|
||||||
|
if (slices < 2) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fillStyle = slices < 1 ? secondary : fillStyle;
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, hole, 0, 2 * Math.PI);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fillStyle = secondary;
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw slices as arcs
|
||||||
|
const totalSlices = slices;
|
||||||
|
const sliceAngle = (2 * Math.PI) / totalSlices;
|
||||||
|
for (let i = 0; i < totalSlices; i++) {
|
||||||
|
const startAngle = i * sliceAngle;
|
||||||
|
const endAngle = (i + 1) * sliceAngle;
|
||||||
|
|
||||||
|
// Calculate the position of the outer arc
|
||||||
|
const outerStartX = hole * Math.cos(startAngle);
|
||||||
|
const outerStartY = hole * Math.sin(startAngle);
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(outerStartX, outerStartY);
|
||||||
|
ctx.arc(0, 0, radius, startAngle, endAngle);
|
||||||
|
ctx.arc(0, 0, hole, endAngle, startAngle, true);
|
||||||
|
ctx.closePath();
|
||||||
|
|
||||||
|
// Fill and stroke the slices with the specified fill style
|
||||||
|
if (i < slices - eaten) {
|
||||||
|
// Regular slices are white
|
||||||
|
ctx.fillStyle = fillStyle;
|
||||||
|
} else {
|
||||||
|
// Missing slices are black
|
||||||
|
ctx.fillStyle = secondary;
|
||||||
|
}
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.fill();
|
||||||
|
ctx.strokeStyle = stroke;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawPie = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
slices: number,
|
||||||
|
eaten: number,
|
||||||
|
radius: number,
|
||||||
|
fillStyle: string,
|
||||||
|
secondary: string,
|
||||||
|
stroke: string,
|
||||||
|
rotation: number,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
): void => {
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(x, y);
|
||||||
|
ctx.rotate((rotation * Math.PI) / 180);
|
||||||
|
|
||||||
|
if (slices < 2) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fillStyle = slices < 1 ? secondary : fillStyle;
|
||||||
|
ctx.fill();
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw slices as arcs
|
||||||
|
const totalSlices = slices;
|
||||||
|
const sliceAngle = (2 * Math.PI) / totalSlices;
|
||||||
|
for (let i = 0; i < totalSlices; i++) {
|
||||||
|
const startAngle = i * sliceAngle;
|
||||||
|
const endAngle = (i + 1) * sliceAngle;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, 0);
|
||||||
|
ctx.arc(0, 0, radius, startAngle, endAngle);
|
||||||
|
ctx.lineTo(0, 0); // Connect to center
|
||||||
|
ctx.closePath();
|
||||||
|
|
||||||
|
// Fill and stroke the slices with the specified fill style
|
||||||
|
if (i < slices - eaten) {
|
||||||
|
// Regular slices are white
|
||||||
|
ctx.fillStyle = fillStyle;
|
||||||
|
} else {
|
||||||
|
// Missing slices are black
|
||||||
|
ctx.fillStyle = secondary;
|
||||||
|
}
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.fill();
|
||||||
|
ctx.strokeStyle = stroke;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawStar = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
points: number,
|
||||||
|
radius: number,
|
||||||
|
fillStyle: string,
|
||||||
|
rotation: number,
|
||||||
|
outerRadius: number,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
): void => {
|
||||||
|
if (points < 1) return drawBall(canvas, radius, fillStyle, x, y);
|
||||||
|
if (points == 1) return drawEquilateral(canvas, radius, fillStyle, 0, x, y);
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(x, y);
|
||||||
|
ctx.rotate((rotation * Math.PI) / 180);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, -radius);
|
||||||
|
for (let i = 0; i < points; i++) {
|
||||||
|
ctx.rotate(Math.PI / points);
|
||||||
|
ctx.lineTo(0, -(radius * outerRadius));
|
||||||
|
ctx.rotate(Math.PI / points);
|
||||||
|
ctx.lineTo(0, -radius);
|
||||||
|
}
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fillStyle = fillStyle;
|
||||||
|
ctx.fill();
|
||||||
|
ctx.restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawStroke = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
width: number,
|
||||||
|
strokeStyle: string,
|
||||||
|
rotation: number = 0,
|
||||||
|
x1: number,
|
||||||
|
y1: number,
|
||||||
|
x2: number,
|
||||||
|
y2: number
|
||||||
|
): void => {
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(x1, y1);
|
||||||
|
ctx.rotate((rotation * Math.PI) / 180);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, 0);
|
||||||
|
ctx.lineTo(x2 - x1, y2 - y1);
|
||||||
|
ctx.lineWidth = width;
|
||||||
|
ctx.strokeStyle = strokeStyle;
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawBox = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
fillStyle: string,
|
||||||
|
rotation: number,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
): void => {
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(x, y);
|
||||||
|
ctx.rotate((rotation * Math.PI) / 180);
|
||||||
|
ctx.fillStyle = fillStyle;
|
||||||
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
ctx.restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawSmiley = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
happiness: number,
|
||||||
|
radius: number,
|
||||||
|
eyeSize: number,
|
||||||
|
fillStyle: string,
|
||||||
|
rotation: number,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
): void => {
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
// Map the rotation value to an angle within the range of -PI to PI
|
||||||
|
const rotationAngle = (rotation / 100) * Math.PI;
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(x, y);
|
||||||
|
ctx.rotate(rotationAngle);
|
||||||
|
|
||||||
|
// Draw face
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
|
||||||
|
ctx.fillStyle = fillStyle;
|
||||||
|
ctx.fill();
|
||||||
|
ctx.lineWidth = radius / 20;
|
||||||
|
ctx.strokeStyle = "black";
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Draw eyes
|
||||||
|
const eyeY = -radius / 5;
|
||||||
|
const eyeXOffset = radius / 2.5;
|
||||||
|
const eyeRadiusX = radius / 8;
|
||||||
|
const eyeRadiusY = (eyeSize * radius) / 10;
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(-eyeXOffset, eyeY, eyeRadiusX, eyeRadiusY, 0, 0, 2 * Math.PI);
|
||||||
|
ctx.fillStyle = "black";
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(eyeXOffset, eyeY, eyeRadiusX, eyeRadiusY, 0, 0, 2 * Math.PI);
|
||||||
|
ctx.fillStyle = "black";
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Draw mouth with happiness number -1.0 to 1.0. 0.0 Should be a straight line.
|
||||||
|
const mouthY = radius / 2;
|
||||||
|
const mouthLength = radius * 0.9;
|
||||||
|
const smileFactor = 0.25; // Adjust for the smile curvature
|
||||||
|
|
||||||
|
let controlPointX = 0;
|
||||||
|
let controlPointY = 0;
|
||||||
|
|
||||||
|
if (happiness >= 0) {
|
||||||
|
controlPointY = mouthY + (happiness * smileFactor * radius) / 2;
|
||||||
|
} else {
|
||||||
|
controlPointY = mouthY + (happiness * smileFactor * radius) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(-mouthLength / 2, mouthY);
|
||||||
|
ctx.quadraticCurveTo(controlPointX, controlPointY, mouthLength / 2, mouthY);
|
||||||
|
ctx.lineWidth = 10;
|
||||||
|
ctx.strokeStyle = "black";
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawText = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
text: string,
|
||||||
|
fontSize: number,
|
||||||
|
rotation: number,
|
||||||
|
font: string,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
fillStyle: string,
|
||||||
|
filter: string
|
||||||
|
): void => {
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(x, y);
|
||||||
|
ctx.rotate((rotation * Math.PI) / 180);
|
||||||
|
ctx.filter = filter;
|
||||||
|
ctx.font = `${fontSize}px ${font}`;
|
||||||
|
ctx.fillStyle = fillStyle;
|
||||||
|
ctx.fillText(text, 0, 0);
|
||||||
|
ctx.restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawImage = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
url: string,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
rotation: number,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
filter: string = "none"
|
||||||
|
): void => {
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(x, y);
|
||||||
|
ctx.rotate((rotation * Math.PI) / 180);
|
||||||
|
ctx.filter = filter;
|
||||||
|
const image = new Image();
|
||||||
|
image.src = url;
|
||||||
|
ctx.drawImage(image, -width / 2, -height / 2, width, height);
|
||||||
|
ctx.restore();
|
||||||
|
};
|
||||||
@ -1,122 +1,6 @@
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { getAnalyser } from "superdough";
|
import { getAnalyser } from "superdough";
|
||||||
import { type Editor } from "./main";
|
import { Editor } from "../../main";
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw a circle at a specific position on the canvas.
|
|
||||||
* @param {number} x - The x-coordinate of the circle's center.
|
|
||||||
* @param {number} y - The y-coordinate of the circle's center.
|
|
||||||
* @param {number} radius - The radius of the circle.
|
|
||||||
* @param {string} color - The fill color of the circle.
|
|
||||||
*/
|
|
||||||
export const drawCircle = (
|
|
||||||
app: Editor,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
radius: number,
|
|
||||||
color: string
|
|
||||||
): void => {
|
|
||||||
// @ts-ignore
|
|
||||||
const canvas: HTMLCanvasElement = app.interface.feedback;
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
if (!ctx) return;
|
|
||||||
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
||||||
ctx.fillStyle = color;
|
|
||||||
ctx.fill();
|
|
||||||
ctx.closePath();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Blinks a script indicator circle.
|
|
||||||
* @param script - The type of script.
|
|
||||||
* @param no - The shift amount multiplier.
|
|
||||||
*/
|
|
||||||
export const blinkScript = (
|
|
||||||
app: Editor,
|
|
||||||
script: "local" | "global" | "init",
|
|
||||||
no?: number
|
|
||||||
) => {
|
|
||||||
if (no !== undefined && no < 1 && no > 9) return;
|
|
||||||
const blinkDuration =
|
|
||||||
(app.clock.bpm / 60 / app.clock.time_signature[1]) * 200;
|
|
||||||
// @ts-ignore
|
|
||||||
const ctx = app.interface.feedback.getContext("2d"); // Assuming a canvas context
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws a circle at a given shift.
|
|
||||||
* @param shift - The pixel distance from the origin.
|
|
||||||
*/
|
|
||||||
const _drawBlinker = (shift: number) => {
|
|
||||||
const horizontalOffset = 50;
|
|
||||||
drawCircle(
|
|
||||||
app,
|
|
||||||
horizontalOffset + shift,
|
|
||||||
app.interface.feedback.clientHeight - 15,
|
|
||||||
8,
|
|
||||||
"#fdba74"
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the circle at a given shift.
|
|
||||||
* @param shift - The pixel distance from the origin.
|
|
||||||
*/
|
|
||||||
const _clearBlinker = (shift: number) => {
|
|
||||||
const x = 50 + shift;
|
|
||||||
const y = app.interface.feedback.clientHeight - 15;
|
|
||||||
const radius = 8;
|
|
||||||
ctx.clearRect(x - radius, y - radius, radius * 2, radius * 2);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (script === "local" && no !== undefined) {
|
|
||||||
const shiftAmount = no * 25;
|
|
||||||
|
|
||||||
// Clear existing timeout if any
|
|
||||||
if (app.blinkTimeouts[shiftAmount]) {
|
|
||||||
clearTimeout(app.blinkTimeouts[shiftAmount]);
|
|
||||||
}
|
|
||||||
|
|
||||||
_drawBlinker(shiftAmount);
|
|
||||||
|
|
||||||
// Save timeout ID for later clearing
|
|
||||||
// @ts-ignore
|
|
||||||
app.blinkTimeouts[shiftAmount] = setTimeout(() => {
|
|
||||||
_clearBlinker(shiftAmount);
|
|
||||||
// Clear the canvas before drawing new blinkers
|
|
||||||
(app.interface.feedback as HTMLCanvasElement)
|
|
||||||
.getContext("2d")!
|
|
||||||
.clearRect(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
(app.interface.feedback as HTMLCanvasElement).width,
|
|
||||||
(app.interface.feedback as HTMLCanvasElement).height
|
|
||||||
);
|
|
||||||
}, blinkDuration);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manages animation updates using requestAnimationFrame.
|
|
||||||
* @param app - The Editor application context.
|
|
||||||
*/
|
|
||||||
export const scriptBlinkers = () => {
|
|
||||||
let lastFrameTime = Date.now();
|
|
||||||
const frameRate = 10;
|
|
||||||
const minFrameDelay = 1000 / frameRate;
|
|
||||||
|
|
||||||
const update = () => {
|
|
||||||
const now = Date.now();
|
|
||||||
const timeSinceLastFrame = now - lastFrameTime;
|
|
||||||
|
|
||||||
if (timeSinceLastFrame >= minFrameDelay) {
|
|
||||||
lastFrameTime = now;
|
|
||||||
}
|
|
||||||
requestAnimationFrame(update);
|
|
||||||
};
|
|
||||||
requestAnimationFrame(update);
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface OscilloscopeConfig {
|
export interface OscilloscopeConfig {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@ -134,15 +18,16 @@ export interface OscilloscopeConfig {
|
|||||||
let lastZeroCrossingType: string | null = null; // 'negToPos' or 'posToNeg'
|
let lastZeroCrossingType: string | null = null; // 'negToPos' or 'posToNeg'
|
||||||
let lastRenderTime: number = 0;
|
let lastRenderTime: number = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes and runs an oscilloscope using an AnalyzerNode.
|
|
||||||
* @param {HTMLCanvasElement} canvas - The canvas element to draw the oscilloscope.
|
|
||||||
* @param {OscilloscopeConfig} config - Configuration for the oscilloscope's appearance and behavior.
|
|
||||||
*/
|
|
||||||
export const runOscilloscope = (
|
export const runOscilloscope = (
|
||||||
canvas: HTMLCanvasElement,
|
canvas: HTMLCanvasElement,
|
||||||
app: Editor
|
app: Editor,
|
||||||
): void => {
|
): void => {
|
||||||
|
/**
|
||||||
|
* Runs the oscilloscope visualization on the provided canvas element.
|
||||||
|
*
|
||||||
|
* @param canvas - The HTMLCanvasElement on which to render the visualization.
|
||||||
|
* @param app - The Editor object containing the configuration for the oscilloscope.
|
||||||
|
*/
|
||||||
let config = app.osc;
|
let config = app.osc;
|
||||||
let analyzer = getAnalyser(config.fftSize);
|
let analyzer = getAnalyser(config.fftSize);
|
||||||
let dataArray = new Float32Array(analyzer.frequencyBinCount);
|
let dataArray = new Float32Array(analyzer.frequencyBinCount);
|
||||||
@ -155,7 +40,7 @@ export const runOscilloscope = (
|
|||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
offset_height: number,
|
offset_height: number,
|
||||||
offset_width: number
|
offset_width: number,
|
||||||
) {
|
) {
|
||||||
const maxFPS = 30;
|
const maxFPS = 30;
|
||||||
const now = performance.now();
|
const now = performance.now();
|
||||||
@ -169,10 +54,12 @@ export const runOscilloscope = (
|
|||||||
canvasCtx.clearRect(0, 0, width, height);
|
canvasCtx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
const performanceFactor = 1;
|
const performanceFactor = 1;
|
||||||
const reducedDataSize = Math.floor(freqDataArray.length * performanceFactor);
|
const reducedDataSize = Math.floor(
|
||||||
|
freqDataArray.length * performanceFactor,
|
||||||
|
);
|
||||||
const numBars = Math.min(
|
const numBars = Math.min(
|
||||||
reducedDataSize,
|
reducedDataSize,
|
||||||
app.osc.orientation === "horizontal" ? width : height
|
app.osc.orientation === "horizontal" ? width : height,
|
||||||
);
|
);
|
||||||
const barWidth =
|
const barWidth =
|
||||||
app.osc.orientation === "horizontal" ? width / numBars : height / numBars;
|
app.osc.orientation === "horizontal" ? width / numBars : height / numBars;
|
||||||
@ -184,7 +71,8 @@ export const runOscilloscope = (
|
|||||||
|
|
||||||
for (let i = 0; i < numBars; i++) {
|
for (let i = 0; i < numBars; i++) {
|
||||||
barHeight = Math.floor(
|
barHeight = Math.floor(
|
||||||
freqDataArray[Math.floor(i * freqDataArray.length / numBars)] * ((height / 256) * app.osc.size)
|
freqDataArray[Math.floor((i * freqDataArray.length) / numBars)]! *
|
||||||
|
((height / 256) * app.osc.size),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (app.osc.orientation === "horizontal") {
|
if (app.osc.orientation === "horizontal") {
|
||||||
@ -192,7 +80,7 @@ export const runOscilloscope = (
|
|||||||
x + offset_width,
|
x + offset_width,
|
||||||
(height - barHeight) / 2 + offset_height,
|
(height - barHeight) / 2 + offset_height,
|
||||||
barWidth + 1,
|
barWidth + 1,
|
||||||
barHeight
|
barHeight,
|
||||||
);
|
);
|
||||||
x += barWidth;
|
x += barWidth;
|
||||||
} else {
|
} else {
|
||||||
@ -200,14 +88,13 @@ export const runOscilloscope = (
|
|||||||
(width - barHeight) / 2 + offset_width,
|
(width - barHeight) / 2 + offset_width,
|
||||||
y + offset_height,
|
y + offset_height,
|
||||||
barHeight,
|
barHeight,
|
||||||
barWidth + 1
|
barWidth + 1,
|
||||||
);
|
);
|
||||||
y += barWidth;
|
y += barWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
// Update the canvas position on each cycle
|
// Update the canvas position on each cycle
|
||||||
const WIDTH = canvas.width;
|
const WIDTH = canvas.width;
|
||||||
@ -230,12 +117,19 @@ export const runOscilloscope = (
|
|||||||
-OFFSET_WIDTH,
|
-OFFSET_WIDTH,
|
||||||
-OFFSET_HEIGHT,
|
-OFFSET_HEIGHT,
|
||||||
WIDTH + 2 * OFFSET_WIDTH,
|
WIDTH + 2 * OFFSET_WIDTH,
|
||||||
HEIGHT + 2 * OFFSET_HEIGHT
|
HEIGHT + 2 * OFFSET_HEIGHT,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (analyzer.fftSize !== app.osc.fftSize) {
|
if (analyzer.fftSize !== app.osc.fftSize) {
|
||||||
|
// Disconnect and release the old analyzer if it exists
|
||||||
|
if (analyzer) {
|
||||||
|
analyzer.disconnect();
|
||||||
|
analyzer = null; // Release the reference for garbage collection
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new analyzer with the updated FFT size
|
||||||
analyzer = getAnalyser(app.osc.fftSize);
|
analyzer = getAnalyser(app.osc.fftSize);
|
||||||
dataArray = new Float32Array(analyzer.frequencyBinCount);
|
dataArray = new Float32Array(analyzer.frequencyBinCount);
|
||||||
}
|
}
|
||||||
@ -245,25 +139,25 @@ export const runOscilloscope = (
|
|||||||
|
|
||||||
canvasCtx.fillStyle = "rgba(0, 0, 0, 0)";
|
canvasCtx.fillStyle = "rgba(0, 0, 0, 0)";
|
||||||
canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
|
canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
|
||||||
if (app.clock.time_position.pulse % app.osc.refresh == 0) {
|
if (app.clock.time_position.tick % app.osc.refresh == 0) {
|
||||||
canvasCtx.clearRect(
|
canvasCtx.clearRect(
|
||||||
-OFFSET_WIDTH,
|
-OFFSET_WIDTH,
|
||||||
-OFFSET_HEIGHT,
|
-OFFSET_HEIGHT,
|
||||||
WIDTH + 2 * OFFSET_WIDTH,
|
WIDTH + 2 * OFFSET_WIDTH,
|
||||||
HEIGHT + 2 * OFFSET_HEIGHT
|
HEIGHT + 2 * OFFSET_HEIGHT,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
canvasCtx.lineWidth = app.osc.thickness;
|
canvasCtx.lineWidth = app.osc.thickness;
|
||||||
|
|
||||||
if (app.osc.color === "random") {
|
if (app.osc.color === "random") {
|
||||||
if (app.clock.time_position.pulse % 16 === 0) {
|
if (app.clock.time_position.tick % 16 === 0) {
|
||||||
canvasCtx.strokeStyle = `hsl(${Math.random() * 360}, 100%, 50%)`;
|
canvasCtx.strokeStyle = `hsl(${Math.random() * 360}, 100%, 50%)`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
canvasCtx.strokeStyle = app.osc.color;
|
canvasCtx.strokeStyle = app.osc.color;
|
||||||
}
|
}
|
||||||
const remainingRefreshTime =
|
const remainingRefreshTime =
|
||||||
app.clock.time_position.pulse % app.osc.refresh;
|
app.clock.time_position.tick % app.osc.refresh;
|
||||||
const opacityRatio = 1 - remainingRefreshTime / app.osc.refresh;
|
const opacityRatio = 1 - remainingRefreshTime / app.osc.refresh;
|
||||||
canvasCtx.globalAlpha = opacityRatio;
|
canvasCtx.globalAlpha = opacityRatio;
|
||||||
canvasCtx.beginPath();
|
canvasCtx.beginPath();
|
||||||
@ -271,9 +165,9 @@ export const runOscilloscope = (
|
|||||||
let startIndex = 0;
|
let startIndex = 0;
|
||||||
for (let i = 1; i < dataArray.length; ++i) {
|
for (let i = 1; i < dataArray.length; ++i) {
|
||||||
let currentType = null;
|
let currentType = null;
|
||||||
if (dataArray[i] >= 0 && dataArray[i - 1] < 0) {
|
if (dataArray[i]! >= 0 && dataArray[i - 1]! < 0) {
|
||||||
currentType = "negToPos";
|
currentType = "negToPos";
|
||||||
} else if (dataArray[i] < 0 && dataArray[i - 1] >= 0) {
|
} else if (dataArray[i]! < 0 && dataArray[i - 1]! >= 0) {
|
||||||
currentType = "posToNeg";
|
currentType = "posToNeg";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,8 +187,8 @@ export const runOscilloscope = (
|
|||||||
drawFrequencyScope(WIDTH, HEIGHT, OFFSET_HEIGHT, OFFSET_WIDTH);
|
drawFrequencyScope(WIDTH, HEIGHT, OFFSET_HEIGHT, OFFSET_WIDTH);
|
||||||
} else if (app.osc.mode === "3D") {
|
} else if (app.osc.mode === "3D") {
|
||||||
for (let i = startIndex; i < dataArray.length; i += 2) {
|
for (let i = startIndex; i < dataArray.length; i += 2) {
|
||||||
const x = (dataArray[i] * WIDTH * app.osc.size) / 2 + WIDTH / 4;
|
const x = (dataArray[i]! * WIDTH * app.osc.size) / 2 + WIDTH / 4;
|
||||||
const y = (dataArray[i + 1] * HEIGHT * app.osc.size) / 2 + HEIGHT / 4;
|
const y = (dataArray[i + 1]! * HEIGHT * app.osc.size) / 2 + HEIGHT / 4;
|
||||||
i === startIndex ? canvasCtx.moveTo(x, y) : canvasCtx.lineTo(x, y);
|
i === startIndex ? canvasCtx.moveTo(x, y) : canvasCtx.lineTo(x, y);
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
@ -305,7 +199,7 @@ export const runOscilloscope = (
|
|||||||
const yOffset = HEIGHT / 4;
|
const yOffset = HEIGHT / 4;
|
||||||
let x = 0;
|
let x = 0;
|
||||||
for (let i = startIndex; i < dataArray.length; i++) {
|
for (let i = startIndex; i < dataArray.length; i++) {
|
||||||
const v = dataArray[i] * 0.5 * HEIGHT * app.osc.size;
|
const v = dataArray[i]! * 0.5 * HEIGHT * app.osc.size;
|
||||||
const y = v + yOffset;
|
const y = v + yOffset;
|
||||||
i === startIndex ? canvasCtx.moveTo(x, y) : canvasCtx.lineTo(x, y);
|
i === startIndex ? canvasCtx.moveTo(x, y) : canvasCtx.lineTo(x, y);
|
||||||
x += sliceWidth;
|
x += sliceWidth;
|
||||||
@ -316,7 +210,7 @@ export const runOscilloscope = (
|
|||||||
const xOffset = WIDTH / 4;
|
const xOffset = WIDTH / 4;
|
||||||
let y = 0;
|
let y = 0;
|
||||||
for (let i = startIndex; i < dataArray.length; i++) {
|
for (let i = startIndex; i < dataArray.length; i++) {
|
||||||
const v = dataArray[i] * 0.5 * WIDTH * app.osc.size;
|
const v = dataArray[i]! * 0.5 * WIDTH * app.osc.size;
|
||||||
const x = v + xOffset;
|
const x = v + xOffset;
|
||||||
i === startIndex ? canvasCtx.moveTo(x, y) : canvasCtx.lineTo(x, y);
|
i === startIndex ? canvasCtx.moveTo(x, y) : canvasCtx.lineTo(x, y);
|
||||||
y += sliceHeight;
|
y += sliceHeight;
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { type Editor } from "./main";
|
import { type Editor } from "../main";
|
||||||
|
import { outputSocket, inputSocket } from "../IO/OSC";
|
||||||
|
|
||||||
const handleResize = (canvas: HTMLCanvasElement) => {
|
const handleResize = (canvas: HTMLCanvasElement) => {
|
||||||
if (!canvas) return;
|
if (!canvas) return;
|
||||||
@ -26,46 +27,49 @@ export const saveBeforeExit = (app: Editor): null => {
|
|||||||
app.currentFile().candidate = app.view.state.doc.toString();
|
app.currentFile().candidate = app.view.state.doc.toString();
|
||||||
app.currentFile().committed = app.view.state.doc.toString();
|
app.currentFile().committed = app.view.state.doc.toString();
|
||||||
app.settings.saveApplicationToLocalStorage(app.universes, app.settings);
|
app.settings.saveApplicationToLocalStorage(app.universes, app.settings);
|
||||||
|
// Close the websocket
|
||||||
|
inputSocket.close();
|
||||||
|
outputSocket.close();
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const installWindowBehaviors = (
|
export const installWindowBehaviors = (
|
||||||
app: Editor,
|
app: Editor,
|
||||||
window: Window,
|
window: Window,
|
||||||
preventMultipleTabs: boolean = false
|
preventMultipleTabs: boolean = false,
|
||||||
) => {
|
) => {
|
||||||
window.addEventListener("resize", () =>
|
window.addEventListener("resize", () =>
|
||||||
handleResize(app.interface.scope as HTMLCanvasElement)
|
handleResize(app.interface.feedback as HTMLCanvasElement),
|
||||||
);
|
);
|
||||||
window.addEventListener("resize", () =>
|
window.addEventListener("resize", () =>
|
||||||
handleResize(app.interface.feedback as HTMLCanvasElement)
|
handleResize(app.interface.scope as HTMLCanvasElement),
|
||||||
);
|
);
|
||||||
window.addEventListener("beforeunload", (event) => {
|
// window.addEventListener("beforeunload", (event) => {
|
||||||
event.preventDefault();
|
// event.preventDefault();
|
||||||
saveBeforeExit(app);
|
// saveBeforeExit(app);
|
||||||
});
|
// });
|
||||||
window.addEventListener("visibilitychange", (event) => {
|
window.addEventListener("visibilitychange", (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
saveState(app);
|
saveState(app);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (preventMultipleTabs) {
|
if (preventMultipleTabs) {
|
||||||
localStorage.openpages = Date.now();
|
localStorage["openpages"] = Date.now();
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"storage",
|
"storage",
|
||||||
function (e) {
|
function(e) {
|
||||||
if (e.key == "openpages") {
|
if (e.key == "openpages") {
|
||||||
// Listen if anybody else is opening the same page!
|
// Listen if anybody else is opening the same page!
|
||||||
localStorage.page_available = Date.now();
|
localStorage["page_available"] = Date.now();
|
||||||
}
|
}
|
||||||
if (e.key == "page_available") {
|
if (e.key == "page_available") {
|
||||||
document.getElementById("all")!.classList.add("invisible");
|
document.getElementById("all")!.classList.add("invisible");
|
||||||
alert(
|
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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
265
src/Docs/Documentation.ts
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
import { Editor } from "../main";
|
||||||
|
import { introduction, atelier, software_interface, shortcuts, code, mouse, interaction } from "./basics";
|
||||||
|
import { amplitude, effects, sampler, synths, filters, audio_basics } from "./learning/audio_engine";
|
||||||
|
import { lfos, functions, generators, variables, probabilities } from './patterns';
|
||||||
|
import { ziffers_basics, ziffers_scales, ziffers_rhythm, ziffers_algorithmic, ziffers_tonnetz, ziffers_syncing } from "./patterns/ziffers";
|
||||||
|
import { loading_samples } from "./learning/samples/loading_samples";
|
||||||
|
import { sample_banks } from "./learning/samples/sample_banks";
|
||||||
|
import { sample_list } from "./learning/samples/sample_list";
|
||||||
|
import { oscilloscope } from "./more/oscilloscope";
|
||||||
|
import { synchronisation } from "./more/synchronisation";
|
||||||
|
import { about } from "./more/about";
|
||||||
|
import { bonus } from "./more/bonus";
|
||||||
|
import { visualization } from "./more/visualization";
|
||||||
|
import { chaining } from "./patterns/chaining";
|
||||||
|
import { time } from "./learning/time/time";
|
||||||
|
import { linear_time } from "./learning/time/linear_time";
|
||||||
|
import { cyclical_time } from "./learning/time/cyclical_time";
|
||||||
|
import { long_forms } from "./learning/time/long_forms";
|
||||||
|
import { midi } from "./learning/midi";
|
||||||
|
import { osc } from "./learning/osc";
|
||||||
|
import { patterns } from "./patterns/patterns";
|
||||||
|
// Setting up the Markdown converter with syntax highlighting
|
||||||
|
import showdown from "showdown";
|
||||||
|
import showdownHighlight from "showdown-highlight";
|
||||||
|
import "highlight.js/styles/atom-one-dark-reasonable.min.css";
|
||||||
|
import { createDocumentationStyle } from "../DOM/DomElements";
|
||||||
|
showdown.setFlavor("github");
|
||||||
|
|
||||||
|
type StyleBinding = {
|
||||||
|
type: string;
|
||||||
|
regex: RegExp;
|
||||||
|
replace: (match: string, p1: string) => string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export 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-brightwhite bg-brightblack border border-black rounded-lg">${shortcut}</kbd>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const makeExampleFactory = (application: Editor): Function => {
|
||||||
|
const make_example = (
|
||||||
|
description: string,
|
||||||
|
code: string,
|
||||||
|
open: boolean = false,
|
||||||
|
) => {
|
||||||
|
const codeId = `codeExample${application.exampleCounter++}`;
|
||||||
|
// Store the code snippet in the data structure
|
||||||
|
application.api.codeExamples[codeId] = code;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<details ${open ? "open" : ""}>
|
||||||
|
<summary >${description}
|
||||||
|
<button class="ml-4 py-1 align-top text-base px-4 hover:bg-brightgreen bg-green inline-block text-selection_foreground" onclick="app.api._playDocExample(app.api.codeExamples['${codeId}'])">▶️ Play</button>
|
||||||
|
<button class="py-1 text-base px-4 hover:brightyellow bg-yellow text-selection_foreground inline-block" onclick="app.api._stopDocExample()">⏸️ Pause</button>
|
||||||
|
<button class="py-1 text-base px-4 hover:bg-brightmagenta bg-magenta text-selection_foreground inline-block" onclick="navigator.clipboard.writeText(app.api.codeExamples['${codeId}'])">📎 Copy</button>
|
||||||
|
</summary>
|
||||||
|
<pre><code class="hljs language-javascript">${code.trim()}</code></pre>
|
||||||
|
</details> `;
|
||||||
|
};
|
||||||
|
return make_example;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const documentation_pages = [
|
||||||
|
"introduction",
|
||||||
|
"atelier",
|
||||||
|
"sampler",
|
||||||
|
"amplitude",
|
||||||
|
"audio_basics",
|
||||||
|
"filters",
|
||||||
|
"effects",
|
||||||
|
"interface",
|
||||||
|
"interaction",
|
||||||
|
"code",
|
||||||
|
"time",
|
||||||
|
"linear",
|
||||||
|
"cyclic",
|
||||||
|
"longform",
|
||||||
|
"synths",
|
||||||
|
"chaining",
|
||||||
|
"patterns",
|
||||||
|
"ziffers_basics",
|
||||||
|
"ziffers_scales",
|
||||||
|
"ziffers_rhythm",
|
||||||
|
"ziffers_algorithmic",
|
||||||
|
"ziffers_tonnetz",
|
||||||
|
"ziffers_syncing",
|
||||||
|
"midi",
|
||||||
|
"osc",
|
||||||
|
"functions",
|
||||||
|
"generators",
|
||||||
|
"lfos",
|
||||||
|
"probabilities",
|
||||||
|
"variables",
|
||||||
|
"synchronisation",
|
||||||
|
"mouse",
|
||||||
|
"shortcuts",
|
||||||
|
"about",
|
||||||
|
"bonus",
|
||||||
|
"oscilloscope",
|
||||||
|
"sample_list",
|
||||||
|
"loading_samples",
|
||||||
|
"visualization"
|
||||||
|
];
|
||||||
|
|
||||||
|
export const documentation_factory = (application: Editor) => {
|
||||||
|
/**
|
||||||
|
* Creates the documentation for the given application.
|
||||||
|
* @param application The editor application.
|
||||||
|
* @returns An object containing various documentation sections.
|
||||||
|
*/
|
||||||
|
application.api.codeExamples = {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
introduction: introduction(application),
|
||||||
|
atelier: atelier(application),
|
||||||
|
interface: software_interface(application),
|
||||||
|
interaction: interaction(application),
|
||||||
|
code: code(application),
|
||||||
|
time: time(application),
|
||||||
|
linear: linear_time(application),
|
||||||
|
cyclic: cyclical_time(application),
|
||||||
|
longform: long_forms(application),
|
||||||
|
synths: synths(application),
|
||||||
|
filters: filters(application),
|
||||||
|
chaining: chaining(application),
|
||||||
|
patterns: patterns(application),
|
||||||
|
ziffers_basics: ziffers_basics(application),
|
||||||
|
ziffers_scales: ziffers_scales(application),
|
||||||
|
ziffers_algorithmic: ziffers_algorithmic(application),
|
||||||
|
ziffers_rhythm: ziffers_rhythm(application),
|
||||||
|
ziffers_tonnetz: ziffers_tonnetz(application),
|
||||||
|
ziffers_syncing: ziffers_syncing(application),
|
||||||
|
midi: midi(application),
|
||||||
|
osc: osc(application),
|
||||||
|
lfos: lfos(application),
|
||||||
|
variables: variables(application),
|
||||||
|
probabilities: probabilities(application),
|
||||||
|
functions: functions(application),
|
||||||
|
generators: generators(application),
|
||||||
|
shortcuts: shortcuts(application),
|
||||||
|
amplitude: amplitude(application),
|
||||||
|
effects: effects(application),
|
||||||
|
sampler: sampler(application),
|
||||||
|
mouse: mouse(application),
|
||||||
|
oscilloscope: oscilloscope(application),
|
||||||
|
audio_basics: audio_basics(application),
|
||||||
|
synchronisation: synchronisation(application),
|
||||||
|
bonus: bonus(application),
|
||||||
|
visualization: visualization(application),
|
||||||
|
sample_list: sample_list(application),
|
||||||
|
sample_banks: sample_banks(application),
|
||||||
|
loading_samples: loading_samples(application),
|
||||||
|
about: about(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const showDocumentation = (app: Editor): void => {
|
||||||
|
const toggleElementVisibility = (elementId: string, shouldHide: boolean): void => {
|
||||||
|
const element = document.getElementById(elementId);
|
||||||
|
if (element) {
|
||||||
|
element.classList.toggle("hidden", shouldHide);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyStyleBindings = (style: Record<string, string>, updateContent: (bindings: StyleBinding[]) => void): void => {
|
||||||
|
const bindings: StyleBinding[] = Object.keys(style).map((key) => ({
|
||||||
|
type: "output",
|
||||||
|
regex: new RegExp(`<${key}([^>]*)>`, "g"),
|
||||||
|
replace: (_, p1) => `<${key} class="${style[key]}" ${p1}>`
|
||||||
|
}));
|
||||||
|
updateContent(bindings);
|
||||||
|
};
|
||||||
|
|
||||||
|
const appHidden = document.getElementById("app")?.classList.contains("hidden");
|
||||||
|
if (appHidden) {
|
||||||
|
toggleElementVisibility("app", false);
|
||||||
|
toggleElementVisibility("documentation", true);
|
||||||
|
app.exampleIsPlaying = false;
|
||||||
|
} else {
|
||||||
|
toggleElementVisibility("app", true);
|
||||||
|
toggleElementVisibility("documentation", false);
|
||||||
|
const style = createDocumentationStyle(app);
|
||||||
|
applyStyleBindings(style, (bindings: StyleBinding[]) => updateDocumentationContent(app, bindings));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the URL to the base URL
|
||||||
|
window.history.pushState({}, '', '/');
|
||||||
|
};
|
||||||
|
|
||||||
|
// export const showDocumentation = (app: Editor) => {
|
||||||
|
// /**
|
||||||
|
// * Shows or hides the documentation based on the current state of the app.
|
||||||
|
// * @param app - The Editor instance.
|
||||||
|
// */
|
||||||
|
// if (document.getElementById("app")?.classList.contains("hidden")) {
|
||||||
|
// document.getElementById("app")?.classList.remove("hidden");
|
||||||
|
// document.getElementById("documentation")?.classList.add("hidden");
|
||||||
|
// app.exampleIsPlaying = false;
|
||||||
|
// } else {
|
||||||
|
// document.getElementById("app")?.classList.add("hidden");
|
||||||
|
// document.getElementById("documentation")?.classList.remove("hidden");
|
||||||
|
// // Load and convert Markdown content from the documentation file
|
||||||
|
// let style = createDocumentationStyle(app);
|
||||||
|
// function update_and_assign(callback: Function) {
|
||||||
|
// let bindings = Object.keys(style).map((key) => ({
|
||||||
|
// type: "output",
|
||||||
|
// regex: new RegExp(`<${key}([^>]*)>`, "g"),
|
||||||
|
// //@ts-ignore
|
||||||
|
// replace: (match, p1) => `<${key} class="${style[key]}" ${p1}>`,
|
||||||
|
// }));
|
||||||
|
// callback(bindings)
|
||||||
|
// }
|
||||||
|
// update_and_assign((e: Object) => updateDocumentationContent(app, e));
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
export const hideDocumentation = () => {
|
||||||
|
/**
|
||||||
|
* Hides the documentation section and shows the main application.
|
||||||
|
*/
|
||||||
|
if (document.getElementById("app")?.classList.contains("hidden")) {
|
||||||
|
document.getElementById("app")?.classList.remove("hidden");
|
||||||
|
document.getElementById("documentation")?.classList.add("hidden");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateDocumentationContent = (app: Editor, bindings: any) => {
|
||||||
|
/**
|
||||||
|
* Updates the content of the documentation pane with the converted markdown.
|
||||||
|
*
|
||||||
|
* @param app - The editor application.
|
||||||
|
* @param bindings - Additional bindings for the showdown converter.
|
||||||
|
*/
|
||||||
|
let loading_message: string = "<h1 class='border-4 py-2 px-2 mx-48 mt-48 text-center text-2xl text-brightwhite'>Loading! <b class='text-red'>Clic to refresh!</b></h1>";
|
||||||
|
const converter = new showdown.Converter({
|
||||||
|
emoji: true,
|
||||||
|
moreStyling: true,
|
||||||
|
backslashEscapesHTMLTags: true,
|
||||||
|
extensions: [showdownHighlight({
|
||||||
|
pre: true,
|
||||||
|
auto_detection: false
|
||||||
|
}), ...bindings],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Object.keys(app.docs).length === 0) {
|
||||||
|
app.docs = documentation_factory(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _update_and_assign(callback: Function) {
|
||||||
|
const converted_markdown = converter.makeHtml(
|
||||||
|
app.docs[app.currentDocumentationPane]!,
|
||||||
|
);
|
||||||
|
callback(converted_markdown)
|
||||||
|
}
|
||||||
|
_update_and_assign((e: string) => {
|
||||||
|
let display_content = e === undefined ? loading_message : e;
|
||||||
|
document.getElementById("documentation-content")!.innerHTML = display_content;
|
||||||
|
})
|
||||||
|
if (document.getElementById("documentation-content")!.innerHTML.replace(/"/g, "'") == loading_message.replace(/"/g, "'")) {
|
||||||
|
setTimeout(() => {
|
||||||
|
updateDocumentationContent(app, bindings);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
src/Docs/basics/TOPOS_COMMANDS.pdf
Normal file
193
src/Docs/basics/atelier.ts
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
import { type Editor } from "../../main";
|
||||||
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
|
export const atelier = (application: Editor): string => {
|
||||||
|
const makeExample = makeExampleFactory(application);
|
||||||
|
return `
|
||||||
|
# Atelier (06 mars 2024)
|
||||||
|
|
||||||
|
Bonjour tout le monde ! Nous sommes :
|
||||||
|
|
||||||
|
- [Rémi Georges](https://remigeorges.fr) : musicien, réalisateur en informatique musicale.
|
||||||
|
- [Agathe Herrou](https://www.youtube.com/@th4music) : musicienne, chercheuse.
|
||||||
|
- [Raphaël Forment](https://raphaelforment.fr) : musicien, doctorant.
|
||||||
|
|
||||||
|
Nous pratiquons le [live coding](https://livecoding.fr). Nous utilisons notre ordinateur comme un instrument de musique, nous programmons de la musique devant notre public. Nous pouvons faire plein de choses comme :
|
||||||
|
|
||||||
|
- créer des instruments de musique, des synthétiseurs, des boîtes à rythme.
|
||||||
|
- jouer des échantillons, charger des images, des vidéos, créer des animations.
|
||||||
|
- contrôler d'autres instruments, jouer avec d'autres musiciens.
|
||||||
|
|
||||||
|
Topos est un instrument de musique. On peut l'utiliser depuis n'importe quel ordinateur, sans avoir à installer quoi que ce soit. Nous l'avons fabriqué pour que tout le monde puisse jouer facilement de la musique.
|
||||||
|
|
||||||
|
|
||||||
|
## Découverte
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Percussions", `
|
||||||
|
tempo(120) // Changer le tempo
|
||||||
|
beat(1)::sound('kick').out()
|
||||||
|
beat(2)::sound('snare').out()
|
||||||
|
beat(.5)::sound('hh').out()
|
||||||
|
`, true,)}
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
- Qu'est-ce qu'il se passe si je change un nombre ?
|
||||||
|
- Qu'est-ce qu'il se passe si je change un nom ?
|
||||||
|
- Essayez par exemple <ic>"sid"</ic> ou <ic>"trump"</ic>.
|
||||||
|
- Qu'est-ce qu'il se passe si j'enlève <ic>.out()</ic> ?
|
||||||
|
- Est-il possible de jouer un rythme très rapide ou très lent ?
|
||||||
|
|
||||||
|
|
||||||
|
### Ajout d'une basse
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Une basse", `
|
||||||
|
// Aucun changement dans le code
|
||||||
|
beat(1)::sound('kick').out()
|
||||||
|
beat(2)::sound('snare').out()
|
||||||
|
beat(.5)::sound('hh').out()
|
||||||
|
|
||||||
|
// Une nouvelle partie
|
||||||
|
beat([0.25,0.5].beat(1))::sound("pluck")
|
||||||
|
.note([40,45].beat(2)).out()
|
||||||
|
`, true,)}
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
- Qu'est-ce que le son <ic>"pluck"</ic> ?
|
||||||
|
|
||||||
|
- Que signifie <ic>.note([40,45].beat(2))</ic> ?
|
||||||
|
|
||||||
|
- Que se passe-t-il si je change la valeur dans <ic>.beat(2)</ic> ?
|
||||||
|
|
||||||
|
- Que se passe-t-il lorsque j'ajoute de nouveaux nombres dans <ic>[40, 45]</ic> ?
|
||||||
|
|
||||||
|
### Ajout d'une mélodie
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Le morceau complet", `
|
||||||
|
// Aucun changement dans le code
|
||||||
|
beat(1)::sound('kick').out()
|
||||||
|
beat(2)::sound('snare').out()
|
||||||
|
beat(.5)::sound('hh').out()
|
||||||
|
beat([0.25,0.5].beat(1))::sound("pluck")
|
||||||
|
.note([40,45].beat(2)).out()
|
||||||
|
|
||||||
|
// Nouvelle partie mélodique
|
||||||
|
beat([0.25,0.5].beat())::sound("pluck")
|
||||||
|
.note([0,7,5,8,2,9,0].scale("Major",60).beat(1))
|
||||||
|
.vib(8).vibmod(1/4)
|
||||||
|
.delay(0.5).room(1.5).size(0.5)
|
||||||
|
.out()
|
||||||
|
`, true,)}
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Ici, on ajoute une nouvelle mélodie mais il s'agit aussi d'un nouvel instrument. C'est pour cela que le code est plus long. Quand on fait du <em>live coding</em>, on code tout en même temps : notes, rythmes, mélodies, sons. C'est beaucoup de choses ! C'est pour cela que le code est court, on essaie de tout taper très vite en jouant !
|
||||||
|
|
||||||
|
- Que signifie selon vous <ic>vib</ic>, <ic>delay</ic>, <ic>room</ic> ou <ic>size</ic> ?
|
||||||
|
|
||||||
|
- Que se passe-t-il si je change les valeurs dans <ic>vib</ic>, <ic>delay</ic>, <ic>room</ic> ou <ic>size</ic> ?
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
**Exercices :**
|
||||||
|
|
||||||
|
- Transformer <ic>vib(8)</ic> en <ic>vib([2,4,8].beat(1))</ic>.
|
||||||
|
- Transformer <ic>"pluck"</ic> en <ic>["pluck", "clap"].beat(1)</ic>.
|
||||||
|
|
||||||
|
Vous pouvez aussi utiliser la fonction <ic>rhythm</ic> pour jouer rapidement des rythmes.
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Rythmes rythmes rythmes", `
|
||||||
|
rhythm(0.5, 3, 8)::sound('bd').out()
|
||||||
|
rhythm(0.5, 3, 8)::sound('clap').out()
|
||||||
|
rhythm(0.5, 6, 8)::sound('hat').out()
|
||||||
|
rhythm(0.25, 6, 8)::sound('hat')
|
||||||
|
.vel(0.3).speed(2).out()
|
||||||
|
rhythm(0.5, 2, 8)::sound('sd').out()
|
||||||
|
`, true)};
|
||||||
|
|
||||||
|
## Créer un instrument
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Nous allons créer un nouvel instrument à partir d'un son de base. Voici un premier son :
|
||||||
|
|
||||||
|
${makeExample("Notre son de base", `beat(2)::sound('sine').note(50).ad(0, .5).out()`, true)}
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Ce son est assez ennuyeux. Nous allons ajouter quelques paramètres :
|
||||||
|
|
||||||
|
${makeExample("Beaucoup mieux !", `beat(2)::sound('sine').note(50).fmi(2).fmh(2).ad(0, .5).out()`, true)}
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Nous allons aussi ajouter quelques effets intéressants :
|
||||||
|
|
||||||
|
${makeExample("Ajout d'un écho", `beat(2)::sound('sine').note(50)
|
||||||
|
.fmi(2).fmh(2).ad(1/16, 1.5)
|
||||||
|
.delay(0.5).delayt(0.75).out()`,
|
||||||
|
true)}
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Nous pouvons utiliser plusieurs techniques pour rendre le son plus dynamique :
|
||||||
|
- générer des valeurs aléatoires pour les paramètres
|
||||||
|
- utiliser des générateurs de valeurs (comme <ic>usine</ic>)
|
||||||
|
- utiliser la souris ou un autre contrôleur pour changer les valeurs en temps réel
|
||||||
|
|
||||||
|
${makeExample("Plus dynamique encore", `
|
||||||
|
beat(2)::sound('sine').note([50,55,57,62,66, 69, 74].mouseX())
|
||||||
|
.fmi(usine(1/4)).fmh([1,2,0.5].beat())
|
||||||
|
.ad(1/16, 1.5).delay(0.5).delayt(0.75)
|
||||||
|
.out()`, true)}
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Un exemple final, le plus complexe jusqu'à présent :
|
||||||
|
|
||||||
|
${makeExample("Un instrument de musique complet", `
|
||||||
|
beat(2)::sound('triangle')
|
||||||
|
.note([50,55,57,62,66, 69, 74].mouseX())
|
||||||
|
.fmi(usine(1/4)).fmh([1,2,0.5].beat())
|
||||||
|
.ad(1/16, 1.5).delay(0.5).delayt(0.75)
|
||||||
|
.room(0.5).size(8).lpf(usine(1/3)*4000).out()`, true)}
|
||||||
|
|
||||||
|
## Compléments
|
||||||
|
|
||||||
|
${makeExample("Quelques échantillons", `
|
||||||
|
ab ade ades2 ades3 ades4 alex alphabet amencutup armora arp arpy auto
|
||||||
|
baa baa2 bass bass0 bass1 bass2 bass3 bassdm bassfoo battles bd bend
|
||||||
|
bev bin birds birds3 bleep blip blue bottle breaks125 breaks152
|
||||||
|
breaks157 breaks165 breath bubble can casio cb cc chin circus clak
|
||||||
|
click clubkick co coins control cosmicg cp cr crow d db diphone
|
||||||
|
diphone2 dist dork2 dorkbot dr dr2 dr55 dr_few drum drumtraks e east
|
||||||
|
electro1 em2 erk f feel feelfx fest fire flick fm foo future gab
|
||||||
|
gabba gabbaloud gabbalouder glasstap glitch glitch2 gretsch gtr h
|
||||||
|
hand hardcore hardkick haw hc hh hh27 hit hmm ho hoover house ht if
|
||||||
|
ifdrums incoming industrial insect invaders jazz jungbass jungle juno
|
||||||
|
jvbass kicklinn koy kurt latibro led less lighter linnhats lt made
|
||||||
|
made2 mash mash2 metal miniyeah monsterb moog mouth mp3 msg mt mute
|
||||||
|
newnotes noise noise2 notes numbers oc off outdoor pad padlong pebbles
|
||||||
|
perc peri pluck popkick print proc procshort psr rave rave2 ravemono
|
||||||
|
realclaps reverbkick rm rs sax sd seawolf sequential sf sheffield
|
||||||
|
short sid sine sitar sn space speakspell speech speechless speedupdown
|
||||||
|
stab stomp subroc3d sugar sundance tabla tabla2 tablex tacscan tech
|
||||||
|
techno tink tok toys trump ul ulgab uxay v voodoo wind wobble world
|
||||||
|
xmas yeah`, true)}
|
||||||
|
|
||||||
|
|
||||||
|
`
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory, key_shortcut } from "../../Documentation";
|
import { makeExampleFactory, key_shortcut } from "../Documentation";
|
||||||
|
|
||||||
export const code = (application: Editor): string => {
|
export const code = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -14,7 +14,7 @@ Topos scripts are using the [JavaScript](https://en.wikipedia.org/wiki/JavaScrip
|
|||||||
|
|
||||||
- [The Modern JavaScript Tutorial](https://javascript.info/): another well known source to learn the language.
|
- [The Modern JavaScript Tutorial](https://javascript.info/): another well known source to learn the language.
|
||||||
|
|
||||||
**You do not need to have any prior knowledge of programming** to use Topos**.
|
**You do not need to have any prior knowledge of programming to use Topos**.
|
||||||
|
|
||||||
# How is the code evaluated?
|
# How is the code evaluated?
|
||||||
|
|
||||||
@ -22,8 +22,8 @@ The code you enter in any of the scripts is evaluated in strict mode. This tells
|
|||||||
|
|
||||||
- **about variables:** the state of your variables is not kept between iterations. If you write <ic>let a = 2</ic> and remove that value from your script, **it will crash**! Variable and state is not preserved between each run of the script. There are other ways to deal with variables and to share variables between scripts! Some variables like **iterators** can keep their state between iterations because they are saved **with the file itself**. There is also **global variables**.
|
- **about variables:** the state of your variables is not kept between iterations. If you write <ic>let a = 2</ic> and remove that value from your script, **it will crash**! Variable and state is not preserved between each run of the script. There are other ways to deal with variables and to share variables between scripts! Some variables like **iterators** can keep their state between iterations because they are saved **with the file itself**. There is also **global variables**.
|
||||||
- **about errors and printing:** your code will crash! Don't worry, we do our best to make it crash in the most gracious way possible. Most errors are caught and displayed in the interface. For weirder bugs, open the dev console with ${key_shortcut(
|
- **about errors and printing:** your code will crash! Don't worry, we do our best to make it crash in the most gracious way possible. Most errors are caught and displayed in the interface. For weirder bugs, open the dev console with ${key_shortcut(
|
||||||
"Ctrl + Shift + I"
|
"Ctrl + Shift + I",
|
||||||
)}. You cannot directly use <ic>console.log('hello, world')</ic> in the interface but you can use <ic>log(message)</ic> to print a one line message. You will have to open the console as well to see your messages being printed there!
|
)}. You cannot directly use <ic>console.log('hello, world')</ic> in the interface but you can use <ic>log(message)</ic> to print a one line message. You will have to open the console as well to see your messages being printed there! You can also use <ic>logOnce(message)</ic> to print a message only once (or everytime you press Ctrl+Shift+Backspace).
|
||||||
- **about new syntax:** sometimes, we had some fun with JavaScript's syntax in order to make it easier/faster to write on stage. <ic>&&</ic> can also be written <ic>::</ic> or <ic>-></ic> because it is faster to type or better for the eyes!
|
- **about new syntax:** sometimes, we had some fun with JavaScript's syntax in order to make it easier/faster to write on stage. <ic>&&</ic> can also be written <ic>::</ic> or <ic>-></ic> because it is faster to type or better for the eyes!
|
||||||
|
|
||||||
# Common idioms
|
# Common idioms
|
||||||
@ -42,7 +42,7 @@ beat(1) :: snd('bd').out()
|
|||||||
//// beat(1) :: snd('bd').out()
|
//// beat(1) :: snd('bd').out()
|
||||||
|
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -52,7 +52,7 @@ ${makeExample(
|
|||||||
beat(4) ? snd('kick').out() : beat(2) :: snd('snare').out()
|
beat(4) ? snd('kick').out() : beat(2) :: snd('snare').out()
|
||||||
// (true) ? log('very true') : log('very false')
|
// (true) ? log('very true') : log('very false')
|
||||||
`,
|
`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ ${makeExample(
|
|||||||
beat(4) ? snd('kick').out() : beat(2) :: snd('snare').out()
|
beat(4) ? snd('kick').out() : beat(2) :: snd('snare').out()
|
||||||
!beat(2) :: beat(0.5) :: snd('clap').out()
|
!beat(2) :: beat(0.5) :: snd('clap').out()
|
||||||
`,
|
`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
# About crashes and bugs
|
# About crashes and bugs
|
||||||
@ -76,7 +76,7 @@ ${makeExample(
|
|||||||
// This is crashing. See? No harm!
|
// This is crashing. See? No harm!
|
||||||
qjldfqsdklqsjdlkqjsdlqkjdlksjd
|
qjldfqsdklqsjdlkqjsdlqkjdlksjd
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
7
src/Docs/basics/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export { introduction } from './welcome';
|
||||||
|
export { atelier } from './atelier';
|
||||||
|
export { software_interface } from './interface';
|
||||||
|
export { shortcuts } from './keyboard';
|
||||||
|
export { code } from './code';
|
||||||
|
export { mouse } from './mouse';
|
||||||
|
export { interaction } from './interaction';
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { type Editor } from "../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory } from "../Documentation";
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -24,8 +24,8 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
beat(1) && active_notes() && sound('sine').chord(active_notes()).out()
|
beat(1) && active_notes() && sound('sine').chord(active_notes()).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Play active notes as arpeggios",
|
"Play active notes as arpeggios",
|
||||||
@ -34,8 +34,8 @@ ${makeExample(
|
|||||||
active_notes().beat(0.5)+[12,24].beat(0.25)
|
active_notes().beat(0.5)+[12,24].beat(0.25)
|
||||||
).cutoff(300 + usine(1/4) * 2000).out()
|
).cutoff(300 + usine(1/4) * 2000).out()
|
||||||
`,
|
`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
* <ic>sticky_notes(channel?: number)</ic>: returns array of the last pressed keys as an array of MIDI note numbers (0-127). Notes are added and removed from the list with the "Note on"-event. Returns undefined if no keys have been pressed.
|
* <ic>sticky_notes(channel?: number)</ic>: returns array of the last pressed keys as an array of MIDI note numbers (0-127). Notes are added and removed from the list with the "Note on"-event. Returns undefined if no keys have been pressed.
|
||||||
@ -46,8 +46,8 @@ ${makeExample(
|
|||||||
beat(0.25) && sticky_notes() && sound('arp')
|
beat(0.25) && sticky_notes() && sound('arp')
|
||||||
.note(sticky_notes().palindrome().beat(0.25)).out()
|
.note(sticky_notes().palindrome().beat(0.25)).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
* <ic>last_note(channel?: number)</ic>: returns the last note that has been received. Returns 60 if no other notes have been received.
|
* <ic>last_note(channel?: number)</ic>: returns the last note that has been received. Returns 60 if no other notes have been received.
|
||||||
|
|
||||||
@ -58,8 +58,8 @@ ${makeExample(
|
|||||||
.vib([1, 3, 5].beat(1))
|
.vib([1, 3, 5].beat(1))
|
||||||
.vibmod([1,3,2,4].beat(2)).out()
|
.vibmod([1,3,2,4].beat(2)).out()
|
||||||
`,
|
`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
* <ic>buffer()</ic>: return true if there are notes in the buffer.
|
* <ic>buffer()</ic>: return true if there are notes in the buffer.
|
||||||
* <ic>buffer_note(channel?: number)</ic>: returns last unread note that has been received. Note is fetched and removed from start of the buffer once this is called. Returns undefined if no notes have been received.
|
* <ic>buffer_note(channel?: number)</ic>: returns last unread note that has been received. Note is fetched and removed from start of the buffer once this is called. Returns undefined if no notes have been received.
|
||||||
@ -69,8 +69,8 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
beat(1) && buffer() && sound('sine').note(buffer_note()).out()
|
beat(1) && buffer() && sound('sine').note(buffer_note()).out()
|
||||||
`,
|
`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -80,33 +80,33 @@ ${makeExample(
|
|||||||
Midi CC messages can be used to control any value in Topos. MIDI input can be defined in Settings and last received CC message can be used to control any numeric value within Topos.
|
Midi CC messages can be used to control any value in Topos. MIDI input can be defined in Settings and last received CC message can be used to control any numeric value within Topos.
|
||||||
|
|
||||||
Currently supported methods for CC input are:
|
Currently supported methods for CC input are:
|
||||||
* <ic>last_cc(control: number, channel?: number)</ic>: Returns last received CC value for given control number (and optional channel). By default last CC value is last value from ANY channel or 64 if no CC messages have been received.
|
* <ic>ccIn(control: number, channel?: number)</ic>: Returns last received CC value for given control number (and optional channel). By default last CC value is last value from ANY channel or 64 if no CC messages have been received.
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Play notes with cc",
|
"Play notes with cc",
|
||||||
`
|
`
|
||||||
beat(0.5) && sound('arp').note(last_cc(74)).out()
|
beat(0.5) && sound('arp').note(ccin(74)).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Control everything with CCs",
|
"Control everything with CCs",
|
||||||
`
|
`
|
||||||
beat(0.5) :: sound('sine')
|
beat(0.5) :: sound('sine')
|
||||||
.freq(last_cc(75)*3)
|
.freq(ccIn(75)*3)
|
||||||
.cutoff(last_cc(76)*2*usine())
|
.cutoff(ccIn(76)*2*usine())
|
||||||
.sustain(1.0)
|
.sustain(1.0)
|
||||||
.out()
|
.out()
|
||||||
|
|
||||||
beat(last_cc(74)/127*.5) :: sound('sine')
|
beat(ccIn(74)/127*.5) :: sound('sine')
|
||||||
.freq(last_cc(75)*6)
|
.freq(ccIn(75)*6)
|
||||||
.cutoff(last_cc(76)*3*usine())
|
.cutoff(ccIn(76)*3*usine())
|
||||||
.sustain(last_cc(74)/127*.25)
|
.sustain(ccIn(74)/127*.25)
|
||||||
.out()
|
.out()
|
||||||
`,
|
`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
## Run scripts with MIDI
|
## Run scripts with MIDI
|
||||||
@ -128,8 +128,8 @@ Topos can output scales to external keyboards lighted keys using the following f
|
|||||||
${makeExample(
|
${makeExample(
|
||||||
"Show scale on external keyboard",
|
"Show scale on external keyboard",
|
||||||
`show_scale("F","aeolian",0,4)`,
|
`show_scale("F","aeolian",0,4)`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample("Hide scale", `hide_scale("F","aeolian",0,4)`, true)}
|
${makeExample("Hide scale", `hide_scale("F","aeolian",0,4)`, true)}
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { key_shortcut, makeExampleFactory } from "../../Documentation";
|
import { key_shortcut, makeExampleFactory } from "../Documentation";
|
||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import topos_arch from "./topos_arch.svg";
|
import topos_arch from "./topos_arch.svg";
|
||||||
import many_universes from "./many_universes.svg";
|
import many_universes from "./many_universes.svg";
|
||||||
@ -18,22 +18,22 @@ The Topos interface is designed on a simple concept: _scripts_ and _universes_.
|
|||||||
Every Topos session is composed of **local**, **global** and **init** scripts. These scripts form a structure called a "_universe_". The scripts can describe whatever you want: songs, sketches, small tools, or whatever. All the scripts are written using the JavaScript programming language. They describe a musical or algorithmic process. You can call them anytime.
|
Every Topos session is composed of **local**, **global** and **init** scripts. These scripts form a structure called a "_universe_". The scripts can describe whatever you want: songs, sketches, small tools, or whatever. All the scripts are written using the JavaScript programming language. They describe a musical or algorithmic process. You can call them anytime.
|
||||||
|
|
||||||
- **the global script** (${key_shortcut(
|
- **the global script** (${key_shortcut(
|
||||||
"Ctrl + G"
|
"Ctrl + G",
|
||||||
)}): _Evaluated for every clock pulse_. The central piece, acting as the conductor for all the other scripts. You can also jam directly from the global script to test your ideas before pushing them to a separate script. You can also access that script using the ${key_shortcut(
|
)}): _Evaluated for every clock pulse_. The central piece, acting as the conductor for all the other scripts. You can also jam directly from the global script to test your ideas before pushing them to a separate script. You can also access that script using the ${key_shortcut(
|
||||||
"F10"
|
"F10",
|
||||||
)} key.
|
)} key.
|
||||||
- **the local scripts** (${key_shortcut(
|
- **the local scripts** (${key_shortcut(
|
||||||
"Ctrl + L"
|
"Ctrl + L",
|
||||||
)}): _Evaluated on demand_. Local scripts are used to store anything too complex to sit in the global script. It can be a musical process, a whole section of your composition, a complex controller that you've built for your hardware, etc... You can also switch to one of the local scripts by using the function keys (${key_shortcut(
|
)}): _Evaluated on demand_. Local scripts are used to store anything too complex to sit in the global script. It can be a musical process, a whole section of your composition, a complex controller that you've built for your hardware, etc... You can also switch to one of the local scripts by using the function keys (${key_shortcut(
|
||||||
"F1"
|
"F1",
|
||||||
)} to ${key_shortcut("F9")}).
|
)} to ${key_shortcut("F9")}).
|
||||||
- **the init script** (${key_shortcut(
|
- **the init script** (${key_shortcut(
|
||||||
"Ctrl + I"
|
"Ctrl + I",
|
||||||
)}): _Evaluated on program load_. Used to set up the software the session to the desired state before playing, for example changing bpm or to initialize global variables (See Functions). You can also access that script using the ${key_shortcut(
|
)}): _Evaluated on program load_. Used to set up the software the session to the desired state before playing, for example changing bpm or to initialize global variables (See Functions). You can also access that script using the ${key_shortcut(
|
||||||
"F11"
|
"F11",
|
||||||
)} key.
|
)} key.
|
||||||
- **the note file** (${key_shortcut(
|
- **the note file** (${key_shortcut(
|
||||||
"Ctrl + N"
|
"Ctrl + N",
|
||||||
)}): _Not evaluated_. Used to store your thoughts or commentaries about the session you are currently playing. It is nothing more than a scratchpad really!
|
)}): _Not evaluated_. Used to store your thoughts or commentaries about the session you are currently playing. It is nothing more than a scratchpad really!
|
||||||
|
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ ${makeExample(
|
|||||||
beat(1) :: script(1) // Calling local script n°1
|
beat(1) :: script(1) // Calling local script n°1
|
||||||
flip(4) :: beat(.5) :: script(2) // Calling script n°2
|
flip(4) :: beat(.5) :: script(2) // Calling script n°2
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -54,7 +54,7 @@ beat(1) :: script([1, 3, 5].pick())
|
|||||||
flip(4) :: beat([.5, .25].beat(16)) :: script(
|
flip(4) :: beat([.5, .25].beat(16)) :: script(
|
||||||
[5, 6, 7, 8].beat())
|
[5, 6, 7, 8].beat())
|
||||||
`,
|
`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
### Navigating the interface
|
### Navigating the interface
|
||||||
@ -81,7 +81,7 @@ There are some useful functions to help you manage your scripts:
|
|||||||
|
|
||||||
|
|
||||||
A set of files is called a _universe_. You can switch between universes immediately immediately by pressing ${key_shortcut(
|
A set of files is called a _universe_. You can switch between universes immediately immediately by pressing ${key_shortcut(
|
||||||
"Ctrl + B"
|
"Ctrl + B",
|
||||||
)}. You can also create a new universe by entering a name. Load a universe by typing its name. Once a universe is loaded, it is not possible to call any data/code from any other universe. Switching between universes does not stop the transport nor reset the clock. The context switches but time keeps flowing. This can be useful for transitioning between songs / parts.
|
)}. You can also create a new universe by entering a name. Load a universe by typing its name. Once a universe is loaded, it is not possible to call any data/code from any other universe. Switching between universes does not stop the transport nor reset the clock. The context switches but time keeps flowing. This can be useful for transitioning between songs / parts.
|
||||||
|
|
||||||
There are some useful functions to help you manage your universes:
|
There are some useful functions to help you manage your universes:
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { key_shortcut } from "../../Documentation";
|
import { key_shortcut } from "../Documentation";
|
||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
export const shortcuts = (app: Editor): string => {
|
export const shortcuts = (app: Editor): string => {
|
||||||
let makeExample = makeExampleFactory(app);
|
let makeExample = makeExampleFactory(app);
|
||||||
@ -14,10 +14,10 @@ Topos is made to be controlled entirely with a keyboard. It is recommanded to st
|
|||||||
| Shortcut | Key | Description |
|
| Shortcut | Key | Description |
|
||||||
|----------|-------|------------------------------------------------------------|
|
|----------|-------|------------------------------------------------------------|
|
||||||
|**Start/Pause** transport|${key_shortcut(
|
|**Start/Pause** transport|${key_shortcut(
|
||||||
"Ctrl + P"
|
"Ctrl + P",
|
||||||
)}|Start or pause audio playback|
|
)}|Start or pause audio playback|
|
||||||
|**Stop** the transport |${key_shortcut(
|
|**Stop** the transport |${key_shortcut(
|
||||||
"Ctrl + S"
|
"Ctrl + S",
|
||||||
)}|Stop and rewind audio playback|
|
)}|Stop and rewind audio playback|
|
||||||
|
|
||||||
### Moving in the interface
|
### Moving in the interface
|
||||||
@ -26,15 +26,15 @@ Topos is made to be controlled entirely with a keyboard. It is recommanded to st
|
|||||||
|----------|-------|------------------------------------------------------------|
|
|----------|-------|------------------------------------------------------------|
|
||||||
|Universe switch|${key_shortcut("Ctrl + B")}|Switch to a new universe|
|
|Universe switch|${key_shortcut("Ctrl + B")}|Switch to a new universe|
|
||||||
|Global Script|${key_shortcut("Ctrl + G")} or ${key_shortcut(
|
|Global Script|${key_shortcut("Ctrl + G")} or ${key_shortcut(
|
||||||
"F10"
|
"F10",
|
||||||
)}|Switch to global script |
|
)}|Switch to global script |
|
||||||
|Local scripts|${key_shortcut("Ctrl + L")} or ${key_shortcut(
|
|Local scripts|${key_shortcut("Ctrl + L")} or ${key_shortcut(
|
||||||
"F11"
|
"F11",
|
||||||
)}|Switch to local scripts |
|
)}|Switch to local scripts |
|
||||||
|Init script|${key_shortcut("Ctrl + L")}|Switch to init script|
|
|Init script|${key_shortcut("Ctrl + L")}|Switch to init script|
|
||||||
|Note File|${key_shortcut("Ctrl + N")}|Switch to note file|
|
|Note File|${key_shortcut("Ctrl + N")}|Switch to note file|
|
||||||
|Local Script|${key_shortcut("F1")} to ${key_shortcut(
|
|Local Script|${key_shortcut("F1")} to ${key_shortcut(
|
||||||
"F9"
|
"F9",
|
||||||
)}|Switch to a specific local script|
|
)}|Switch to a specific local script|
|
||||||
|Documentation|${key_shortcut("Ctrl + D")}|Open the documentation|
|
|Documentation|${key_shortcut("Ctrl + D")}|Open the documentation|
|
||||||
|
|
||||||
@ -44,24 +44,28 @@ Topos is made to be controlled entirely with a keyboard. It is recommanded to st
|
|||||||
|----------|-------|------------------------------------------------------------|
|
|----------|-------|------------------------------------------------------------|
|
||||||
|Evaluate|${key_shortcut("Ctrl + Enter")}| Evaluate the current script |
|
|Evaluate|${key_shortcut("Ctrl + Enter")}| Evaluate the current script |
|
||||||
|Local Eval|${key_shortcut("Ctrl + F1")} to ${key_shortcut(
|
|Local Eval|${key_shortcut("Ctrl + F1")} to ${key_shortcut(
|
||||||
"Ctrl + F9"
|
"Ctrl + F9",
|
||||||
)}|Local File Evaluation|
|
)}|Local File Evaluation|
|
||||||
|Force Eval|${key_shortcut(
|
|Force Eval|${key_shortcut(
|
||||||
"Ctrl + Shift + Enter"
|
"Ctrl + Shift + Enter",
|
||||||
)}|Force evaluation of the current script|
|
)}|Force evaluation of the current script|
|
||||||
|
|Clear cache & Eval|${key_shortcut(
|
||||||
|
"Ctrl + Shift + Backspace (Delete)",
|
||||||
|
)}|Clears cache and forces evaluation to update cached scripts|
|
||||||
|
|
||||||
### Special
|
### Special
|
||||||
|
|
||||||
| Shortcut | Key | Description |
|
| Shortcut | Key | Description |
|
||||||
|----------|-------|------------------------------------------------------------|
|
|----------|-------|------------------------------------------------------------|
|
||||||
|Vim Mode|${key_shortcut("Ctrl + V")}| Switch between Vim and Normal Mode|
|
|Vim Mode|${key_shortcut("Ctrl + V")}| Switch between Vim and Normal Mode|
|
||||||
|
|Maximize|${key_shortcut("Ctrl + M")}| Show/Hide the interface|
|
||||||
|
|
||||||
# Keyboard Fill
|
# Keyboard Fill
|
||||||
|
|
||||||
By pressing the ${key_shortcut(
|
By pressing the ${key_shortcut(
|
||||||
"Alt"
|
"Alt",
|
||||||
)} key, you can trigger the <ic>Fill</ic> mode which can either be <ic>true</ic> or <ic>false</ic>. The fill will be set to <ic>true</ic> as long as the key is held. Try pressing ${key_shortcut(
|
)} key, you can trigger the <ic>Fill</ic> mode which can either be <ic>true</ic> or <ic>false</ic>. The fill will be set to <ic>true</ic> as long as the key is held. Try pressing ${key_shortcut(
|
||||||
"Alt"
|
"Alt",
|
||||||
)} when playing this example:
|
)} when playing this example:
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -69,7 +73,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
beat(fill() ? 1/4 : 1/2)::sound('cp').out()
|
beat(fill() ? 1/4 : 1/2)::sound('cp').out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
export const mouse = (app: Editor): string => {
|
export const mouse = (app: Editor): string => {
|
||||||
let makeExample = makeExampleFactory(app);
|
let makeExample = makeExampleFactory(app);
|
||||||
@ -27,7 +27,7 @@ beat(.25) :: sound('sine')
|
|||||||
.pan(r(0, 1))
|
.pan(r(0, 1))
|
||||||
.room(0.35).size(4).out()
|
.room(0.35).size(4).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
@ -46,7 +46,7 @@ beat(.25) :: sound('sine')
|
|||||||
.delay(0.5).delayt(1/6).delayfb(0.2)
|
.delay(0.5).delayt(1/6).delayfb(0.2)
|
||||||
.note(noteX())
|
.note(noteX())
|
||||||
.room(0.35).size(4).out()`,
|
.room(0.35).size(4).out()`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Mouse and Arrays
|
## Mouse and Arrays
|
||||||
@ -64,7 +64,7 @@ log([1,2,3,4].mouseX())
|
|||||||
log([4,5,6,7].mouseY())
|
log([4,5,6,7].mouseY())
|
||||||
|
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
@ -1,6 +1,6 @@
|
|||||||
import { makeExampleFactory, key_shortcut } from "../../Documentation";
|
import { makeExampleFactory, key_shortcut } from "../Documentation";
|
||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { examples } from "../../examples/excerpts";
|
import { examples } from "../excerpts";
|
||||||
|
|
||||||
export const introduction = (application: Editor): string => {
|
export const introduction = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -8,13 +8,13 @@ export const introduction = (application: Editor): string => {
|
|||||||
# Welcome
|
# Welcome
|
||||||
|
|
||||||
Welcome to the **Topos** documentation. You can jump here anytime by pressing ${key_shortcut(
|
Welcome to the **Topos** documentation. You can jump here anytime by pressing ${key_shortcut(
|
||||||
"Ctrl + D"
|
"Ctrl + D",
|
||||||
)}. Press again to make the documentation disappear. Contributions are much appreciated! The documentation [lives here](https://github.com/Bubobubobubobubo/topos/tree/main/src/documentation).
|
)}. Press again to make the documentation disappear. Contributions are much appreciated! The documentation [lives here](https://github.com/Bubobubobubobubo/topos/tree/main/src/documentation).
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Welcome! Eval to get started",
|
"Welcome! Eval to get started",
|
||||||
examples[Math.floor(Math.random() * examples.length)],
|
examples[Math.floor(Math.random() * examples.length)],
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
# What is Topos?
|
# What is Topos?
|
||||||
@ -30,7 +30,7 @@ rhythm(.25, [5, 7].beat(2), 8) :: sound(['hc', 'fikea', 'hat'].pick(1))
|
|||||||
.db(-ir(1,8)).speed([1,[0.5, 2].pick()]).room(0.5).size(3).o(4).out()
|
.db(-ir(1,8)).speed([1,[0.5, 2].pick()]).room(0.5).size(3).o(4).out()
|
||||||
beat([2,0.5].dur(13.5, 0.5))::snd('fsoftsnare')
|
beat([2,0.5].dur(13.5, 0.5))::snd('fsoftsnare')
|
||||||
.n(0).speed([1, 0.5]).o(4).out()`,
|
.n(0).speed([1, 0.5]).o(4).out()`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -47,7 +47,7 @@ beat(.25)::snd('sine')
|
|||||||
.delay(0.5).delayt(0.25).delayfb(0.7) // Delay
|
.delay(0.5).delayt(0.25).delayfb(0.7) // Delay
|
||||||
.room(0.5).size(8) // Reverb
|
.room(0.5).size(8) // Reverb
|
||||||
.out()`,
|
.out()`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -58,15 +58,18 @@ beat(.5) :: sound('sid').n($(2))
|
|||||||
beat(.25) :: sound('sid').note(
|
beat(.25) :: sound('sid').note(
|
||||||
[34, 36, 41].beat(.25) + [[0,-24].pick(),12].beat())
|
[34, 36, 41].beat(.25) + [[0,-24].pick(),12].beat())
|
||||||
.room(0.9).size(0.9).n(4).out()`,
|
.room(0.9).size(0.9).n(4).out()`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
Topos is deeply inspired by the [Monome Teletype](https://monome.org/). The Teletype is/was an open source hardware module for Eurorack synthesizers. While the Teletype was initially born as an hardware module, Topos aims to be a web-browser based cousin of it! It is a sequencer, a scriptable interface, a companion for algorithmic music-making. Topos wishes to fullfill the same goal as the Teletype, keeping the same spirit alive on the web. It is free, open-source, and made to be shared and used by everyone. Learn more about live coding on [livecoding.fr](https://livecoding.fr).
|
Topos is deeply inspired by the [Monome Teletype](https://monome.org/). The Teletype is/was an open source hardware module for Eurorack synthesizers. While the Teletype was initially born as an hardware module, Topos aims to be a web-browser based cousin of it! It is a sequencer, a scriptable interface, a companion for algorithmic music-making. Topos wishes to fullfill the same goal as the Teletype, keeping the same spirit alive on the web. It is free, open-source, and made to be shared and used by everyone. Learn more about live coding on [livecoding.fr](https://livecoding.fr).
|
||||||
|
|
||||||
## Demo Songs
|
## Alternative documentation source (.pdf)
|
||||||
|
|
||||||
|
You can also find a .pdf version listing the principal commands and functions [here](https://github.com/Bubobubobubobubo/topos/blob/main/src/documentation/basics/TOPOS_COMMANDS.pdf). This document has been generated by Chris Collis. It recaps the main sections of this documentation and can be a good companion while learning Topos.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
<p>You can <a href='https://ko-fi.com/I2I2RSBHF' target='_blank'><img height='36' style='display: inline; border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi3.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a> to support the development :) </p>
|
||||||
|
|
||||||
Reloading the application will get you one random song example to study every time. Press ${key_shortcut(
|
|
||||||
"F5"
|
|
||||||
)} and listen to them all! The demo songs are also used a bit everywhere in the documentation to illustrate some of the working principles :).
|
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
@ -733,8 +733,8 @@ const completionDatabase: CompletionDatabase = {
|
|||||||
midi: {
|
midi: {
|
||||||
name: "midi",
|
name: "midi",
|
||||||
category: "midi",
|
category: "midi",
|
||||||
description: "Send a MIDI message",
|
description: "Send a MIDI message (note, velocity, channel)",
|
||||||
example: "midi(144, 60, 100)",
|
example: "midi(144, 60, 1)",
|
||||||
},
|
},
|
||||||
control_change: {
|
control_change: {
|
||||||
name: "control_change",
|
name: "control_change",
|
||||||
@ -742,6 +742,12 @@ const completionDatabase: CompletionDatabase = {
|
|||||||
description: "Send a MIDI control change message",
|
description: "Send a MIDI control change message",
|
||||||
example: "control_change({control: 1, value: 60, channel: 10})",
|
example: "control_change({control: 1, value: 60, channel: 10})",
|
||||||
},
|
},
|
||||||
|
cc: {
|
||||||
|
name: "cc",
|
||||||
|
category: "midi",
|
||||||
|
description: "Send a MIDI control change message",
|
||||||
|
example: "cc({control: 1, value: 60, channel: 10})",
|
||||||
|
},
|
||||||
program_change: {
|
program_change: {
|
||||||
name: "program_change",
|
name: "program_change",
|
||||||
category: "midi",
|
category: "midi",
|
||||||
@ -782,7 +788,7 @@ const completionDatabase: CompletionDatabase = {
|
|||||||
name: "counter",
|
name: "counter",
|
||||||
category: "patterns",
|
category: "patterns",
|
||||||
description: "Counter/iterator",
|
description: "Counter/iterator",
|
||||||
example: "counter('my_counter_, 20, 1)",
|
example: "counter('my_counter', 20, 1)",
|
||||||
},
|
},
|
||||||
drunk: {
|
drunk: {
|
||||||
name: "drunk",
|
name: "drunk",
|
||||||
@ -808,11 +814,17 @@ const completionDatabase: CompletionDatabase = {
|
|||||||
description: "Wraps (or not) of the drunk walk (boolean)",
|
description: "Wraps (or not) of the drunk walk (boolean)",
|
||||||
example: "drunk_wrap(true)",
|
example: "drunk_wrap(true)",
|
||||||
},
|
},
|
||||||
v: {
|
global: {
|
||||||
name: "v",
|
name: "global",
|
||||||
category: "variable",
|
category: "variable",
|
||||||
description: "Global Variable setter or getter",
|
description: "Global Variable setter or getter",
|
||||||
example: "v('my_var', 10) // Sets global variable 'my_var' to 10",
|
example: "global.my_var = 10; // Sets global variable 'my_var' to 10",
|
||||||
|
},
|
||||||
|
g: {
|
||||||
|
name: "g",
|
||||||
|
category: "variable",
|
||||||
|
description: "Global Variable setter or getter",
|
||||||
|
example: "g.my_var = 10; // Sets global variable 'my_var' to 10",
|
||||||
},
|
},
|
||||||
delete_variable: {
|
delete_variable: {
|
||||||
name: "delete_variable",
|
name: "delete_variable",
|
||||||
@ -898,12 +910,6 @@ const completionDatabase: CompletionDatabase = {
|
|||||||
description: "Detects if the Alt key is pressed",
|
description: "Detects if the Alt key is pressed",
|
||||||
example: "fill() ? 1 : 0.5",
|
example: "fill() ? 1 : 0.5",
|
||||||
},
|
},
|
||||||
comp: {
|
|
||||||
name: "comp",
|
|
||||||
category: "synthesis",
|
|
||||||
description: "Compressor threshold (dB)",
|
|
||||||
example: "sound('sine').comp(-4).out()",
|
|
||||||
},
|
|
||||||
ratio: {
|
ratio: {
|
||||||
name: "ratio",
|
name: "ratio",
|
||||||
category: "synthesis",
|
category: "synthesis",
|
||||||
@ -934,7 +940,7 @@ const completionDatabase: CompletionDatabase = {
|
|||||||
description: "Noise amount in the signal (0-1)",
|
description: "Noise amount in the signal (0-1)",
|
||||||
example: "sound('triangle').noise(.25).out()",
|
example: "sound('triangle').noise(.25).out()",
|
||||||
},
|
},
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
export const inlineHoveringTips = hoverTooltip(
|
export const inlineHoveringTips = hoverTooltip(
|
||||||
(view: any, pos: any, side: any) => {
|
(view: any, pos: any, side: any) => {
|
||||||
@ -956,20 +962,19 @@ export const inlineHoveringTips = hoverTooltip(
|
|||||||
) {
|
) {
|
||||||
return { dom: document.createElement("div") };
|
return { dom: document.createElement("div") };
|
||||||
}
|
}
|
||||||
let completion =
|
let completion = completionDatabase[text.slice(start - from, end - from)]!;
|
||||||
completionDatabase[text.slice(start - from, end - from)] || {};
|
|
||||||
let divContent = `
|
let divContent = `
|
||||||
<h1 class="text-orange-300 text-base pb-1">${completion.name} [<em class="text-white">${completion.category}</em>]</h1>
|
<h1 class="text-brightwhite text-base pb-1">${completion.name} [<em class="text-white">${completion.category}</em>]</h1>
|
||||||
<p class="text-base pl-4">${completion.description}</p>
|
<p class="text-base pl-4">${completion.description}</p>
|
||||||
<pre class="-mt-2"><code class="pl-4 text-base">${completion.example}</code></pre></div>
|
<pre class="-mt-2"><code class="pl-4 text-base">${completion.example}</code></pre></div>
|
||||||
`;
|
`;
|
||||||
let dom = document.createElement("div");
|
let dom = document.createElement("div");
|
||||||
dom.classList.add("px-4", "py-2", "bg-neutral-700", "rounded-lg");
|
dom.classList.add("px-4", "py-2", "bg-background", "rounded-lg");
|
||||||
dom.innerHTML = divContent;
|
dom.innerHTML = divContent;
|
||||||
return { dom };
|
return { dom };
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const toposCompletions = (context: CompletionContext) => {
|
export const toposCompletions = (context: CompletionContext) => {
|
||||||
@ -980,13 +985,13 @@ export const toposCompletions = (context: CompletionContext) => {
|
|||||||
from: word.from,
|
from: word.from,
|
||||||
options: Object.keys(completionDatabase).map((key) => ({
|
options: Object.keys(completionDatabase).map((key) => ({
|
||||||
label: key,
|
label: key,
|
||||||
type: completionDatabase[key].category,
|
type: completionDatabase[key]!.category,
|
||||||
info: () => {
|
info: () => {
|
||||||
let div = document.createElement("div");
|
let div = document.createElement("div");
|
||||||
div.innerHTML = `
|
div.innerHTML = `
|
||||||
<h1 class="text-orange-300 text-base pb-1">${completionDatabase[key].name} [<em class="text-white">${completionDatabase[key].category}</em>]</h1>
|
<h1 class="text-brightwhite text-base pb-1">${completionDatabase[key]!.name} [<em class="text-white">${completionDatabase[key]!.category}</em>]</h1>
|
||||||
<p class="text-base pl-4">${completionDatabase[key].description}</p>
|
<p class="text-base pl-4">${completionDatabase[key]!.description}</p>
|
||||||
<div class="overflow-hidden overflow-scroll rounded px-2 ml-4 mt-2 bg-neutral-800"><code class="text-sm">${completionDatabase[key].example}</code></div>
|
<div class="overflow-hidden overflow-scroll rounded px-2 ml-4 mt-2 bg-neutral-800"><code class="text-sm">${completionDatabase[key]!.example}</code></div>
|
||||||
`;
|
`;
|
||||||
div.classList.add("px-4", "py-2", "rounded-lg", "w-92");
|
div.classList.add("px-4", "py-2", "rounded-lg", "w-92");
|
||||||
return div;
|
return div;
|
||||||
@ -994,6 +999,7 @@ export const toposCompletions = (context: CompletionContext) => {
|
|||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
return null
|
||||||
};
|
};
|
||||||
|
|
||||||
export const soundCompletions = (context: CompletionContext) => {
|
export const soundCompletions = (context: CompletionContext) => {
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const amplitude = (application: Editor): string => {
|
export const amplitude = (application: Editor): string => {
|
||||||
@ -20,7 +20,7 @@ ${makeExample(
|
|||||||
"Velocity manipulated by a counter",
|
"Velocity manipulated by a counter",
|
||||||
`
|
`
|
||||||
beat(.5)::snd('cp').vel($(1)%10 / 10).out()`,
|
beat(.5)::snd('cp').vel($(1)%10 / 10).out()`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Amplitude Enveloppe
|
## Amplitude Enveloppe
|
||||||
@ -33,24 +33,25 @@ beat(.5)::snd('cp').vel($(1)%10 / 10).out()`,
|
|||||||
| <ic>decay</ic> | dec | Decay value (time to decay to sustain level) |
|
| <ic>decay</ic> | dec | Decay value (time to decay to sustain level) |
|
||||||
| <ic>sustain</ic> | sus | Sustain value (gain when sound is held) |
|
| <ic>sustain</ic> | sus | Sustain value (gain when sound is held) |
|
||||||
| <ic>release</ic> | rel | Release value (time for the sound to die off) |
|
| <ic>release</ic> | rel | Release value (time for the sound to die off) |
|
||||||
|
| <ic>adsr</ic> | | Shortcut that combines all the parameters together |
|
||||||
|
|
||||||
Note that the **sustain** value is not a duration but an amplitude value (how loud). The other values are the time for each stage to take place. Here is a fairly complete example using the <ic>sawtooth</ic> basic waveform.
|
Note that the **sustain** value is not a duration but an amplitude value (how loud). The other values are the time for each stage to take place. Here is a fairly complete example using the <ic>sawtooth</ic> basic waveform.
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Simple synthesizer",
|
"Simple synthesizer",
|
||||||
`
|
`
|
||||||
let smooth = (sound) => {
|
register("smooth", x => x.cutoff(r(100,500))
|
||||||
return sound.cutoff(r(100,500))
|
|
||||||
.lpadsr(usaw(1/8) * 8, 0.05, .125, 0, 0)
|
.lpadsr(usaw(1/8) * 8, 0.05, .125, 0, 0)
|
||||||
.gain(r(0.25, 0.4)).adsr(0, r(.2,.4), r(0,0.5), 0)
|
.gain(r(0.25, 0.4)).adsr(0, r(.2,.4), r(0,0.5), 0)
|
||||||
.room(0.9).size(2).o(2).vib(r(2,8)).vibmod(0.125)
|
.room(0.9).size(2).o(2).vib(r(2,8)).vibmod(0.125))
|
||||||
}
|
beat(.25)::sound('sawtooth')
|
||||||
beat(.25)::smooth(sound('sawtooth')
|
.note([50,57,55,60].beat(1))
|
||||||
.note([50,57,55,60].beat(1))).out();
|
.smooth().out();
|
||||||
beat(.25)::smooth(sound('sawtooth')
|
beat(.25)::sound('sawtooth')
|
||||||
.note([50,57,55,60].add(12).beat(1.5))).out();
|
.note([50,57,55,60].add(12).beat(1.5))
|
||||||
|
.smooth().out();
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
Sometimes, using a full ADSR envelope is a bit overkill. There are other simpler controls to manipulate the envelope like the <ic>.ad</ic> method:
|
Sometimes, using a full ADSR envelope is a bit overkill. There are other simpler controls to manipulate the envelope like the <ic>.ad</ic> method:
|
||||||
@ -59,20 +60,19 @@ Sometimes, using a full ADSR envelope is a bit overkill. There are other simpler
|
|||||||
${makeExample(
|
${makeExample(
|
||||||
"Replacing .adsr by .ad",
|
"Replacing .adsr by .ad",
|
||||||
`
|
`
|
||||||
let smooth = (sound) => {
|
register("smooth", x => x.cutoff(r(100,500))
|
||||||
return sound.cutoff(r(100,500))
|
|
||||||
.lpadsr(usaw(1/8) * 8, 0.05, .125, 0, 0)
|
.lpadsr(usaw(1/8) * 8, 0.05, .125, 0, 0)
|
||||||
.gain(r(0.25, 0.4)).ad(0, .25)
|
.gain(r(0.25, 0.4)).ad(0, 0.25)
|
||||||
.room(0.9).size(2).o(2).vib(r(2,8)).vibmod(0.125)
|
.room(0.9).size(2).o(2).vib(r(2,8)).vibmod(0.125))
|
||||||
}
|
beat(.25)::sound('sawtooth')
|
||||||
beat(.25)::smooth(sound('sawtooth')
|
.note([50,57,55,60].beat(1))
|
||||||
.note([50,57,55,60].beat(1))).out();
|
.smooth().out();
|
||||||
beat(.25)::smooth(sound('sawtooth')
|
beat(.25)::sound('sawtooth')
|
||||||
.note([50,57,55,60].add(12).beat(1.5))).out();
|
.note([50,57,55,60].add(12).beat(1.5))
|
||||||
|
.smooth().out();
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
`}
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const audio_basics = (application: Editor): string => {
|
export const audio_basics = (application: Editor): string => {
|
||||||
@ -22,7 +22,7 @@ ${makeExample(
|
|||||||
beat(1) && sound('bd').out()
|
beat(1) && sound('bd').out()
|
||||||
beat(0.5) && sound('hh').out()
|
beat(0.5) && sound('hh').out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
These commands, in plain english, can be translated to:
|
These commands, in plain english, can be translated to:
|
||||||
@ -38,7 +38,7 @@ ${makeExample(
|
|||||||
beat(1) && sound('bd').coarse(0.25).room(0.5).orbit(2).out();
|
beat(1) && sound('bd').coarse(0.25).room(0.5).orbit(2).out();
|
||||||
beat(0.5) && sound('hh').delay(0.25).delaytime(0.125).out();
|
beat(0.5) && sound('hh').delay(0.25).delaytime(0.125).out();
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
Now, it translates as follows:
|
Now, it translates as follows:
|
||||||
@ -53,11 +53,13 @@ If you remove <ic>beat</ic> instruction, you will end up with a deluge of kick d
|
|||||||
|
|
||||||
To play a sound, you always need the <ic>.out()</ic> method at the end of your chain. THis method tells **Topos** to send the chain to the audio engine. The <ic>.out</ic> method can take an optional argument to send the sound to a numbered effect bus, from <ic>0</ic> to <ic>n</ic> :
|
To play a sound, you always need the <ic>.out()</ic> method at the end of your chain. THis method tells **Topos** to send the chain to the audio engine. The <ic>.out</ic> method can take an optional argument to send the sound to a numbered effect bus, from <ic>0</ic> to <ic>n</ic> :
|
||||||
|
|
||||||
${makeExample("Using the .out method",
|
${makeExample(
|
||||||
|
"Using the .out method",
|
||||||
`
|
`
|
||||||
// Playing a clap on the third bus (0-indexed)
|
// Playing a clap on the third bus (0-indexed)
|
||||||
beat(1)::sound('cp').out(2)
|
beat(1)::sound('cp').out(2)
|
||||||
`, true
|
`,
|
||||||
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
Try to remove <ic>.out</ic>. You will see that no sound is playing at all!
|
Try to remove <ic>.out</ic>. You will see that no sound is playing at all!
|
||||||
@ -67,7 +69,7 @@ Try to remove <ic>.out</ic>. You will see that no sound is playing at all!
|
|||||||
- Sounds are **composed** by adding qualifiers/parameters that modify the sound or synthesizer you have picked (_e.g_ <ic>sound('...').blabla(...)..something(...).out()</ic>. Think of it as _audio chains_.
|
- Sounds are **composed** by adding qualifiers/parameters that modify the sound or synthesizer you have picked (_e.g_ <ic>sound('...').blabla(...)..something(...).out()</ic>. Think of it as _audio chains_.
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
'Complex sonic object',
|
"Complex sonic object",
|
||||||
`
|
`
|
||||||
beat(1) :: sound('pad').n(1)
|
beat(1) :: sound('pad').n(1)
|
||||||
.begin(rand(0, 0.4))
|
.begin(rand(0, 0.4))
|
||||||
@ -75,7 +77,7 @@ beat(1) :: sound('pad').n(1)
|
|||||||
.size(0.9).room(0.9)
|
.size(0.9).room(0.9)
|
||||||
.velocity(0.25)
|
.velocity(0.25)
|
||||||
.pan(usine()).release(2).out()`,
|
.pan(usine()).release(2).out()`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Picking a specific sound
|
## Picking a specific sound
|
||||||
@ -104,7 +106,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
beat(1) && sound('kick').n([1,2,3,4,5,6,7,8].pick()).out()
|
beat(1) && sound('kick').n([1,2,3,4,5,6,7,8].pick()).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
You can also use the <ic>:</ic> to pick a sample number directly from the <ic>sound</ic> function:
|
You can also use the <ic>:</ic> to pick a sample number directly from the <ic>sound</ic> function:
|
||||||
@ -114,7 +116,7 @@ You can also use the <ic>:</ic> to pick a sample number directly from the <ic>so
|
|||||||
`
|
`
|
||||||
beat(1) && sound('kick:3').out()
|
beat(1) && sound('kick:3').out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
You can use any number to pick a sound. Don't be afraid of using a number too big. If the number exceeds the number of available samples, it will simply wrap around and loop infinitely over the folder. Let's demonstrate this by using the mouse over a very large sample folder:
|
You can use any number to pick a sound. Don't be afraid of using a number too big. If the number exceeds the number of available samples, it will simply wrap around and loop infinitely over the folder. Let's demonstrate this by using the mouse over a very large sample folder:
|
||||||
@ -124,7 +126,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
// Move your mouse to change the sample being used!
|
// Move your mouse to change the sample being used!
|
||||||
beat(.25) && sound('ST09').n(Math.floor(mouseX())).out()`,
|
beat(.25) && sound('ST09').n(Math.floor(mouseX())).out()`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
@ -147,14 +149,18 @@ There is a special method to choose the _orbit_ that your sound is going to use:
|
|||||||
|
|
||||||
You can play a sound _dry_ and another sound _wet_. Take a look at this example where the reverb is only affecting one of the sounds:
|
You can play a sound _dry_ and another sound _wet_. Take a look at this example where the reverb is only affecting one of the sounds:
|
||||||
|
|
||||||
${makeExample("Dry and wet", `
|
${makeExample(
|
||||||
|
"Dry and wet",
|
||||||
|
`
|
||||||
|
|
||||||
// This sound is dry
|
// This sound is dry
|
||||||
beat(1)::sound('hh').out()
|
beat(1)::sound('hh').out()
|
||||||
|
|
||||||
// This sound is wet (reverb)
|
// This sound is wet (reverb)
|
||||||
beat(2)::sound('cp').orbit(2).room(0.5).size(8).out()
|
beat(2)::sound('cp').orbit(2).room(0.5).size(8).out()
|
||||||
`, true)}
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
## The art of chaining
|
## The art of chaining
|
||||||
|
|
||||||
@ -168,10 +174,9 @@ beat(0.25) && sound('fhh')
|
|||||||
.room(0.9).size(0.9).gain(1)
|
.room(0.9).size(0.9).gain(1)
|
||||||
.cutoff(usine(1/2) * 5000)
|
.cutoff(usine(1/2) * 5000)
|
||||||
.out()`,
|
.out()`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
Most audio parameters can be used both for samples and synthesizers. This is quite unconventional if you are familiar with a more traditional music software.
|
Most audio parameters can be used both for samples and synthesizers. This is quite unconventional if you are familiar with a more traditional music software.
|
||||||
`}
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const distortion = (application: Editor): string => {
|
export const distortion = (application: Editor): string => {
|
||||||
@ -23,9 +23,8 @@ ${makeExample(
|
|||||||
beat(.5)::snd('pad').coarse($(1) % 16).clip(.5).out(); // Comment me
|
beat(.5)::snd('pad').coarse($(1) % 16).clip(.5).out(); // Comment me
|
||||||
beat(.5)::snd('pad').crush([16, 8, 4].beat(2)).clip(.5).out()
|
beat(.5)::snd('pad').crush([16, 8, 4].beat(2)).clip(.5).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
`}
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const reverb = (application: Editor): string => {
|
export const effects = (application: Editor): string => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
return `
|
return `
|
||||||
@ -28,7 +28,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
beat(2)::snd('cp').room(0.5).size(4).out()
|
beat(2)::snd('cp').room(0.5).size(4).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
## Delay
|
## Delay
|
||||||
@ -42,10 +42,13 @@ A good sounding delay unit that can go into feedback territory. Use it without m
|
|||||||
| <ic>delayfeedback</ic> | delayfb | Delay feedback (between <ic>0</ic> and <ic>1</ic>) |
|
| <ic>delayfeedback</ic> | delayfb | Delay feedback (between <ic>0</ic> and <ic>1</ic>) |
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Who doesn't like delay?", `
|
"Who doesn't like delay?",
|
||||||
|
`
|
||||||
beat(2)::snd('cp').delay(0.5).delaytime(0.75).delayfb(0.8).out()
|
beat(2)::snd('cp').delay(0.5).delaytime(0.75).delayfb(0.8).out()
|
||||||
beat(4)::snd('snare').out()
|
beat(4)::snd('snare').out()
|
||||||
beat(1)::snd('kick').out()`, true)}
|
beat(1)::snd('kick').out()`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
## Phaser
|
## Phaser
|
||||||
|
|
||||||
@ -56,13 +59,17 @@ beat(1)::snd('kick').out()`, true)}
|
|||||||
| <ic>phaserSweep</ic> | <ic>phassweep</ic> | Phaser frequency sweep (in hertz) |
|
| <ic>phaserSweep</ic> | <ic>phassweep</ic> | Phaser frequency sweep (in hertz) |
|
||||||
| <ic>phaserCenter</ic> | <ic>phascenter</ic> | Phaser center frequency (default to 1000) |
|
| <ic>phaserCenter</ic> | <ic>phascenter</ic> | Phaser center frequency (default to 1000) |
|
||||||
|
|
||||||
${makeExample("Super cool phaser lick", `
|
${makeExample(
|
||||||
|
"Super cool phaser lick",
|
||||||
|
`
|
||||||
rhythm(.5, 7, 8)::sound('wt_stereo')
|
rhythm(.5, 7, 8)::sound('wt_stereo')
|
||||||
.phaser(0.75).phaserSweep(3000)
|
.phaser(0.75).phaserSweep(3000)
|
||||||
.phaserCenter(1500).phaserDepth(1)
|
.phaserCenter(1500).phaserDepth(1)
|
||||||
.note([0, 1, 2, 3, 4, 5, 6].scale('pentatonic', 50).beat(0.25))
|
.note([0, 1, 2, 3, 4, 5, 6].scale('pentatonic', 50).beat(0.25))
|
||||||
.room(0.5).size(4).out()
|
.room(0.5).size(4).out()
|
||||||
`, true)}
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
## Distorsion, saturation, destruction
|
## Distorsion, saturation, destruction
|
||||||
|
|
||||||
@ -81,6 +88,36 @@ ${makeExample(
|
|||||||
beat(.5)::snd('pad').coarse($(1) % 16).clip(.5).out(); // Comment me
|
beat(.5)::snd('pad').coarse($(1) % 16).clip(.5).out(); // Comment me
|
||||||
beat(.5)::snd('pad').crush([16, 8, 4].beat(2)).clip(.5).out()
|
beat(.5)::snd('pad').crush([16, 8, 4].beat(2)).clip(.5).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)};
|
)};
|
||||||
`}
|
|
||||||
|
## Vibrato
|
||||||
|
|
||||||
|
You can also add some amount of vibrato to the sound using the <ic>vib</ic> and <ic>vibmod</ic> methods. These can turn any oscillator into something more lively and/or into a sound effect when used with a high amount of modulation.
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Different vibrato settings",
|
||||||
|
`
|
||||||
|
tempo(140);
|
||||||
|
beat(1) :: sound('triangle')
|
||||||
|
.freq(400).release(0.2)
|
||||||
|
.vib([1/2, 1, 2, 4].beat())
|
||||||
|
.vibmod([1,2,4,8].beat(2))
|
||||||
|
.out()`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
|
## Compression
|
||||||
|
|
||||||
|
This effect is leveraging the basic WebAudio compressor. More information can be found about it on the [DynamicsCompressorNode](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode?retiredLocale=de#instance_properties) page. This can be come quite complex :)
|
||||||
|
|
||||||
|
| Method | Alias | Description |
|
||||||
|
|------------|-----------|---------------------------------|
|
||||||
|
| <ic>comp</ic> | cmp | Compressor threshold value (dB) over which compressor operates |
|
||||||
|
| <ic>ratio</ic> | rt | Compressor ratio: input amount in dB needed for 1dB change in the output |
|
||||||
|
| <ic>knee</ic> | kn | dB value defining the range over which the signal transitions to compressed section |
|
||||||
|
| <ic>compAttack</ic> | cmpa | In seconds, time to decrease the gain by 10db |
|
||||||
|
| <ic>compRelease</ic> | cmpr | In seconds, time to increase the gain by 10db |
|
||||||
|
|
||||||
|
`;
|
||||||
|
};
|
||||||
145
src/Docs/learning/audio_engine/filters.ts
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import { type Editor } from "../../../main";
|
||||||
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
|
export const filters = (application: Editor): string => {
|
||||||
|
const makeExample = makeExampleFactory(application);
|
||||||
|
return `
|
||||||
|
# Filters
|
||||||
|
|
||||||
|
Filters can be applied to both synthesizers and samples. They are used to shape the sound by removing or emphasizing certain frequencies. They are also used to create movement in the sound by modulating the cutoff frequency of the filter over time.
|
||||||
|
|
||||||
|
- **lowpass filter**: filters the high frequencies, keeping the low frequencies.
|
||||||
|
- **highpass filter**: filtering the low frequencies, keeping the high frequencies.
|
||||||
|
- **bandpass filter**: filters the low and high frequencies around a frequency band, keeping what's in the middle.
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Filtering the high frequencies of an oscillator",
|
||||||
|
`beat(.5) :: sound('sawtooth').cutoff(50 + usine(1/8) * 2000).out()`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
|
These filters all come with their own set of parameters. Note that we are describing the parameters of the three different filter types here. Choose the right parameters depending on the filter type you are using:
|
||||||
|
|
||||||
|
|
||||||
|
### Lowpass filter
|
||||||
|
|
||||||
|
| Method | Alias | Description |
|
||||||
|
|------------|-----------|---------------------------------|
|
||||||
|
| <ic>cutoff</ic> | <ic>lpf</ic> | cutoff frequency of the lowpass filter |
|
||||||
|
| <ic>resonance</ic> | <ic>lpq</ic> | resonance of the lowpass filter (0-1) |
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Filtering a bass",
|
||||||
|
`beat(.5) :: sound('jvbass').lpf([250,1000,8000].beat()).out()`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
|
### Highpass filter
|
||||||
|
|
||||||
|
| Method | Alias | Description |
|
||||||
|
|------------|-----------|---------------------------------|
|
||||||
|
| <ic>hcutoff</ic> | <ic>hpf</ic> | cutoff frequency of the highpass filter |
|
||||||
|
| <ic>hresonance</ic> | <ic>hpq</ic> | resonance of the highpass filter (0-1) |
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Filtering a noise source",
|
||||||
|
`beat(.5) :: sound('gtr').hpf([250,1000, 2000, 3000, 4000].beat()).end(0.5).out()`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
|
### Bandpass filter
|
||||||
|
|
||||||
|
| Method | Alias | Description |
|
||||||
|
|------------|-----------|---------------------------------|
|
||||||
|
| <ic>bandf</ic> | <ic>bpf</ic> | cutoff frequency of the bandpass filter |
|
||||||
|
| <ic>bandq</ic> | <ic>bpq</ic> | resonance of the bandpass filter (0-1) |
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Sweeping the filter on the same guitar sample",
|
||||||
|
`beat(.5) :: sound('gtr').bandf(100 + usine(1/8) * 4000).end(0.5).out()`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
|
Alternatively, <ic>lpf</ic>, <ic>hpf</ic> and <ic>bpf</ic> can take a second argument, the **resonance**.
|
||||||
|
|
||||||
|
## Filter order (type)
|
||||||
|
|
||||||
|
You can also use the <ic>ftype</ic> method to change the filter type (order). There are two types by default, <ic>12db</ic> for a gentle slope or <ic>24db</ic> for a really steep filtering slope. The <ic>24db</ic> type is particularly useful for substractive synthesis if you are trying to emulate some of the Moog or Prophet sounds:
|
||||||
|
|
||||||
|
- <ic>ftype(type: string)</ic>: sets the filter type (order), either <ic>12db</ic> or <ic>24db</ic>.
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Filtering a bass",
|
||||||
|
`beat(.5) :: sound('jvbass').ftype(['12db', '24db'].beat(4)).lpf([250,1000,8000].beat()).out()`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
|
## Filter envelopes
|
||||||
|
|
||||||
|
The examples we have studied so far are static. They filter the sound around a fixed cutoff frequency. To make the sound more interesting, you can use the ADSR filter envelopes to shape the filter cutoff frequency over time. You will always find amplitude and filter envelopes on most commercial synthesizers. This is done using the following methods:
|
||||||
|
|
||||||
|
### Lowpass envelope
|
||||||
|
|
||||||
|
| Method | Alias | Description |
|
||||||
|
|------------|-----------|---------------------------------|
|
||||||
|
| <ic>lpenv</ic> | <ic>lpe</ic> | lowpass frequency modulation amount (negative or positive) |
|
||||||
|
| <ic>lpattack</ic> | <ic>lpa</ic> | attack of the lowpass filter |
|
||||||
|
| <ic>lpdecay</ic> | <ic>lpd</ic> | decay of the lowpass filter |
|
||||||
|
| <ic>lpsustain</ic> | <ic>lps</ic> | sustain of the lowpass filter |
|
||||||
|
| <ic>lprelease</ic> | <ic>lpr</ic> | release of the lowpass filter |
|
||||||
|
| <ic>lpadsr</ic> | | (**takes five arguments**) set all the parameters |
|
||||||
|
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Filtering a sawtooth wave dynamically",
|
||||||
|
`beat(.5) :: sound('sawtooth').note([48,60].beat())
|
||||||
|
.cutoff(5000).lpa([0.05, 0.25, 0.5].beat(2))
|
||||||
|
.lpenv(-8).lpq(10).out()`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
|
### Highpass envelope
|
||||||
|
|
||||||
|
| Method | Alias | Description |
|
||||||
|
|------------|-----------|---------------------------------|
|
||||||
|
| <ic>hpenv</ic> | <ic>hpe</ic> | highpass frequency modulation amount (negative or positive) |
|
||||||
|
| <ic>hpattack</ic> | <ic>hpa</ic> | attack of the highpass filter |
|
||||||
|
| <ic>hpdecay</ic> | <ic>hpd</ic> | decay of the highpass filter |
|
||||||
|
| <ic>hpsustain</ic> | <ic>hps</ic> | sustain of the highpass filter |
|
||||||
|
| <ic>hprelease</ic> | <ic>hpr</ic> | release of the highpass filter |
|
||||||
|
| <ic>hpadsr</ic> | | (**takes five arguments**) set all the parameters |
|
||||||
|
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Let's use another filter using the same example",
|
||||||
|
`beat(.5) :: sound('sawtooth').note([48,60].beat())
|
||||||
|
.hcutoff(1000).hpa([0.05, 0.25, 0.5].beat(2))
|
||||||
|
.hpenv(8).hpq(10).out()`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
|
### Bandpass envelope
|
||||||
|
|
||||||
|
| Method | Alias | Description |
|
||||||
|
|------------|-----------|---------------------------------|
|
||||||
|
| <ic>bpenv</ic> | <ic>bpe</ic> | bandpass frequency modulation amount (negative or positive) |
|
||||||
|
| <ic>bpattack</ic> | <ic>bpa</ic> | attack of the bandpass filter |
|
||||||
|
| <ic>bpdecay</ic> | <ic>bpd</ic> | decay of the bandpass filter |
|
||||||
|
| <ic>bpsustain</ic> | <ic>bps</ic> | sustain of the bandpass filter |
|
||||||
|
| <ic>bprelease</ic> | <ic>bpr</ic> | release of the bandpass filter |
|
||||||
|
| <ic>bpadsr</ic> | | (**takes five arguments**) set all the parameters |
|
||||||
|
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"And the bandpass filter, just for fun",
|
||||||
|
`beat(.5) :: sound('sawtooth').note([48,60].beat())
|
||||||
|
.bandf([500,1000,2000].beat(2))
|
||||||
|
.bpa([0.25, 0.125, 0.5].beat(2) * 4)
|
||||||
|
.bpenv(-4).release(2).out()
|
||||||
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
`;
|
||||||
|
};
|
||||||
6
src/Docs/learning/audio_engine/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export { amplitude } from './amplitude';
|
||||||
|
export { effects } from './effects';
|
||||||
|
export { sampler } from './sampler';
|
||||||
|
export { synths } from './synths';
|
||||||
|
export { filters } from './filters';
|
||||||
|
export { audio_basics } from './audio_basics';
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const sampler = (application: Editor): string => {
|
export const sampler = (application: Editor): string => {
|
||||||
@ -38,7 +38,7 @@ beat(.5)::snd('pad').begin(0.2)
|
|||||||
.room(0.8).size(0.5)
|
.room(0.8).size(0.5)
|
||||||
.clip(1).out()
|
.clip(1).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
|
|
||||||
@ -46,37 +46,53 @@ beat(.5)::snd('pad').begin(0.2)
|
|||||||
|
|
||||||
Let's play with the <ic>speed</ic> parameter to control the pitch of sample playback:
|
Let's play with the <ic>speed</ic> parameter to control the pitch of sample playback:
|
||||||
|
|
||||||
${makeExample("Controlling the playback speed", `
|
${makeExample(
|
||||||
|
"Controlling the playback speed",
|
||||||
|
`
|
||||||
beat(0.5)::sound('notes')
|
beat(0.5)::sound('notes')
|
||||||
.speed([1,2,3,4].palindrome().beat(0.5)).out()
|
.speed([1,2,3,4].palindrome().beat(0.5)).out()
|
||||||
`, true)}
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
It also works by using negative values. It reverses the playback:
|
It also works by using negative values. It reverses the playback:
|
||||||
|
|
||||||
${makeExample("Playing samples backwards", `
|
${makeExample(
|
||||||
|
"Playing samples backwards",
|
||||||
|
`
|
||||||
beat(0.5)::sound('notes')
|
beat(0.5)::sound('notes')
|
||||||
.speed(-[1,2,3,4].palindrome().beat(0.5)).out()
|
.speed(-[1,2,3,4].palindrome().beat(0.5)).out()
|
||||||
`, true)}
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
Of course you can play melodies using samples:
|
Of course you can play melodies using samples:
|
||||||
|
|
||||||
|
|
||||||
${makeExample("Playing melodies using samples", `
|
${makeExample(
|
||||||
|
"Playing melodies using samples",
|
||||||
|
`
|
||||||
beat(0.5)::sound('notes')
|
beat(0.5)::sound('notes')
|
||||||
.room(0.5).size(4)
|
.room(0.5).size(4)
|
||||||
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.5)).out()
|
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.5)).out()
|
||||||
`, true)}
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
## Panning
|
## Panning
|
||||||
|
|
||||||
To pan samples, use the <ic>.pan</ic> method with a number between <ic>0</ic> and <ic>1</ic>.
|
To pan samples, use the <ic>.pan</ic> method with a number between <ic>0</ic> and <ic>1</ic>.
|
||||||
|
|
||||||
|
|
||||||
${makeExample("Playing melodies using samples", `
|
${makeExample(
|
||||||
|
"Playing melodies using samples",
|
||||||
|
`
|
||||||
beat(0.25)::sound('notes')
|
beat(0.25)::sound('notes')
|
||||||
.room(0.5).size(4).pan(r(0, 1))
|
.room(0.5).size(4).pan(r(0, 1))
|
||||||
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.25)).out()
|
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.25)).out()
|
||||||
`, true)}
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
## Looping over a sample
|
## Looping over a sample
|
||||||
@ -84,13 +100,17 @@ beat(0.25)::sound('notes')
|
|||||||
Using <ic>loop</ic> (<ic>1</ic> for looping), <ic>loopBegin</ic> and <ic>loopEnd</ic> (between <ic>0</ic> and <ic>1</ic>), you can loop over the length of a sample. It can be super effective to create granular effects.
|
Using <ic>loop</ic> (<ic>1</ic> for looping), <ic>loopBegin</ic> and <ic>loopEnd</ic> (between <ic>0</ic> and <ic>1</ic>), you can loop over the length of a sample. It can be super effective to create granular effects.
|
||||||
|
|
||||||
|
|
||||||
${makeExample("Granulation using loop", `
|
${makeExample(
|
||||||
|
"Granulation using loop",
|
||||||
|
`
|
||||||
beat(0.25)::sound('fikea').loop(1)
|
beat(0.25)::sound('fikea').loop(1)
|
||||||
.lpf(ir(2000, 5000))
|
.lpf(ir(2000, 5000))
|
||||||
.loopBegin(0).loopEnd(r(0, 1))
|
.loopBegin(0).loopEnd(r(0, 1))
|
||||||
.room(0.5).size(4).pan(r(0, 1))
|
.room(0.5).size(4).pan(r(0, 1))
|
||||||
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.25)).out()
|
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.25)).out()
|
||||||
`, true)}
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
## Stretching a sample
|
## Stretching a sample
|
||||||
|
|
||||||
@ -111,34 +131,45 @@ Sometimes, you will find it necessary to cut a sample. It can be because the sam
|
|||||||
|
|
||||||
Know about the <ic>begin</ic> and <ic>end</ic> parameters. They are not related to the sampler itself, but to the length of the event you are playing. Let's cut the granular example:
|
Know about the <ic>begin</ic> and <ic>end</ic> parameters. They are not related to the sampler itself, but to the length of the event you are playing. Let's cut the granular example:
|
||||||
|
|
||||||
${makeExample("Cutting a sample using end", `
|
${makeExample(
|
||||||
|
"Cutting a sample using end",
|
||||||
|
`
|
||||||
beat(0.25)::sound('notes')
|
beat(0.25)::sound('notes')
|
||||||
.end(usine(1/2)/0.5)
|
.end(usine(1/2)/0.5)
|
||||||
.room(0.5).size(4).pan(r(0, 1))
|
.room(0.5).size(4).pan(r(0, 1))
|
||||||
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.25)).out()
|
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.25)).out()
|
||||||
`, true)}
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
You can also use <ic>clip</ic> to cut the sample everytime a new sample comes in:
|
You can also use <ic>clip</ic> to cut the sample everytime a new sample comes in:
|
||||||
|
|
||||||
|
|
||||||
${makeExample("Cutting a sample using end", `
|
${makeExample(
|
||||||
|
"Cutting a sample using end",
|
||||||
|
`
|
||||||
beat(0.125)::sound('notes')
|
beat(0.125)::sound('notes')
|
||||||
.cut(1)
|
.cut(1)
|
||||||
.room(0.5).size(4).pan(r(0, 1))
|
.room(0.5).size(4).pan(r(0, 1))
|
||||||
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.125)
|
.note([0, 2, 3, 4, 5].scale('minor', 50).beat(0.125)
|
||||||
+ [-12,12].beat()).out()
|
+ [-12,12].beat()).out()
|
||||||
`, true)}
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
## Adding vibrato to samples
|
## Adding vibrato to samples
|
||||||
|
|
||||||
You can add vibrato to any sample using <ic>vib</ic> and <ic>vibmod</ic>:
|
You can add vibrato to any sample using <ic>vib</ic> and <ic>vibmod</ic>:
|
||||||
|
|
||||||
${makeExample("Adding vibrato to a sample", `
|
${makeExample(
|
||||||
|
"Adding vibrato to a sample",
|
||||||
|
`
|
||||||
|
|
||||||
beat(1)::sound('fhang').vib([1, 2, 4].bar()).vibmod([0.5, 2].beat()).out()
|
beat(1)::sound('fhang').vib([1, 2, 4].bar()).vibmod([0.5, 2].beat()).out()
|
||||||
`, true)}
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
`}
|
|
||||||
|
|
||||||
|
|
||||||
|
`;
|
||||||
|
};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const synths = (application: Editor): string => {
|
export const synths = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -17,20 +17,21 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
beat(.5) && snd(['sine', 'triangle', 'sawtooth', 'square'].beat()).freq(100).out()
|
beat(.5) && snd(['sine', 'triangle', 'sawtooth', 'square'].beat()).freq(100).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
Note that you can also use noise if you do not want to use a periodic oscillator:
|
Note that you can also use noise if you do not want to use a periodic oscillator:
|
||||||
|
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Listening to the different types of noise",
|
"Listening to the different types of noise",
|
||||||
`
|
`
|
||||||
beat(.5) && snd(['brown', 'pink', 'white'].beat()).adsr(0,.1,0,0).out()
|
beat(.5) && snd(['brown', 'pink', 'white', 'crackle'].beat()).adsr(0,.1,0,0).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
The <ic>crackle</ic> type can be controlled using the <ic>density</ic> parameter.
|
||||||
|
|
||||||
Two functions are primarily used to control the frequency of the synthesizer:
|
Two functions are primarily used to control the frequency of the synthesizer:
|
||||||
- <ic>freq(hz: number)</ic>: sets the frequency of the oscillator.
|
- <ic>freq(hz: number)</ic>: sets the frequency of the oscillator.
|
||||||
- <ic>note(note: number|string)</ic>: sets the MIDI note of the oscillator (MIDI note converted to hertz).
|
- <ic>note(note: number|string)</ic>: sets the MIDI note of the oscillator (MIDI note converted to hertz).
|
||||||
@ -40,7 +41,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
beat(.5) && snd('triangle').freq([100,200,400].beat(2)).out()
|
beat(.5) && snd('triangle').freq([100,200,400].beat(2)).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -48,7 +49,7 @@ beat(.5) && snd('triangle').freq([100,200,400].beat(2)).out()
|
|||||||
`
|
`
|
||||||
beat(.5) && snd('triangle').note([60,"F4"].pick()).out()
|
beat(.5) && snd('triangle').note([60,"F4"].pick()).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
Chords can also played using different parameters:
|
Chords can also played using different parameters:
|
||||||
@ -60,7 +61,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
beat(1) && snd('triangle').chord(["C","Em7","Fmaj7","Emin"].beat(2)).adsr(0,.2).out()
|
beat(1) && snd('triangle').chord(["C","Em7","Fmaj7","Emin"].beat(2)).adsr(0,.2).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -68,44 +69,10 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
beat(.5) && snd('triangle').chord(60,64,67,72).invert([1,-3,4,-5].pick()).adsr(0,.2).out()
|
beat(.5) && snd('triangle').chord(60,64,67,72).invert([1,-3,4,-5].pick()).adsr(0,.2).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Vibrato
|
# Controlling amplitude
|
||||||
|
|
||||||
You can also add some amount of vibrato to the sound using the <ic>vib</ic> and <ic>vibmod</ic> methods. These can turn any oscillator into something more lively and/or into a sound effect when used with a high amount of modulation.
|
|
||||||
|
|
||||||
${makeExample(
|
|
||||||
"Different vibrato settings",
|
|
||||||
`
|
|
||||||
tempo(140);
|
|
||||||
beat(1) :: sound('triangle')
|
|
||||||
.freq(400).release(0.2)
|
|
||||||
.vib([1/2, 1, 2, 4].beat())
|
|
||||||
.vibmod([1,2,4,8].beat(2))
|
|
||||||
.out()`,
|
|
||||||
true
|
|
||||||
)}
|
|
||||||
|
|
||||||
## Noise
|
|
||||||
|
|
||||||
A certain amount of brown noise can be added by using the <ic>.noise</ic> key:
|
|
||||||
|
|
||||||
${makeExample(
|
|
||||||
"Different vibrato settings",
|
|
||||||
`
|
|
||||||
tempo(140);
|
|
||||||
beat(1) :: sound('triangle')
|
|
||||||
.freq(400).release(0.2)
|
|
||||||
.noise([0.2,0.4,0.5].bar())
|
|
||||||
.vib([1/2, 1, 2, 4].beat())
|
|
||||||
.vibmod([1,2,4,8].beat(2))
|
|
||||||
.out()`,
|
|
||||||
true
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
|
||||||
## Controlling the amplitude
|
|
||||||
|
|
||||||
Controlling the amplitude and duration of the sound can be done using various techniques. The most important thing to learn is probably how set the amplitude (volume) of your synthesizer:
|
Controlling the amplitude and duration of the sound can be done using various techniques. The most important thing to learn is probably how set the amplitude (volume) of your synthesizer:
|
||||||
- <ic>gain(gain: number)</ic>: sets the gain of the oscillator.
|
- <ic>gain(gain: number)</ic>: sets the gain of the oscillator.
|
||||||
@ -114,15 +81,17 @@ Controlling the amplitude and duration of the sound can be done using various te
|
|||||||
${makeExample(
|
${makeExample(
|
||||||
"Setting the gain",
|
"Setting the gain",
|
||||||
`beat(0.25) :: sound('sawtooth').gain([0.0, 1/8, 1/4, 1/2, 1].beat(0.5)).out()`,
|
`beat(0.25) :: sound('sawtooth').gain([0.0, 1/8, 1/4, 1/2, 1].beat(0.5)).out()`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Setting the velocity",
|
"Setting the velocity",
|
||||||
`beat(0.25) :: sound('sawtooth').velocity([0.0, 1/8, 1/4, 1/2, 1].beat(0.5)).out()`,
|
`beat(0.25) :: sound('sawtooth').velocity([0.0, 1/8, 1/4, 1/2, 1].beat(0.5)).out()`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
## Envelopes
|
||||||
|
|
||||||
<div class="mt-4 mb-4 lg:grid lg:grid-cols-4 lg:gap-4">
|
<div class="mt-4 mb-4 lg:grid lg:grid-cols-4 lg:gap-4">
|
||||||
<img class="col-span-1 lg:ml-12 bg-gray-100 rounded-lg px-2 py-2", src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/ADSR_Envelope_Graph.svg/1280px-ADSR_Envelope_Graph.svg.png" width="400" />
|
<img class="col-span-1 lg:ml-12 bg-gray-100 rounded-lg px-2 py-2", src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/ADSR_Envelope_Graph.svg/1280px-ADSR_Envelope_Graph.svg.png" width="400" />
|
||||||
<z class="pl-8 lg:text-2xl text-base text-white lg:mx-6 mx-2 my-4 leading-normal col-span-3 ">Synthesizers typically come with an amplitude envelope that can help you to shape the sound with a slow attack or long release. This is done in Topos using the amplitude envelope, composed of four parameters: <ic>attack</ic>, <ic>decay</ic>, <ic>sustain</ic> and <ic>release</ic>:</z>
|
<z class="pl-8 lg:text-2xl text-base text-white lg:mx-6 mx-2 my-4 leading-normal col-span-3 ">Synthesizers typically come with an amplitude envelope that can help you to shape the sound with a slow attack or long release. This is done in Topos using the amplitude envelope, composed of four parameters: <ic>attack</ic>, <ic>decay</ic>, <ic>sustain</ic> and <ic>release</ic>:</z>
|
||||||
@ -141,7 +110,7 @@ beat(0.5) :: sound('wt_piano')
|
|||||||
.freq(100).decay(.2)
|
.freq(100).decay(.2)
|
||||||
.sustain([0.1,0.5].beat(4))
|
.sustain([0.1,0.5].beat(4))
|
||||||
.out()`,
|
.out()`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
This ADSR envelope design is important to know because it is used for other aspects of the synthesis engine such as the filters that we are now going to talk about. But wait, I've kept the best for the end. The <ic>adsr()</ic> combines all the parameters together. It is a shortcut for setting the ADSR envelope:
|
This ADSR envelope design is important to know because it is used for other aspects of the synthesis engine such as the filters that we are now going to talk about. But wait, I've kept the best for the end. The <ic>adsr()</ic> combines all the parameters together. It is a shortcut for setting the ADSR envelope:
|
||||||
@ -157,7 +126,7 @@ beat(0.5) :: sound('wt_piano')
|
|||||||
.adsr(0, .2, [0.1,0.5].beat(4), 0)
|
.adsr(0, .2, [0.1,0.5].beat(4), 0)
|
||||||
.out()
|
.out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>ad(attack: number, decay: number)</ic>: sets the attack and decay phases, setting sustain and release to <ic>0</ic>.
|
- <ic>ad(attack: number, decay: number)</ic>: sets the attack and decay phases, setting sustain and release to <ic>0</ic>.
|
||||||
@ -171,81 +140,21 @@ beat(0.5) :: sound('wt_piano')
|
|||||||
.ad(0, .2)
|
.ad(0, .2)
|
||||||
.out()
|
.out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Substractive synthesis using filters
|
## Substractive synthesis using filters
|
||||||
|
|
||||||
The most basic synthesis technique used since the 1970s is called substractive synthesis. This technique is based on the use of rich sound sources (oscillators) as a base to build rich and moving timbres. Because rich sources contain a lot of different harmonics, you might want to filter some of them to obtain the timbre you are looking for. To do so, Topos comes with a set of basic filters that can be used to shape the sound exactly to your liking. There are three filter types by defaut, with more to be added in the future:
|
The most basic synthesis technique used since the 1970s is called substractive synthesis. This technique is based on the use of rich sound sources (oscillators) as a base to build rich and moving timbres. Because rich sources contain a lot of different harmonics, you might want to filter some of them to obtain the timbre you are looking for. To do so, Topos comes with a set of basic filters that can be used to shape the sound exactly to your liking. There are three filter types by defaut, with more to be added in the future:
|
||||||
|
|
||||||
- **lowpass filter**: filters the high frequencies, keeping the low frequencies.
|
See the Filters page for details on lowpass, highpass and bandpass filters. I also encourage you to study these simple examples to get more familiar with the construction of basic substractive synthesizers:
|
||||||
- **highpass filter**: filtering the low frequencies, keeping the high frequencies.
|
|
||||||
- **bandpass filter**: filters the low and high frequencies around a frequency band, keeping what's in the middle.
|
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Filtering the high frequencies of an oscillator",
|
"Filtering the high frequencies of an oscillator",
|
||||||
`beat(.5) :: sound('sawtooth').cutoff(50 + usine(1/8) * 2000).out()`,
|
`beat(.5) :: sound('sawtooth').cutoff(50 + usine(1/8) * 2000).out()`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
These filters all come with their own set of parameters. Note that we are describing the parameters of the three different filter types here. Choose the right parameters depending on the filter type you are using:
|
|
||||||
|
|
||||||
|
|
||||||
### Lowpass filter
|
|
||||||
|
|
||||||
| Method | Alias | Description |
|
|
||||||
|------------|-----------|---------------------------------|
|
|
||||||
| <ic>cutoff</ic> | <ic>lpf</ic> | cutoff frequency of the lowpass filter |
|
|
||||||
| <ic>resonance</ic> | <ic>lpq</ic> | resonance of the lowpass filter (0-1) |
|
|
||||||
|
|
||||||
${makeExample(
|
|
||||||
"Filtering a bass",
|
|
||||||
`beat(.5) :: sound('jvbass').lpf([250,1000,8000].beat()).out()`,
|
|
||||||
true
|
|
||||||
)}
|
|
||||||
|
|
||||||
### Highpass filter
|
|
||||||
|
|
||||||
| Method | Alias | Description |
|
|
||||||
|------------|-----------|---------------------------------|
|
|
||||||
| <ic>hcutoff</ic> | <ic>hpf</ic> | cutoff frequency of the highpass filter |
|
|
||||||
| <ic>hresonance</ic> | <ic>hpq</ic> | resonance of the highpass filter (0-1) |
|
|
||||||
|
|
||||||
${makeExample(
|
|
||||||
"Filtering a noise source",
|
|
||||||
`beat(.5) :: sound('gtr').hpf([250,1000, 2000, 3000, 4000].beat()).end(0.5).out()`,
|
|
||||||
true
|
|
||||||
)}
|
|
||||||
|
|
||||||
### Bandpass filter
|
|
||||||
|
|
||||||
| Method | Alias | Description |
|
|
||||||
|------------|-----------|---------------------------------|
|
|
||||||
| <ic>bandf</ic> | <ic>bpf</ic> | cutoff frequency of the bandpass filter |
|
|
||||||
| <ic>bandq</ic> | <ic>bpq</ic> | resonance of the bandpass filter (0-1) |
|
|
||||||
|
|
||||||
${makeExample(
|
|
||||||
"Sweeping the filter on the same guitar sample",
|
|
||||||
`beat(.5) :: sound('gtr').bandf(100 + usine(1/8) * 4000).end(0.5).out()`,
|
|
||||||
true
|
|
||||||
)}
|
|
||||||
|
|
||||||
Alternatively, <ic>lpf</ic>, <ic>hpf</ic> and <ic>bpf</ic> can take a second argument, the **resonance**.
|
|
||||||
|
|
||||||
## Filter order (type)
|
|
||||||
|
|
||||||
You can also use the <ic>ftype</ic> method to change the filter type (order). There are two types by default, <ic>12db</ic> for a gentle slope or <ic>24db</ic> for a really steep filtering slope. The <ic>24db</ic> type is particularly useful for substractive synthesis if you are trying to emulate some of the Moog or Prophet sounds:
|
|
||||||
|
|
||||||
- <ic>ftype(type: string)</ic>: sets the filter type (order), either <ic>12db</ic> or <ic>24db</ic>.
|
|
||||||
|
|
||||||
${makeExample(
|
|
||||||
"Filtering a bass",
|
|
||||||
`beat(.5) :: sound('jvbass').ftype(['12db', '24db'].beat(4)).lpf([250,1000,8000].beat()).out()`,
|
|
||||||
true
|
|
||||||
)}
|
|
||||||
|
|
||||||
I also encourage you to study these simple examples to get more familiar with the construction of basic substractive synthesizers:
|
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Simple synthesizer voice with filter",
|
"Simple synthesizer voice with filter",
|
||||||
`
|
`
|
||||||
@ -254,7 +163,7 @@ beat(.5) && snd('sawtooth')
|
|||||||
.resonance(0.2).freq([100,150].pick())
|
.resonance(0.2).freq([100,150].pick())
|
||||||
.out()
|
.out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -265,7 +174,7 @@ beat(.5) :: [100,101].forEach((freq) => sound('square').freq(freq*2).sustain(0.0
|
|||||||
beat([.5, .75, 2].beat()) :: [100,101].forEach((freq) => sound('square')
|
beat([.5, .75, 2].beat()) :: [100,101].forEach((freq) => sound('square')
|
||||||
.freq(freq*4 + usquare(2) * 200).sustain(0.125).out())
|
.freq(freq*4 + usquare(2) * 200).sustain(0.125).out())
|
||||||
beat(.25) :: sound('square').freq(100*[1,2,4,8].beat()).sustain(0.1).out()`,
|
beat(.25) :: sound('square').freq(100*[1,2,4,8].beat()).sustain(0.1).out()`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -279,76 +188,26 @@ beat(1/8)::sound('sine')
|
|||||||
.freq(mouseX())
|
.freq(mouseX())
|
||||||
.gain(0.25)
|
.gain(0.25)
|
||||||
.out()`,
|
.out()`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Filter envelopes
|
## Noise
|
||||||
|
|
||||||
The examples we have studied so far are static. They filter the sound around a fixed cutoff frequency. To make the sound more interesting, you can use the ADSR filter envelopes to shape the filter cutoff frequency over time. You will always find amplitude and filter envelopes on most commercial synthesizers. This is done using the following methods:
|
|
||||||
|
|
||||||
### Lowpass envelope
|
|
||||||
|
|
||||||
| Method | Alias | Description |
|
|
||||||
|------------|-----------|---------------------------------|
|
|
||||||
| <ic>lpenv</ic> | <ic>lpe</ic> | lowpass frequency modulation amount (negative or positive) |
|
|
||||||
| <ic>lpattack</ic> | <ic>lpa</ic> | attack of the lowpass filter |
|
|
||||||
| <ic>lpdecay</ic> | <ic>lpd</ic> | decay of the lowpass filter |
|
|
||||||
| <ic>lpsustain</ic> | <ic>lps</ic> | sustain of the lowpass filter |
|
|
||||||
| <ic>lprelease</ic> | <ic>lpr</ic> | release of the lowpass filter |
|
|
||||||
| <ic>lpadsr</ic> | | (**takes five arguments**) set all the parameters |
|
|
||||||
|
|
||||||
|
A certain amount of brown noise can be added by using the <ic>.noise</ic> key:
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Filtering a sawtooth wave dynamically",
|
"Different vibrato settings",
|
||||||
`beat(.5) :: sound('sawtooth').note([48,60].beat())
|
`
|
||||||
.cutoff(5000).lpa([0.05, 0.25, 0.5].beat(2))
|
tempo(140);
|
||||||
.lpenv(-8).lpq(10).out()`,
|
beat(1) :: sound('triangle')
|
||||||
true
|
.freq(400).release(0.2)
|
||||||
|
.noise([0.2,0.4,0.5].bar())
|
||||||
|
.vib([1/2, 1, 2, 4].beat())
|
||||||
|
.vibmod([1,2,4,8].beat(2))
|
||||||
|
.out()`,
|
||||||
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
### Highpass envelope
|
|
||||||
|
|
||||||
| Method | Alias | Description |
|
|
||||||
|------------|-----------|---------------------------------|
|
|
||||||
| <ic>hpenv</ic> | <ic>hpe</ic> | highpass frequency modulation amount (negative or positive) |
|
|
||||||
| <ic>hpattack</ic> | <ic>hpa</ic> | attack of the highpass filter |
|
|
||||||
| <ic>hpdecay</ic> | <ic>hpd</ic> | decay of the highpass filter |
|
|
||||||
| <ic>hpsustain</ic> | <ic>hps</ic> | sustain of the highpass filter |
|
|
||||||
| <ic>hprelease</ic> | <ic>hpr</ic> | release of the highpass filter |
|
|
||||||
| <ic>hpadsr</ic> | | (**takes five arguments**) set all the parameters |
|
|
||||||
|
|
||||||
|
|
||||||
${makeExample(
|
|
||||||
"Let's use another filter using the same example",
|
|
||||||
`beat(.5) :: sound('sawtooth').note([48,60].beat())
|
|
||||||
.hcutoff(1000).hpa([0.05, 0.25, 0.5].beat(2))
|
|
||||||
.hpenv(8).hpq(10).out()`,
|
|
||||||
true
|
|
||||||
)}
|
|
||||||
|
|
||||||
### Bandpass envelope
|
|
||||||
|
|
||||||
| Method | Alias | Description |
|
|
||||||
|------------|-----------|---------------------------------|
|
|
||||||
| <ic>bpenv</ic> | <ic>bpe</ic> | bandpass frequency modulation amount (negative or positive) |
|
|
||||||
| <ic>bpattack</ic> | <ic>bpa</ic> | attack of the bandpass filter |
|
|
||||||
| <ic>bpdecay</ic> | <ic>bpd</ic> | decay of the bandpass filter |
|
|
||||||
| <ic>bpsustain</ic> | <ic>bps</ic> | sustain of the bandpass filter |
|
|
||||||
| <ic>bprelease</ic> | <ic>bpr</ic> | release of the bandpass filter |
|
|
||||||
| <ic>bpadsr</ic> | | (**takes five arguments**) set all the parameters |
|
|
||||||
|
|
||||||
|
|
||||||
${makeExample(
|
|
||||||
"And the bandpass filter, just for fun",
|
|
||||||
`beat(.5) :: sound('sawtooth').note([48,60].beat())
|
|
||||||
.bandf([500,1000,2000].beat(2))
|
|
||||||
.bpa([0.25, 0.125, 0.5].beat(2) * 4)
|
|
||||||
.bpenv(-4).release(2).out()
|
|
||||||
`,
|
|
||||||
true
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
|
||||||
## Wavetable synthesis
|
## Wavetable synthesis
|
||||||
|
|
||||||
Topos can also do wavetable synthesis. Wavetable synthesis allows you to use any sound file as a source to build an oscillator. By default, Topos comes with more than 1000 waveforms thanks to the awesome [AKWF](https://www.adventurekid.se/akrt/waveforms/adventure-kid-waveforms/) pack made by Kristoffer Ekstrand. Any sample name that contains <ic>wt_</ic> as a prefix will be interpreted by the sampler as a wavetable and thus as an oscillator. See for yourself:
|
Topos can also do wavetable synthesis. Wavetable synthesis allows you to use any sound file as a source to build an oscillator. By default, Topos comes with more than 1000 waveforms thanks to the awesome [AKWF](https://www.adventurekid.se/akrt/waveforms/adventure-kid-waveforms/) pack made by Kristoffer Ekstrand. Any sample name that contains <ic>wt_</ic> as a prefix will be interpreted by the sampler as a wavetable and thus as an oscillator. See for yourself:
|
||||||
@ -363,7 +222,7 @@ beat(.25) :: sound('wt_symetric:8').note([50,55,57,60].beat(.25) - [12,0]
|
|||||||
beat(1) :: sound('kick').n(4).out()
|
beat(1) :: sound('kick').n(4).out()
|
||||||
beat(2) :: sound('snare').out()
|
beat(2) :: sound('snare').out()
|
||||||
beat(.5) :: sound('hh').out()`,
|
beat(.5) :: sound('hh').out()`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
@ -381,7 +240,7 @@ beat(2) :: v('selec', irand(1, 100))
|
|||||||
beat(2) :: v('swave', collection.pick())
|
beat(2) :: v('swave', collection.pick())
|
||||||
beat(0.5) :: sound(v('swave')).n(v('selec')).out()
|
beat(0.5) :: sound(v('swave')).n(v('selec')).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
You can work with them just like with any other waveform. Having so many of them makes them also very useful for generating sound effects, percussive, sounds, etc...
|
You can work with them just like with any other waveform. Having so many of them makes them also very useful for generating sound effects, percussive, sounds, etc...
|
||||||
@ -407,7 +266,7 @@ beat(.25) && snd('triangle').adsr(0.02, 0.1, 0.1, 0.1)
|
|||||||
.pan(noise()).note([60,55, 60, 63].beat() + [0, 7].pick()).out()
|
.pan(noise()).note([60,55, 60, 63].beat() + [0, 7].pick()).out()
|
||||||
beat(2) :: sound('cp').room(1).sz(1).out()
|
beat(2) :: sound('cp').room(1).sz(1).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -418,7 +277,7 @@ beat([4].bar()) :: sound('sine').fm('5.2183:4.5').sustain(0.05).out()
|
|||||||
beat(.5) :: sound('sine')
|
beat(.5) :: sound('sine')
|
||||||
.fmh([1, 1.75].beat())
|
.fmh([1, 1.75].beat())
|
||||||
.fmi($(1) % 30).orbit(2).room(0.5).out()`,
|
.fmi($(1) % 30).orbit(2).room(0.5).out()`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -432,7 +291,7 @@ beat(0.25) :: sound('sine')
|
|||||||
.cutoff(1500).delay(0.5).delayt(0.125)
|
.cutoff(1500).delay(0.5).delayt(0.125)
|
||||||
.delayfb(0.8).fmh(Math.floor(usine(.5) * 4))
|
.delayfb(0.8).fmh(Math.floor(usine(.5) * 4))
|
||||||
.out()`,
|
.out()`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
**Note:** you can also set the _modulation index_ and the _harmonic ratio_ with the <ic>fm</ic> argument. You will have to feed both as a string: <ic>fm('2:4')</ic>. If you only feed one number, only the _modulation index_ will be updated.
|
**Note:** you can also set the _modulation index_ and the _harmonic ratio_ with the <ic>fm</ic> argument. You will have to feed both as a string: <ic>fm('2:4')</ic>. If you only feed one number, only the _modulation index_ will be updated.
|
||||||
@ -453,7 +312,7 @@ beat(.5) :: sound('sine')
|
|||||||
.fmwave('triangle')
|
.fmwave('triangle')
|
||||||
.fmsus(0).fmdec(0.2).out()
|
.fmsus(0).fmdec(0.2).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## ZzFX
|
## ZzFX
|
||||||
@ -467,7 +326,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
beat(.5) :: sound(['z_sine', 'z_triangle', 'z_sawtooth', 'z_tan', 'z_noise'].beat()).out()
|
beat(.5) :: sound(['z_sine', 'z_triangle', 'z_sawtooth', 'z_tan', 'z_noise'].beat()).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Minimalist chiptune",
|
"Minimalist chiptune",
|
||||||
@ -481,7 +340,7 @@ beat(.5) :: sound('z_triangle')
|
|||||||
.room(0.5).size(0.9)
|
.room(0.5).size(0.9)
|
||||||
.pitchJumpTime(0.01).out()
|
.pitchJumpTime(0.01).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
It comes with a set of parameters that can be used to tweak the sound. Don't underestimate this synth! It is very powerful for generating anything ranging from chaotic noise sources to lush pads:
|
It comes with a set of parameters that can be used to tweak the sound. Don't underestimate this synth! It is very powerful for generating anything ranging from chaotic noise sources to lush pads:
|
||||||
@ -519,7 +378,7 @@ beat(.25) :: sound('z_tan')
|
|||||||
.sustain(0).decay([0.2, 0.1].pick())
|
.sustain(0).decay([0.2, 0.1].pick())
|
||||||
.out()
|
.out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"What is happening to me?",
|
"What is happening to me?",
|
||||||
@ -529,7 +388,7 @@ beat(1) :: snd('zzfx').zzfx([
|
|||||||
[1.12,,97,.11,.16,.01,4,.77,,,30,.17,,,-1.9,,.01,.67,.2]
|
[1.12,,97,.11,.16,.01,4,.77,,,30,.17,,,-1.9,,.01,.67,.2]
|
||||||
].beat()).out()
|
].beat()).out()
|
||||||
`,
|
`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Les voitures dans le futur",
|
"Les voitures dans le futur",
|
||||||
@ -541,7 +400,7 @@ beat(1) :: sound(['z_triangle', 'z_sine'].pick())
|
|||||||
.room(0.9).size(0.9)
|
.room(0.9).size(0.9)
|
||||||
.delayt(0.75).delayfb(0.5).out()
|
.delayt(0.75).delayfb(0.5).out()
|
||||||
`,
|
`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
Note that you can also design sounds [on this website](https://killedbyapixel.github.io/ZzFX/) and copy the generated code in Topos. To do so, please use the <ic>zzfx</ic> method with the generated array:
|
Note that you can also design sounds [on this website](https://killedbyapixel.github.io/ZzFX/) and copy the generated code in Topos. To do so, please use the <ic>zzfx</ic> method with the generated array:
|
||||||
@ -551,13 +410,15 @@ ${makeExample(
|
|||||||
|
|
||||||
beat(2) :: sound('zzfx').zzfx([3.62,,452,.16,.1,.21,,2.5,,,403,.05,.29,,,,.17,.34,.22,.68]).out()
|
beat(2) :: sound('zzfx').zzfx([3.62,,452,.16,.1,.21,,2.5,,,403,.05,.29,,,,.17,.34,.22,.68]).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
# Speech synthesis
|
# Speech synthesis
|
||||||
|
|
||||||
Topos can also speak using the [Web Speech API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API). There are two ways to use speech synthesis:
|
Topos can also speak using the [Web Speech API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API). There are two ways to use speech synthesis:
|
||||||
|
|
||||||
|
Speech synthesis API can crash your browser if you use it too much. To avoid crashing the calls should be limited using methods like beat() or run it only once using once().
|
||||||
|
|
||||||
- <ic>speak(text: string, lang: string, voice: number, rate: number, pitch: number, volume: number)</ic>
|
- <ic>speak(text: string, lang: string, voice: number, rate: number, pitch: number, volume: number)</ic>
|
||||||
- <ic>text</ic>: the text you would like to synthesize (_e.g_ <ic>"Wow, Topos can speak!"</ic>).
|
- <ic>text</ic>: the text you would like to synthesize (_e.g_ <ic>"Wow, Topos can speak!"</ic>).
|
||||||
- <ic>lang</ic>: language code, for example <ic>en</ic> for English, <ic>fr</ic> for French or with the country code for example British English <ic>en-GB</ic>. See supported values from the [list](https://cloud.google.com/speech-to-text/docs/speech-to-text-supported-languages).
|
- <ic>lang</ic>: language code, for example <ic>en</ic> for English, <ic>fr</ic> for French or with the country code for example British English <ic>en-GB</ic>. See supported values from the [list](https://cloud.google.com/speech-to-text/docs/speech-to-text-supported-languages).
|
||||||
@ -569,17 +430,17 @@ Topos can also speak using the [Web Speech API](https://developer.mozilla.org/en
|
|||||||
${makeExample(
|
${makeExample(
|
||||||
"Hello world!",
|
"Hello world!",
|
||||||
`
|
`
|
||||||
beat(4) :: speak("Hello world!")
|
once() && speak("Hello world!")
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Let's hear people talking about Topos",
|
"Let's hear people talking about Topos",
|
||||||
`
|
`
|
||||||
beat(2) :: speak("Topos!","fr",irand(0,5))
|
beat(2) && speak("Topos!","fr",irand(0,5))
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
@ -588,9 +449,9 @@ You can also use speech by chaining methods to a string:
|
|||||||
${makeExample(
|
${makeExample(
|
||||||
"Foobaba is the real deal",
|
"Foobaba is the real deal",
|
||||||
`
|
`
|
||||||
onbeat(4) :: "Foobaba".voice(irand(0,10)).speak()
|
onbeat(4) && "Foobaba".voice(irand(0,10)).speak()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -601,9 +462,9 @@ ${makeExample(
|
|||||||
const object = ["happy","sad","tired"].pick()
|
const object = ["happy","sad","tired"].pick()
|
||||||
const sentence = subject+" "+verb+" "+" "+object
|
const sentence = subject+" "+verb+" "+" "+object
|
||||||
|
|
||||||
beat(6) :: sentence.pitch(0).rate(0).voice([0,2].pick()).speak()
|
beat(6) && sentence.pitch(0).rate(0).voice([0,2].pick()).speak()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -617,13 +478,13 @@ ${makeExample(
|
|||||||
"Flamboyant", "Cosmique", "Croissant!"
|
"Flamboyant", "Cosmique", "Croissant!"
|
||||||
];
|
];
|
||||||
|
|
||||||
onbeat(4) :: croissant.bar()
|
onbeat(4) && croissant.bar()
|
||||||
.lang("fr")
|
.lang("fr")
|
||||||
.volume(rand(0.2,2.0))
|
.volume(rand(0.2,2.0))
|
||||||
.rate(rand(.4,.6))
|
.rate(rand(.4,.6))
|
||||||
.speak();
|
.speak();
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { type Editor } from "../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory, key_shortcut } from "../Documentation";
|
import { makeExampleFactory, key_shortcut } from "../Documentation";
|
||||||
|
|
||||||
export const midi = (application: Editor): string => {
|
export const midi = (application: Editor): string => {
|
||||||
@ -15,9 +15,9 @@ You can use Topos to play MIDI thanks to the [WebMIDI API](https://developer.moz
|
|||||||
Your web browser is capable of sending and receiving MIDI information through the [Web MIDI API](https://developer.mozilla.org/en-US/docs/Web/API/Web_MIDI_API). The support for MIDI on browsers is a bit shaky. Please, take some time to configure and test. To our best knowledge, **Chrome** is currently leading on this feature, followed closely by **Firefox**. The other major web browsers are also starting to support this API. **There are two important functions for configuration:**
|
Your web browser is capable of sending and receiving MIDI information through the [Web MIDI API](https://developer.mozilla.org/en-US/docs/Web/API/Web_MIDI_API). The support for MIDI on browsers is a bit shaky. Please, take some time to configure and test. To our best knowledge, **Chrome** is currently leading on this feature, followed closely by **Firefox**. The other major web browsers are also starting to support this API. **There are two important functions for configuration:**
|
||||||
|
|
||||||
- <ic>midi_outputs()</ic>: prints the list of available MIDI devices on the screen. You will have to open the web console using ${key_shortcut(
|
- <ic>midi_outputs()</ic>: prints the list of available MIDI devices on the screen. You will have to open the web console using ${key_shortcut(
|
||||||
"Ctrl+Shift+I"
|
"Ctrl+Shift+I",
|
||||||
)} or sometimes ${key_shortcut(
|
)} or sometimes ${key_shortcut(
|
||||||
"F12"
|
"F12",
|
||||||
)}. You can also open it from the menu of your web browser. **Note:** close the docs to see it printed.
|
)}. You can also open it from the menu of your web browser. **Note:** close the docs to see it printed.
|
||||||
|
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
midi_outputs()
|
midi_outputs()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>midi_output(output_name: string)</ic>: enter your desired output to connect to it.
|
- <ic>midi_output(output_name: string)</ic>: enter your desired output to connect to it.
|
||||||
@ -36,7 +36,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
midi_output("MIDI Rocket-Trumpet")
|
midi_output("MIDI Rocket-Trumpet")
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
That's it! You are now ready to play with MIDI.
|
That's it! You are now ready to play with MIDI.
|
||||||
@ -54,7 +54,7 @@ ${makeExample(
|
|||||||
// => midi_output("MIDI Bus 1")
|
// => midi_output("MIDI Bus 1")
|
||||||
rhythm(.5, 5, 8) :: midi(50).out()
|
rhythm(.5, 5, 8) :: midi(50).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -63,7 +63,7 @@ ${makeExample(
|
|||||||
// MIDI Note 50, Velocity 50 + LFO, Channel 0
|
// MIDI Note 50, Velocity 50 + LFO, Channel 0
|
||||||
rhythm(.5, 5, 8) :: midi(50, 50 + usine(.5) * 20, 0).out()
|
rhythm(.5, 5, 8) :: midi(50, 50 + usine(.5) * 20, 0).out()
|
||||||
`,
|
`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -72,7 +72,7 @@ ${makeExample(
|
|||||||
// MIDI Note 50, Velocity 50 + LFO, Channel 0
|
// MIDI Note 50, Velocity 50 + LFO, Channel 0
|
||||||
rhythm(.5, 5, 8) :: midi({note: 50, velocity: 50 + usine(.5) * 20, channel: 0}).out()
|
rhythm(.5, 5, 8) :: midi({note: 50, velocity: 50 + usine(.5) * 20, channel: 0}).out()
|
||||||
`,
|
`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
We can now have some fun and starting playing a small piano piece:
|
We can now have some fun and starting playing a small piano piece:
|
||||||
@ -86,7 +86,7 @@ beat(.25) && midi([64, 76].pick()).sustain(0.05).out()
|
|||||||
beat(.75) && midi([64, 67, 69].beat()).sustain(0.05).out()
|
beat(.75) && midi([64, 67, 69].beat()).sustain(0.05).out()
|
||||||
beat(.25) && midi([64, 67, 69].beat() + 24).sustain(0.05).out()
|
beat(.25) && midi([64, 67, 69].beat() + 24).sustain(0.05).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Control and Program Changes
|
## Control and Program Changes
|
||||||
@ -99,7 +99,7 @@ ${makeExample(
|
|||||||
control_change({control: [24,25].pick(), value: irand(1,120), channel: 1})
|
control_change({control: [24,25].pick(), value: irand(1,120), channel: 1})
|
||||||
control_change({control: [30,35].pick(), value: irand(1,120) / 2, channel: 1})
|
control_change({control: [30,35].pick(), value: irand(1,120) / 2, channel: 1})
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>program_change(program: number, channel: number)</ic>: send a MIDI Program Change. This function takes two arguments to specify the program and the channel (_e.g._ <ic>program_change(1, 1)</ic>).
|
- <ic>program_change(program: number, channel: number)</ic>: send a MIDI Program Change. This function takes two arguments to specify the program and the channel (_e.g._ <ic>program_change(1, 1)</ic>).
|
||||||
@ -109,7 +109,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
program_change([1,2,3,4,5,6,7,8].pick(), 1)
|
program_change([1,2,3,4,5,6,7,8].pick(), 1)
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
sysex(0x90, 0x40, 0x7f)
|
sysex(0x90, 0x40, 0x7f)
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Clock
|
## Clock
|
||||||
@ -135,7 +135,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
beat(.25) && midi_clock() // Sending clock to MIDI device from the global buffer
|
beat(.25) && midi_clock() // Sending clock to MIDI device from the global buffer
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Using midi with ziffers
|
## Using midi with ziffers
|
||||||
@ -147,7 +147,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
z1('0 2 e 5 2 q 4 2').midi().port(2).channel(4).out()
|
z1('0 2 e 5 2 q 4 2').midi().port(2).channel(4).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -155,7 +155,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
z1('(0 2 e 5 2):0 (4 2):1').midi().out()
|
z1('(0 2 e 5 2):0 (4 2):1').midi().out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
75
src/Docs/learning/osc.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { type Editor } from "../../main";
|
||||||
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
|
export const osc = (application: Editor): string => {
|
||||||
|
// @ts-ignore
|
||||||
|
const makeExample = makeExampleFactory(application);
|
||||||
|
return `
|
||||||
|
# Open Sound Control
|
||||||
|
|
||||||
|
Topos is a sandboxed web application. It cannot speak with your computer directly or only through a secure connexion. You can use the [Open Sound Control](https://en.wikipedia.org/wiki/Open_Sound_Control) protocol to send and receive data from your computer. This protocol is used by many softwares and hardware devices. You can use it to control your favorite DAW, your favorite synthesizer, your favorite robot, or anything really! To use **OSC** with Topos, you will need to download the <ic>ToposServer</ic> by [following this link](https://github.com/Bubobubobubobubo/Topos). You can download everything as a zip file or clone the project if you know what you are doing. Here is a quick guide to get you started:
|
||||||
|
|
||||||
|
- 1) Download <ic>Topos</ic> and navigate to the <ic>ToposServer</ic> folder.
|
||||||
|
- 2) Install the dependencies using <ic>npm install</ic>. Start the server using <ic>npm start</ic>.
|
||||||
|
- 3) Open the <ic>Topos</ic> application in your web browser (server first, then application).
|
||||||
|
|
||||||
|
The <ic>ToposServer</ic> server is used both for **OSC** _input_ and _output_.
|
||||||
|
|
||||||
|
## Input
|
||||||
|
|
||||||
|
Send an **OSC** message to the server from another application or device at the address <ic>localhost:30000</ic>. Topos will store the last 1000 messages in a queue. You can access this queue using the <ic>getOsc()</ic> function.
|
||||||
|
|
||||||
|
### Unfiltered messages
|
||||||
|
|
||||||
|
You can access the last 1000 messages using the <ic>getOsc()</ic> function without any argument. This is raw data, you will need to parse it yourself:
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Reading the last OSC messages",
|
||||||
|
`
|
||||||
|
beat(1)::getOsc()
|
||||||
|
// 0 : {data: Array(2), address: '/lala'}
|
||||||
|
// 1 : {data: Array(2), address: '/lala'}
|
||||||
|
// 2 : {data: Array(2), address: '/lala'}`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
|
### Filtered messages
|
||||||
|
|
||||||
|
The <ic>getOsc()</ic> can receive an address filter as an argument. This will return only the messages that match the filter:
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Reading the last OSC messages (filtered)",
|
||||||
|
`
|
||||||
|
beat(1)::getOsc("/lala")
|
||||||
|
// 0 : (2) [89, 'bob']
|
||||||
|
// 1 : (2) [84, 'bob']
|
||||||
|
// 2 : (2) [82, 'bob']
|
||||||
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
Once the server is loaded, you are ready to send an **OSC** message:
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Sending a simple OSC message",
|
||||||
|
`
|
||||||
|
beat(1)::sound('cp').speed(2).vel(0.5).osc()
|
||||||
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
|
This is a simple **OSC** message that will inherit all the properties of the sound. You can also send customized OSC messages using the <ic>osc()</ic> function:
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Sending a customized OSC message",
|
||||||
|
`
|
||||||
|
// osc(address, port, ...message)
|
||||||
|
osc('/my/osc/address', 5000, 1, 2, 3)
|
||||||
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
|
`;
|
||||||
|
};
|
||||||
3
src/Docs/learning/samples/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { loading_samples } from './loading_samples';
|
||||||
|
export { sample_banks } from './sample_banks';
|
||||||
|
export { sample_list } from './sample_list';
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const loading_samples = (application: Editor): string => {
|
export const loading_samples = (application: Editor): string => {
|
||||||
@ -18,7 +18,7 @@ ${makeExample(
|
|||||||
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
|
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
|
||||||
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
||||||
}, 'github:tidalcycles/Dirt-Samples/master/');`,
|
}, 'github:tidalcycles/Dirt-Samples/master/');`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
This example is loading two samples from each folder declared in the original repository (in the <ic>strudel.json</ic> file). You can then play with them using the syntax you are already used to:
|
This example is loading two samples from each folder declared in the original repository (in the <ic>strudel.json</ic> file). You can then play with them using the syntax you are already used to:
|
||||||
@ -27,7 +27,7 @@ ${makeExample(
|
|||||||
"Playing with the loaded samples",
|
"Playing with the loaded samples",
|
||||||
`rhythm(.5, 5, 8)::sound('bd').n(ir(1,2)).end(1).out()
|
`rhythm(.5, 5, 8)::sound('bd').n(ir(1,2)).end(1).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
Internally, Topos is loading samples using a different technique where sample maps are directly taken from the previously mentioned <ic>strudel.json</ic> file that lives in each repository:
|
Internally, Topos is loading samples using a different technique where sample maps are directly taken from the previously mentioned <ic>strudel.json</ic> file that lives in each repository:
|
||||||
@ -40,7 +40,7 @@ samples("github:tidalcycles/Dirt-Samples/master");
|
|||||||
samples("github:Bubobubobubobubo/Dough-Samples/main");
|
samples("github:Bubobubobubobubo/Dough-Samples/main");
|
||||||
samples("github:Bubobubobubobubo/Dough-Amiga/main");
|
samples("github:Bubobubobubobubo/Dough-Amiga/main");
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
To learn more about the audio sample loading mechanism, please refer to [this page](https://strudel.tidalcycles.org/learn/samples) written by Felix Roos who has implemented the sample loading mechanism. The API is absolutely identic in Topos!
|
To learn more about the audio sample loading mechanism, please refer to [this page](https://strudel.tidalcycles.org/learn/samples) written by Felix Roos who has implemented the sample loading mechanism. The API is absolutely identic in Topos!
|
||||||
@ -57,9 +57,10 @@ samples("shabda:ocean")
|
|||||||
|
|
||||||
// Use the sound without 'shabda:'
|
// Use the sound without 'shabda:'
|
||||||
beat(1)::sound('ocean').clip(1).out()
|
beat(1)::sound('ocean').clip(1).out()
|
||||||
`, true
|
`,
|
||||||
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
You can also use the <ic>.n</ic> attribute like usual to load a different sample.
|
You can also use the <ic>.n</ic> attribute like usual to load a different sample.
|
||||||
`
|
`;
|
||||||
}
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const sample_banks = (application: Editor): string => {
|
export const sample_banks = (application: Editor): string => {
|
||||||
@ -9,5 +9,5 @@ export const sample_banks = (application: Editor): string => {
|
|||||||
There is a <ic>bank</ic> attribute that can help you to sort audio samples from large collections.
|
There is a <ic>bank</ic> attribute that can help you to sort audio samples from large collections.
|
||||||
|
|
||||||
**AJKPercusyn**, **AkaiLinn**, **AkaiMPC60**, **AkaiXR10**, **AlesisHR16**, **AlesisSR16**, **BossDR110**, **BossDR220**, **BossDR55**, **BossDR550**, **BossDR660**, **CasioRZ1**, **CasioSK1**, **CasioVL1**, **DoepferMS404**, **EmuDrumulator**, **EmuModular**, **EmuSP12**, **KorgDDM110**, **KorgKPR77**, **KorgKR55**, **KorgKRZ**, **KorgM1**, **KorgMinipops**, **KorgPoly800**, **KorgT3**, **Linn9000**, **LinnDrum**, **LinnLM1**, **LinnLM2**, **MFB512**, **MPC1000**, **MoogConcertMateMG1**, **OberheimDMX**, **RhodesPolaris**, **RhythmAce**, **RolandCompurhythm1000**, **RolandCompurhythm78**, **RolandCompurhythm8000**, **RolandD110**, **RolandD70**, **RolandDDR30**, **RolandJD990**, **RolandMC202**, **RolandMC303**, **RolandMT32**, **RolandR8**, **RolandS50**, **RolandSH09**, **RolandSystem100**, **RolandTR505**, **RolandTR606**, **RolandTR626**, **RolandTR707**, **RolandTR727**, **RolandTR808**, **RolandTR909**, **SakataDPM48**, **SequentialCircuitsDrumtracks**, **SequentialCircuitsTom**, **SergeModular**, **SimmonsSDS400**, **SimmonsSDS5**, **SoundmastersR88**, **UnivoxMicroRhythmer12**, **ViscoSpaceDrum**, **XdrumLM8953**, **YamahaRM50**, **YamahaRX21**, **YamahaRX5**, **YamahaRY30**, **YamahaTG33**.
|
**AJKPercusyn**, **AkaiLinn**, **AkaiMPC60**, **AkaiXR10**, **AlesisHR16**, **AlesisSR16**, **BossDR110**, **BossDR220**, **BossDR55**, **BossDR550**, **BossDR660**, **CasioRZ1**, **CasioSK1**, **CasioVL1**, **DoepferMS404**, **EmuDrumulator**, **EmuModular**, **EmuSP12**, **KorgDDM110**, **KorgKPR77**, **KorgKR55**, **KorgKRZ**, **KorgM1**, **KorgMinipops**, **KorgPoly800**, **KorgT3**, **Linn9000**, **LinnDrum**, **LinnLM1**, **LinnLM2**, **MFB512**, **MPC1000**, **MoogConcertMateMG1**, **OberheimDMX**, **RhodesPolaris**, **RhythmAce**, **RolandCompurhythm1000**, **RolandCompurhythm78**, **RolandCompurhythm8000**, **RolandD110**, **RolandD70**, **RolandDDR30**, **RolandJD990**, **RolandMC202**, **RolandMC303**, **RolandMT32**, **RolandR8**, **RolandS50**, **RolandSH09**, **RolandSystem100**, **RolandTR505**, **RolandTR606**, **RolandTR626**, **RolandTR707**, **RolandTR727**, **RolandTR808**, **RolandTR909**, **SakataDPM48**, **SequentialCircuitsDrumtracks**, **SequentialCircuitsTom**, **SergeModular**, **SimmonsSDS400**, **SimmonsSDS5**, **SoundmastersR88**, **UnivoxMicroRhythmer12**, **ViscoSpaceDrum**, **XdrumLM8953**, **YamahaRM50**, **YamahaRX21**, **YamahaRX5**, **YamahaRY30**, **YamahaTG33**.
|
||||||
`
|
`;
|
||||||
}
|
};
|
||||||
@ -1,7 +1,10 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const samples_to_markdown = (application: Editor, tag_filter?: string) => {
|
export const samples_to_markdown = (
|
||||||
|
application: Editor,
|
||||||
|
tag_filter?: string,
|
||||||
|
) => {
|
||||||
let samples = application.api._all_samples();
|
let samples = application.api._all_samples();
|
||||||
let markdownList = "";
|
let markdownList = "";
|
||||||
let keys = Object.keys(samples);
|
let keys = Object.keys(samples);
|
||||||
@ -29,7 +32,7 @@ export const samples_to_markdown = (application: Editor, tag_filter?: string) =>
|
|||||||
|
|
||||||
markdownList += `
|
markdownList += `
|
||||||
<button
|
<button
|
||||||
class="hover:bg-neutral-500 inline px-4 py-2 bg-neutral-700 text-orange-300 text-xl"
|
class="hover:bg-foreground inline px-4 py-2 bg-black text-brightwhite hover:text-background text-xl"
|
||||||
onclick="app.api._playDocExampleOnce(app.api.codeExamples['${codeId}'])"
|
onclick="app.api._playDocExampleOnce(app.api.codeExamples['${codeId}'])"
|
||||||
>
|
>
|
||||||
${keys[i]}
|
${keys[i]}
|
||||||
@ -44,13 +47,11 @@ export const injectAllSamples = (application: Editor): string => {
|
|||||||
return generatedPage;
|
return generatedPage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const injectDrumMachineSamples = (application: Editor): string => {
|
export const injectDrumMachineSamples = (application: Editor): string => {
|
||||||
let generatedPage = samples_to_markdown(application, "Machines");
|
let generatedPage = samples_to_markdown(application, "Machines");
|
||||||
return generatedPage;
|
return generatedPage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const sample_list = (application: Editor): string => {
|
export const sample_list = (application: Editor): string => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
@ -63,38 +64,81 @@ 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:
|
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 :)", `
|
${makeExample(
|
||||||
|
"Wavetable synthesis made easy :)",
|
||||||
|
`
|
||||||
beat(0.5)::sound('wt_stereo').n([0, 1].pick()).ad(0, .25).out()
|
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.
|
Pick one folder and spend some time exploring it. There is a lot of different waveforms.
|
||||||
|
|
||||||
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-background mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
||||||
${samples_to_markdown(application, "Waveforms")}
|
${samples_to_markdown(application, "Waveforms")}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Drum machines sample pack
|
## Drum machines sample pack
|
||||||
|
|
||||||
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:
|
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(
|
${makeExample(
|
||||||
"Using a classic drum machine", `
|
"Using a classic drum machine",
|
||||||
|
`
|
||||||
beat(0.5)::sound(['bd', 'cp'].pick()).bank("AkaiLinn").out()
|
beat(0.5)::sound(['bd', 'cp'].pick()).bank("AkaiLinn").out()
|
||||||
`, true)}
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
Here is the complete list of available machines:
|
Here is the complete list of available machines:
|
||||||
|
|
||||||
|
|
||||||
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-background mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
||||||
${samples_to_markdown(application, "Machines")}
|
${samples_to_markdown(application, "Machines")}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
In practice, using them will lead you to write short two letters long sample names, each one for a different piece of the kit:
|
||||||
|
|
||||||
|
| Kit Piece | Short name |
|
||||||
|
|-----------|----------------|
|
||||||
|
|
|
||||||
|
| **Bass/kick drum** | <ic>bd</ic> |
|
||||||
|
| **Snare drum** | <ic>sd</ic> |
|
||||||
|
| **Rimshot** | <ic>rim</ic> |
|
||||||
|
| **Clap** | <ic>cp</ic> |
|
||||||
|
| **Closed hi-hat** | <ic>hh</ic> |
|
||||||
|
| **Open hi-hat** | <ic>oh</ic> |
|
||||||
|
| **Crash** | <ic>cr</ic> |
|
||||||
|
| **Ride** | <ic>rd</ic> |
|
||||||
|
| **Shakers (and maracas, cabasas, etc)** | <ic>sh</ic> |
|
||||||
|
| **High tom** | <ic>ht</ic> |
|
||||||
|
| **Medium tom** | <ic>mt</ic> |
|
||||||
|
| **Low tom** | <ic>lt</ic> |
|
||||||
|
| **Cowbell** | <ic>cb</ic> |
|
||||||
|
| **Tambourine** | <ic>tb</ic> |
|
||||||
|
| **Other percussions** | <ic>perc</ic> |
|
||||||
|
| **Miscellaneous samples** | <ic>misc</ic> |
|
||||||
|
| **Effects** | <ic>fx</ic> |
|
||||||
|
|
||||||
|
Note that there is also a <ic>drumMachine</ic> function that allows you to play a random drum machine without even typing the name.
|
||||||
|
It takes a single argument, a number, that will pick a machine for you in the list:
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Using a classic drum machine",
|
||||||
|
`
|
||||||
|
beat(1/2)::sound(['bd', 'cp'].pick()).drumMachine(1).out()
|
||||||
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
## FoxDot sample pack
|
## FoxDot sample pack
|
||||||
|
|
||||||
The default sample pack used by Ryan Kirkbride's [FoxDot](https://github.com/Qirky/FoxDot). It is a nice curated sample pack that covers all the basic sounds you could want.
|
The default sample pack used by Ryan Kirkbride's [FoxDot](https://github.com/Qirky/FoxDot). It is a nice curated sample pack that covers all the basic sounds you could want.
|
||||||
|
|
||||||
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-background mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
||||||
${samples_to_markdown(application, "FoxDot")}
|
${samples_to_markdown(application, "FoxDot")}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -102,7 +146,7 @@ ${samples_to_markdown(application, "FoxDot")}
|
|||||||
|
|
||||||
This set of audio samples is taken from [this wonderful collection](https://archive.org/details/AmigaSoundtrackerSamplePacksst-xx) of **Ultimate Tracker Amiga samples**. They were initially made by Karsten Obarski. These files were processed: pitched down one octave, gain down 6db. The audio has been processed with [SoX](https://github.com/chirlu/sox). The script used to do so is also included in this repository.
|
This set of audio samples is taken from [this wonderful collection](https://archive.org/details/AmigaSoundtrackerSamplePacksst-xx) of **Ultimate Tracker Amiga samples**. They were initially made by Karsten Obarski. These files were processed: pitched down one octave, gain down 6db. The audio has been processed with [SoX](https://github.com/chirlu/sox). The script used to do so is also included in this repository.
|
||||||
|
|
||||||
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-background mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
||||||
${samples_to_markdown(application, "Amiga")}
|
${samples_to_markdown(application, "Amiga")}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -111,14 +155,16 @@ ${samples_to_markdown(application, "Amiga")}
|
|||||||
A collection of many different amen breaks. Use <ic>.stretch()</ic> to play with these:
|
A collection of many different amen breaks. Use <ic>.stretch()</ic> to play with these:
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Stretching an amen break", `
|
"Stretching an amen break",
|
||||||
|
`
|
||||||
beat(4)::sound('amen1').stretch(4).out()
|
beat(4)::sound('amen1').stretch(4).out()
|
||||||
`, true,
|
`,
|
||||||
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
The stretch should be adapted based on the length of each amen break.
|
The stretch should be adapted based on the length of each amen break.
|
||||||
|
|
||||||
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-background mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
||||||
${samples_to_markdown(application, "Amen")}
|
${samples_to_markdown(application, "Amen")}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -127,8 +173,24 @@ ${samples_to_markdown(application, "Amen")}
|
|||||||
Many live coders are expecting to find the Tidal sample library wherever they go, so here it is :)
|
Many live coders are expecting to find the Tidal sample library wherever they go, so here it is :)
|
||||||
|
|
||||||
|
|
||||||
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-background mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
||||||
${samples_to_markdown(application, "Tidal")}
|
${samples_to_markdown(application, "Tidal")}
|
||||||
</div>
|
</div>
|
||||||
`
|
|
||||||
}
|
## Juliette's voice
|
||||||
|
|
||||||
|
This sample pack is only one folder full of french phonems! It sounds super nice.
|
||||||
|
|
||||||
|
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-background mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
||||||
|
${samples_to_markdown(application, "Juliette")}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Your samples
|
||||||
|
|
||||||
|
These samples are the one you have loaded for the duration of the session using the <ic>Import Samples</ic> button in the configuration menu.
|
||||||
|
|
||||||
|
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-background mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
|
||||||
|
${samples_to_markdown(application, "user")}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const cyclical_time = (app: Editor): string => {
|
export const cyclical_time = (app: Editor): string => {
|
||||||
@ -21,7 +21,7 @@ ${makeExample(
|
|||||||
// This code is alternating between different mod values
|
// This code is alternating between different mod values
|
||||||
beat([1,1/2,1/4,1/8].beat(2)) :: sound('hat').n(0).out()
|
beat([1,1/2,1/4,1/8].beat(2)) :: sound('hat').n(0).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -41,7 +41,7 @@ beat(1/3) :: blip(400).pan(r(0,1)).out();
|
|||||||
flip(3) :: beat(1/6) :: blip(800).pan(r(0,1)).out();
|
flip(3) :: beat(1/6) :: blip(800).pan(r(0,1)).out();
|
||||||
beat([1,0.75].beat(2)) :: blip([50, 100].beat(2)).pan(r(0,1)).out();
|
beat([1,0.75].beat(2)) :: blip([50, 100].beat(2)).pan(r(0,1)).out();
|
||||||
`,
|
`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -49,7 +49,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
beat([.5, 1.25])::sound('hat').out()
|
beat([.5, 1.25])::sound('hat').out()
|
||||||
`,
|
`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>pulse(n: number | number[] = 1, offset: number = 1)</ic>: return true every _n_ pulses. A pulse is the tiniest possible rhythmic value.
|
- <ic>pulse(n: number | number[] = 1, offset: number = 1)</ic>: return true every _n_ pulses. A pulse is the tiniest possible rhythmic value.
|
||||||
@ -63,7 +63,7 @@ ${makeExample(
|
|||||||
pulse([24, 16])::sound('hat').ad(0, .02).out()
|
pulse([24, 16])::sound('hat').ad(0, .02).out()
|
||||||
pulse([48, [36,24].dur(4, 1)])::sound('fhardkick').ad(0, .1).out()
|
pulse([48, [36,24].dur(4, 1)])::sound('fhardkick').ad(0, .1).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"pulse is the OG rhythmic function in Topos",
|
"pulse is the OG rhythmic function in Topos",
|
||||||
@ -71,7 +71,7 @@ ${makeExample(
|
|||||||
pulse([48, 24, 16].beat(4)) :: sound('linnhats').out()
|
pulse([48, 24, 16].beat(4)) :: sound('linnhats').out()
|
||||||
beat(1)::snd(['bd', '808oh'].beat(1)).out()
|
beat(1)::snd(['bd', '808oh'].beat(1)).out()
|
||||||
`,
|
`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ ${makeExample(
|
|||||||
bar(1)::sound('kick').out()
|
bar(1)::sound('kick').out()
|
||||||
beat(1)::sound('hat').speed(2).out()
|
beat(1)::sound('hat').speed(2).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ beat(1)::sound('hat').speed(2).out()
|
|||||||
beat(1, 0.5)::sound('hat').speed(4).out()
|
beat(1, 0.5)::sound('hat').speed(4).out()
|
||||||
bar(1, 0.5)::sound('sn').out()
|
bar(1, 0.5)::sound('sn').out()
|
||||||
`,
|
`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>onbeat(...n: number[])</ic>: The <ic>onbeat</ic> function allows you to lock on to a specific beat from the clock to execute code. It can accept multiple arguments. It's usage is very straightforward and not hard to understand. You can pass either integers or floating point numbers. By default, topos is using a <ic>4/4</ic> bar meaning that you can target any of these beats (or in-between) with this function.
|
- <ic>onbeat(...n: number[])</ic>: The <ic>onbeat</ic> function allows you to lock on to a specific beat from the clock to execute code. It can accept multiple arguments. It's usage is very straightforward and not hard to understand. You can pass either integers or floating point numbers. By default, topos is using a <ic>4/4</ic> bar meaning that you can target any of these beats (or in-between) with this function.
|
||||||
@ -109,7 +109,7 @@ onbeat(1,2,3,4)::snd('kick').out() // Bassdrum on each beat
|
|||||||
onbeat(2,4)::snd('snare').n([8,4].beat(4)).out() // Snare on acccentuated beats
|
onbeat(2,4)::snd('snare').n([8,4].beat(4)).out() // Snare on acccentuated beats
|
||||||
onbeat(1.5,2.5,3.5, 3.75)::snd('hat').gain(r(0.9,1.1)).out() // Cool high-hats
|
onbeat(1.5,2.5,3.5, 3.75)::snd('hat').gain(r(0.9,1.1)).out() // Cool high-hats
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## XOX Style sequencers
|
## XOX Style sequencers
|
||||||
@ -125,7 +125,7 @@ seq('xoxo')::sound('fhardkick').out()
|
|||||||
seq('ooxo')::sound('fsoftsnare').out()
|
seq('ooxo')::sound('fsoftsnare').out()
|
||||||
seq('xoxo', 0.25)::sound('fhh').out()
|
seq('xoxo', 0.25)::sound('fhh').out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -135,7 +135,7 @@ seq('xoxooxxoo', [0.5, 0.25].dur(2, 1))::sound('fhardkick').out()
|
|||||||
seq('ooxo', [1, 2].bar())::sound('fsoftsnare').speed(0.5).out()
|
seq('ooxo', [1, 2].bar())::sound('fsoftsnare').speed(0.5).out()
|
||||||
seq(['xoxoxoxx', 'xxoo'].bar())::sound('fhh').out()
|
seq(['xoxoxoxx', 'xxoo'].bar())::sound('fhh').out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>fullseq(expr: string, duration: number = 0.5): boolean</ic> : a variant. Will return <ic>true</ic> or <ic>false</ic> for a whole period, depending on the symbol. Useful for long structure patterns.
|
- <ic>fullseq(expr: string, duration: number = 0.5): boolean</ic> : a variant. Will return <ic>true</ic> or <ic>false</ic> for a whole period, depending on the symbol. Useful for long structure patterns.
|
||||||
@ -159,7 +159,7 @@ function complexPat() {
|
|||||||
}
|
}
|
||||||
fullseq('xooxooxx', 4) ? simplePat() : complexPat()
|
fullseq('xooxooxx', 4) ? simplePat() : complexPat()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
@ -181,7 +181,7 @@ rhythm(.5, 7, 8)::sound('sine')
|
|||||||
.freq(125).ad(0, .2).out()
|
.freq(125).ad(0, .2).out()
|
||||||
rhythm(.5, 3, 8)::sound('sine').freq(500).ad(0, .5).out()
|
rhythm(.5, 3, 8)::sound('sine').freq(500).ad(0, .5).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
@ -194,7 +194,7 @@ ${makeExample(
|
|||||||
oneuclid(5, 9) :: snd('kick').out()
|
oneuclid(5, 9) :: snd('kick').out()
|
||||||
oneuclid(7,16) :: snd('east').end(0.5).n(irand(3,5)).out()
|
oneuclid(7,16) :: snd('east').end(0.5).n(irand(3,5)).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>bin(iterator: number, n: number): boolean</ic>: a binary rhythm generator. It transforms the given number into its binary representation (_e.g_ <ic>34</ic> becomes <ic>100010</ic>). It then returns a boolean value based on the iterator in order to generate a rhythm.
|
- <ic>bin(iterator: number, n: number): boolean</ic>: a binary rhythm generator. It transforms the given number into its binary representation (_e.g_ <ic>34</ic> becomes <ic>100010</ic>). It then returns a boolean value based on the iterator in order to generate a rhythm.
|
||||||
@ -207,7 +207,7 @@ bpm(135);
|
|||||||
beat(.5) && bin($(1), 12) && snd('kick').n([4,9].beat(1.5)).out()
|
beat(.5) && bin($(1), 12) && snd('kick').n([4,9].beat(1.5)).out()
|
||||||
beat(.5) && bin($(2), 34) && snd('snare').n([3,5].beat(1)).out()
|
beat(.5) && bin($(2), 34) && snd('snare').n([3,5].beat(1)).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -221,7 +221,7 @@ binrhythm([.5, .25].beat(1), 30) && snd('wt_granular').n(a)
|
|||||||
.adsr(0, r(.1, .4), 0, 0).freq([50, 60, 72].beat(4))
|
.adsr(0, r(.1, .4), 0, 0).freq([50, 60, 72].beat(4))
|
||||||
.room(1).size(1).out()
|
.room(1).size(1).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -233,7 +233,7 @@ beat(.5) && bin($(1), 911) && snd('ST69').n([2,3,4].beat())
|
|||||||
.room(1).size(1).out()
|
.room(1).size(1).out()
|
||||||
beat(.5) && sound('amencutup').n(irand(2,7)).shape(0.3).out()
|
beat(.5) && sound('amencutup').n(irand(2,7)).shape(0.3).out()
|
||||||
`,
|
`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
If you don't find it spicy enough, you can add some more probabilities to your rhythms by taking advantage of the probability functions. See the functions documentation page to learn more about them.
|
If you don't find it spicy enough, you can add some more probabilities to your rhythms by taking advantage of the probability functions. See the functions documentation page to learn more about them.
|
||||||
@ -247,7 +247,7 @@ prob(60)::beat(.5) && euclid($(2), 3, 8) && snd('mash')
|
|||||||
.pan(usine(1/4)).out()
|
.pan(usine(1/4)).out()
|
||||||
prob(80)::beat(.5) && sound(['hh', 'hat'].pick()).out()
|
prob(80)::beat(.5) && sound(['hh', 'hat'].pick()).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
import pulses from "./pulses.svg";
|
import pulses from "./pulses.svg";
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
log(\`\$\{cbar()}\, \$\{cbeat()\}, \$\{cpulse()\}\`)
|
log(\`\$\{cbar()}\, \$\{cbeat()\}, \$\{cpulse()\}\`)
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
### BPM and PPQN
|
### BPM and PPQN
|
||||||
@ -83,7 +83,7 @@ if((cbar() % 4) > 1) {
|
|||||||
// This is always playing no matter what happens
|
// This is always playing no matter what happens
|
||||||
beat([.5, .5, 1, .25].beat(0.5)) :: sound('shaker').out()
|
beat([.5, .5, 1, .25].beat(0.5)) :: sound('shaker').out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Time Warping
|
## Time Warping
|
||||||
@ -108,7 +108,7 @@ flip(3) :: beat([.25,.5].beat(.5)) :: sound('dr')
|
|||||||
// Jumping back and forth in time
|
// Jumping back and forth in time
|
||||||
beat(.25) :: warp([12, 48, 24, 1, 120, 30].pick())
|
beat(.25) :: warp([12, 48, 24, 1, 120, 30].pick())
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>beat_warp(beat: number)</ic>: this function jumps to the _n_ beat of the clock. The first beat is <ic>1</ic>.
|
- <ic>beat_warp(beat: number)</ic>: this function jumps to the _n_ beat of the clock. The first beat is <ic>1</ic>.
|
||||||
@ -126,11 +126,11 @@ beat([.25,.125].beat(2))::snd('arpy')
|
|||||||
.cutoff(usine(.5) * 5000).resonance(20).gain(0.3)
|
.cutoff(usine(.5) * 5000).resonance(20).gain(0.3)
|
||||||
.end(0.8).room(0.9).size(0.9).n(3).out();
|
.end(0.8).room(0.9).size(0.9).n(3).out();
|
||||||
beat(.5) :: snd('arpy').note(
|
beat(.5) :: snd('arpy').note(
|
||||||
[30, 33, 35].repeatAll(4).beat(1) - [12,0].beat(0.5)).out()
|
[30, 33, 35].repeat(4).beat(1) - [12,0].beat(0.5)).out()
|
||||||
// Comment me to stop warping!
|
// Comment me to stop warping!
|
||||||
beat(1) :: beat_warp([2,4,5,10,11].pick())
|
beat(1) :: beat_warp([2,4,5,10,11].pick())
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
## Transport-based rhythm generators
|
## Transport-based rhythm generators
|
||||||
@ -144,7 +144,7 @@ onbeat(1,2,3,4)::snd('kick').out() // Bassdrum on each beat
|
|||||||
onbeat(2,4)::snd('snare').n([8,4].beat(4)).out() // Snare on acccentuated beats
|
onbeat(2,4)::snd('snare').n([8,4].beat(4)).out() // Snare on acccentuated beats
|
||||||
onbeat(1.5,2.5,3.5, 3.75)::snd('hat').gain(r(0.9,1.1)).out() // Cool high-hats
|
onbeat(1.5,2.5,3.5, 3.75)::snd('hat').gain(r(0.9,1.1)).out() // Cool high-hats
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -156,7 +156,7 @@ beat([.25, 1/8].beat(1.5))::snd('hat').n(2)
|
|||||||
.gain(rand(0.4, 0.7)).end(0.05)
|
.gain(rand(0.4, 0.7)).end(0.05)
|
||||||
.pan(usine()).out()
|
.pan(usine()).out()
|
||||||
`,
|
`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>oncount(beats: number[], meter: number)</ic>: This function is similar to <ic>onbeat</ic> but it allows you to specify a custom number of beats as the last argument.
|
- <ic>oncount(beats: number[], meter: number)</ic>: This function is similar to <ic>onbeat</ic> but it allows you to specify a custom number of beats as the last argument.
|
||||||
@ -171,19 +171,19 @@ z1('1/16 (0 2 3 4)+(0 2 4 6)').scale('pentatonic').sound('sawtooth')
|
|||||||
onbeat(1,1.5,2,3,4) :: sound('bd').gain(2.0).out()
|
onbeat(1,1.5,2,3,4) :: sound('bd').gain(2.0).out()
|
||||||
oncount([1,3,5.5,7,7.5,8],8) :: sound('hh').gain(irand(1.0,4.0)).out()
|
oncount([1,3,5.5,7,7.5,8],8) :: sound('hh').gain(irand(1.0,4.0)).out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Using oncount to create rhythms with a custom meter",
|
"Using oncount to create rhythms with a custom meter",
|
||||||
`
|
`
|
||||||
bpm(200)
|
tempo(200)
|
||||||
oncount([1, 5, 9, 13],16) :: sound('808bd').n(4).shape(0.5).gain(1.0).out()
|
oncount([1, 5, 9, 13],16) :: sound('808bd').n(4).shape(0.5).gain(1.0).out()
|
||||||
oncount([5, 6, 13],16) :: sound('shaker').room(0.25).gain(0.9).out()
|
oncount([5, 6, 13],16) :: sound('shaker').room(0.25).gain(0.9).out()
|
||||||
oncount([2, 3, 3.5, 6, 7, 10, 15],16) :: sound('hh').n(8).gain(0.8).out()
|
oncount([2, 3, 3.5, 6, 7, 10, 15],16) :: sound('hh').n(8).gain(0.8).out()
|
||||||
oncount([1, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16],16) :: sound('hh').out()
|
oncount([1, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16],16) :: sound('hh').out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../main";
|
import { type Editor } from "../../../main";
|
||||||
import { makeExampleFactory } from "../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
|
|
||||||
export const long_forms = (app: Editor): string => {
|
export const long_forms = (app: Editor): string => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -19,7 +19,7 @@ ${makeExample(
|
|||||||
// Playing each script for 8 bars in succession
|
// Playing each script for 8 bars in succession
|
||||||
script([1,2,3,4].bar(8))
|
script([1,2,3,4].bar(8))
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
You can also give a specific duration to each section using <ic>.dur</ic>:
|
You can also give a specific duration to each section using <ic>.dur</ic>:
|
||||||
@ -29,7 +29,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
script([1,2,3,4].dur(8, 2, 16, 4))
|
script([1,2,3,4].dur(8, 2, 16, 4))
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- **Use universes as well**. Transitions between universes are _seamless_, instantaneous. Just switch to different content if you ever hit the limitations of the current _universe_.
|
- **Use universes as well**. Transitions between universes are _seamless_, instantaneous. Just switch to different content if you ever hit the limitations of the current _universe_.
|
||||||
@ -44,7 +44,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
flip(4) :: beat(1) :: snd('kick').out()
|
flip(4) :: beat(1) :: snd('kick').out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -56,7 +56,7 @@ flip(2.5, 75) :: beat(.25) :: snd('click')
|
|||||||
flip(2.5) :: beat(.5) :: snd('bd').out()
|
flip(2.5) :: beat(.5) :: snd('bd').out()
|
||||||
beat(.25) :: sound('hat').end(0.1).cutoff(1200).pan(usine(1/4)).out()
|
beat(.25) :: sound('hat').end(0.1).cutoff(1200).pan(usine(1/4)).out()
|
||||||
`,
|
`,
|
||||||
false
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -68,7 +68,7 @@ if (flip(4, 75)) {
|
|||||||
beat(.5) :: snd('snare').out()
|
beat(.5) :: snd('snare').out()
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ic>flip</ic> is extremely powerful and is used internally for a lot of other Topos functions. You can also use it to think about **longer durations** spanning over multiple bars. Here is a silly composition that is using <ic>flip</ic> to generate a 4 bars long pattern.
|
<ic>flip</ic> is extremely powerful and is used internally for a lot of other Topos functions. You can also use it to think about **longer durations** spanning over multiple bars. Here is a silly composition that is using <ic>flip</ic> to generate a 4 bars long pattern.
|
||||||
@ -93,7 +93,7 @@ if (flip(8)) {
|
|||||||
beat(.5)::snd('diphone').end(0.5).n([1,2,3,4].pick()).out()
|
beat(.5)::snd('diphone').end(0.5).n([1,2,3,4].pick()).out()
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
You can use it everywhere to spice things up, including as a method parameter picker:
|
You can use it everywhere to spice things up, including as a method parameter picker:
|
||||||
@ -103,7 +103,7 @@ ${makeExample(
|
|||||||
`
|
`
|
||||||
beat(.5)::snd(flip(2) ? 'kick' : 'hat').out()
|
beat(.5)::snd(flip(2) ? 'kick' : 'hat').out()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
- <ic>flipbar(n: number = 1)</ic>: this method works just like <ic>flip</ic> but counts in bars instead of beats. It allows you to think about even larger time cycles. You can also pair it with regular <ic>flip</ic> for writing complex and long-spanning algorithmic beats.
|
- <ic>flipbar(n: number = 1)</ic>: this method works just like <ic>flip</ic> but counts in bars instead of beats. It allows you to think about even larger time cycles. You can also pair it with regular <ic>flip</ic> for writing complex and long-spanning algorithmic beats.
|
||||||
@ -122,7 +122,7 @@ function b() {
|
|||||||
flipbar(2) && a()
|
flipbar(2) && a()
|
||||||
flipbar(3) && b()
|
flipbar(3) && b()
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Alternating over four bars",
|
"Alternating over four bars",
|
||||||
@ -131,7 +131,7 @@ flipbar(2)
|
|||||||
? beat(.5) && snd(['kick', 'hh'].beat(1)).out()
|
? beat(.5) && snd(['kick', 'hh'].beat(1)).out()
|
||||||
: beat(.5) && snd(['east', 'east:2'].beat(1)).out()
|
: beat(.5) && snd(['east', 'east:2'].beat(1)).out()
|
||||||
`,
|
`,
|
||||||
false
|
false,
|
||||||
)};
|
)};
|
||||||
|
|
||||||
|
|
||||||
@ -155,7 +155,7 @@ if (onbar([1,2], 4)) {
|
|||||||
rhythm(.5, 1, 7) :: snd('jvbass').n(2).out();
|
rhythm(.5, 1, 7) :: snd('jvbass').n(2).out();
|
||||||
rhythm(.5, 2, 7) :: snd('snare').n(3).out();
|
rhythm(.5, 2, 7) :: snd('snare').n(3).out();
|
||||||
}`,
|
}`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
@ -1,5 +1,5 @@
|
|||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../../Documentation";
|
||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../../main";
|
||||||
import times from "./times.svg";
|
import times from "./times.svg";
|
||||||
|
|
||||||
export const time = (application: Editor): string => {
|
export const time = (application: Editor): string => {
|
||||||
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
@ -14,6 +14,21 @@ Topos is an experimental web based algorithmic sequencer programmed by **BuboBub
|
|||||||
|
|
||||||
Topos is a free and open-source software distributed under [GPL-3.0](https://github.com/Bubobubobubobubo/Topos/blob/main/LICENSE) licence. We welcome all contributions and ideas. You can find the source code on [GitHub](https://github.com/Bubobubobubobubo/topos). You can also join us on [Discord](https://discord.gg/dnUTPbu6bN) to discuss about the project and live coding in general.
|
Topos is a free and open-source software distributed under [GPL-3.0](https://github.com/Bubobubobubobubo/Topos/blob/main/LICENSE) licence. We welcome all contributions and ideas. You can find the source code on [GitHub](https://github.com/Bubobubobubobubo/topos). You can also join us on [Discord](https://discord.gg/dnUTPbu6bN) to discuss about the project and live coding in general.
|
||||||
|
|
||||||
|
## Support the project
|
||||||
|
|
||||||
|
You can support the project by making a small donation on [Kofi](https://ko-fi.com/Manage/).
|
||||||
|
|
||||||
|
<div style="display: flex; justify-content: center;">
|
||||||
|
<iframe
|
||||||
|
id='kofiframe'
|
||||||
|
src='https://ko-fi.com/raphaelbubo/?hidefeed=true&widget=true&embed=true&preview=true'
|
||||||
|
style='border:none;width:40%;padding:4px;background:#f9f9f9;'
|
||||||
|
height='590'
|
||||||
|
title='raphaelbubo'>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
- Felix Roos for the [SuperDough](https://www.npmjs.com/package/superdough) audio engine.
|
- Felix Roos for the [SuperDough](https://www.npmjs.com/package/superdough) audio engine.
|
||||||
106
src/Docs/more/bonus.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import { type Editor } from "../../main";
|
||||||
|
import { key_shortcut, makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
|
export const bonus = (application: Editor): string => {
|
||||||
|
const makeExample = makeExampleFactory(application);
|
||||||
|
|
||||||
|
return `
|
||||||
|
# Bonus features
|
||||||
|
|
||||||
|
Some features have been included as a bonus. These features are often about patterning over things that are not directly related to sound: pictures, video, etc.
|
||||||
|
|
||||||
|
## Editor theme configuration
|
||||||
|
|
||||||
|
The editor theme can be changed using the <ic>theme</ic> and <ic>randomTheme</ic> functions. The following example will use a random color scheme for every beat:
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Random theme on each beat",
|
||||||
|
`
|
||||||
|
beat(1)::randomTheme()
|
||||||
|
`, true)}
|
||||||
|
|
||||||
|
You can also pick a theme using the <ic>theme</ic> function with a string as only argument:
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Picking a theme",
|
||||||
|
`
|
||||||
|
beat(1)::theme("Batman")
|
||||||
|
`, true)}
|
||||||
|
|
||||||
|
## Hydra Visual Live Coding
|
||||||
|
|
||||||
|
<div class="mx-12 bg-neutral-600 rounded-lg flex flex-col items-center justify-center">
|
||||||
|
<warning>⚠️ This feature can generate flashing images that could trigger photosensitivity or epileptic seizures. ⚠️ </warning>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
[Hydra](https://hydra.ojack.xyz/?sketch_id=mahalia_1) is a popular live-codable video synthesizer developed by [Olivia Jack](https://ojack.xyz/) and other contributors. It follows an analog synthesizer patching metaphor to encourage live coding complex shaders. Being very easy to use, extremely powerful and also very rewarding to use, Hydra has become a popular choice for adding visuals into a live code performance.
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Hydra integration",
|
||||||
|
`beat(4) :: hydra.osc(3, 0.5, 2).out()`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
|
Close the documentation to see the effect: ${key_shortcut(
|
||||||
|
"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.
|
||||||
|
|
||||||
|
Stopping **Hydra** is simple:
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Stopping Hydra",
|
||||||
|
`
|
||||||
|
beat(4) :: stop_hydra() // this one
|
||||||
|
beat(4) :: hydra.hush() // or this one
|
||||||
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
### Changing the resolution
|
||||||
|
|
||||||
|
You can change Hydra resolution using this simple method:
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Changing Hydra resolution",
|
||||||
|
`hydra.setResolution(1024, 768)`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
I won't teach Hydra. You can find some great resources directly on the [Hydra website](https://hydra.ojack.xyz/):
|
||||||
|
- [Hydra interactive documentation](https://hydra.ojack.xyz/docs/)
|
||||||
|
- [List of Hydra Functions](https://hydra.ojack.xyz/api/)
|
||||||
|
- [Source code on GitHub](https://github.com/hydra-synth/hydra)
|
||||||
|
|
||||||
|
### The Hydra namespace
|
||||||
|
|
||||||
|
In comparison with the basic Hydra editor, please note that you have to prefix all Hydra functions with <ic>hydra.</ic> to avoid conflicts with Topos functions. For example, <ic>osc()</ic> becomes <ic>hydra.osc()</ic>.
|
||||||
|
|
||||||
|
${makeExample("Hydra namespace", `hydra.voronoi(20).out()`, true)}
|
||||||
|
|
||||||
|
## GIF player
|
||||||
|
|
||||||
|
Topos embeds a small <ic>.gif</ic> picture player with a small API. GIFs are automatically fading out after the given duration. Look at the following example:
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Playing many gifs",
|
||||||
|
`
|
||||||
|
beat(0.25)::gif({
|
||||||
|
url:v('gif')[$(1)%6], // Any URL will do!
|
||||||
|
opacity: r(0.5, 1), // Opacity (0-1)
|
||||||
|
size:"300px", // CSS size property
|
||||||
|
center:false, // Centering on the screen?
|
||||||
|
filter:'none', // CSS Filter
|
||||||
|
dur: 2, // In beats (Topos unit)
|
||||||
|
rotation: ir(1, 360), // Rotation (in degrees)
|
||||||
|
posX: ir(1,1200), // CSS Horizontal Position
|
||||||
|
posY: ir(1, 800), // CSS Vertical Position
|
||||||
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
`;
|
||||||
|
};
|
||||||
5
src/Docs/more/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export { about } from './about';
|
||||||
|
export { bonus } from './bonus';
|
||||||
|
export { oscilloscope } from './oscilloscope';
|
||||||
|
export { synchronisation } from './synchronisation';
|
||||||
|
export { visualization } from './visualization.ts';
|
||||||
@ -1,11 +1,23 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
export const oscilloscope = (application: Editor): string => {
|
export const oscilloscope = (application: Editor): string => {
|
||||||
const makeExample = makeExampleFactory(application);
|
const makeExample = makeExampleFactory(application);
|
||||||
return `# Oscilloscope
|
return `# Oscilloscope
|
||||||
|
|
||||||
You can turn on the oscilloscope to generate interesting visuals or to inspect audio. Use the <ic>scope()</ic> function to turn it on and off. The oscilloscope is off by default.
|
You can turn on the oscilloscope to generate interesting visuals or to inspect audio. Use the <ic>scope()</ic> function to turn on/off the oscilloscope and to configure it. The oscilloscope is off by default.
|
||||||
|
|
||||||
|
You need to manually feed the scope with the sounds you want to inspect:
|
||||||
|
|
||||||
|
${makeExample(
|
||||||
|
"Feeding a sine to the oscilloscope",
|
||||||
|
`
|
||||||
|
beat(1)::sound('sine').freq(200).ad(0, .2).scope().out()
|
||||||
|
`,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
|
||||||
|
Here is a layout of the scope configuration options:
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
"Oscilloscope configuration",
|
"Oscilloscope configuration",
|
||||||
@ -23,7 +35,7 @@ scope({
|
|||||||
refresh: 1 // refresh rate (in pulses)
|
refresh: 1 // refresh rate (in pulses)
|
||||||
})
|
})
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${makeExample(
|
${makeExample(
|
||||||
@ -44,7 +56,7 @@ scope({enabled: true, thickness: 8,
|
|||||||
color: ['purple', 'green', 'random'].beat(),
|
color: ['purple', 'green', 'random'].beat(),
|
||||||
size: 0.5, fftSize: 2048})
|
size: 0.5, fftSize: 2048})
|
||||||
`,
|
`,
|
||||||
true
|
true,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
Note that these values can be patterned as well! You can transform the oscilloscope into its own light show if you want. The picture is not stable anyway so you won't have much use of it for precision work :)
|
Note that these values can be patterned as well! You can transform the oscilloscope into its own light show if you want. The picture is not stable anyway so you won't have much use of it for precision work :)
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type Editor } from "../../main";
|
import { type Editor } from "../../main";
|
||||||
import { makeExampleFactory } from "../../Documentation";
|
import { makeExampleFactory } from "../Documentation";
|
||||||
|
|
||||||
export const synchronisation = (app: Editor): string => {
|
export const synchronisation = (app: Editor): string => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||