Merge branch 'main' of github.com:Bubobubobubobubo/Topos
This commit is contained in:
111
README.md
111
README.md
@ -1,52 +1,30 @@
|
||||
# Topos
|
||||
# Topos: A Web-Based Algorithmic Sequencer
|
||||
|
||||
<p align="center"> |
|
||||
<a href="https://discord.gg/aPgV7mSFZh">Discord</a> |
|
||||
<a href="https://raphaelforment.fr/">BuboBubo</a> |
|
||||
<a href="about:blank">Amiika</a> |
|
||||
<a href="https://toplap.org/">About Live Coding</a> |
|
||||
<br><br>
|
||||
<h2 align="center"><b>Contributors</b></h2>
|
||||
<p align='center'>
|
||||
<a href="https://github.com/bubobubobubobubo/Topos/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=bubobubobubobubo/Topos" />
|
||||
</a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
Topos is a web-based application that lives [here](https://topos.raphaelforment.fr). Documentation and description is directly included in the application itself.
|
||||
|
||||

|
||||
|
||||
## Disclaimer
|
||||
|
||||
**Topos is only a proof of concept:**
|
||||
**Topos** is a very young project. It is not ready for end users, do not expect stable features and/or user support. Contributors and curious people are welcome! The software is not stabilized at all for the moment but it is already possible to have fun playing with it.
|
||||
|
||||
- It is not ready for users.
|
||||
- Do not expect stable features and/or support!
|
||||
- Contributors are welcome!
|
||||
## Installation (for devs and contributors)
|
||||
|
||||
## Presentation
|
||||
|
||||
Topos is an algorithmic sequencer inspired by the [Monome Teletype](https://monome.org/docs/teletype/). It is meant to use from a web browser, without installation. It is not meant to be a clone of the Teletype but rather a new take based on the same concept. The goal is to provide a tool that can be used to generate music but also to learn about live coding and algorithmic music. A desktop based version is also available, using [Tauri](https://tauri.app/). Hopefully, it will support MIDI and OSC in the future for a better integration with musical hardware.
|
||||
|
||||
Topos can generate sound through [WebAudio](https://www.npmjs.com/package/superdough) and/or [MIDI](https://developer.mozilla.org/en-US/docs/Web/API/Web_MIDI_API). Users can enter short JS code snippets evaluated in a _sandboxed_ environment. A simple to use API provides tools to manipulate time, transport, instruments, data, etc...
|
||||
|
||||
## How does it work?
|
||||
|
||||
Topos is based on the manipulation of short code snippets, each of them stored in their own file. A set of files is called a **universe**. You can switch from universe to universe _anytime_, even while the application is running. The application is saving the state of the universe in the browser's local storage.
|
||||
|
||||
Like the **Teletype**, A **Topos universe** contains a set of files:
|
||||
|
||||
- **the global script** (`Ctrl+G`): evaluated on a loop for every pulse! Used to call scripts, introduce major changes, etc... The global script really is the conductor of the piece. It can also be used to test short code snippets when you don't feel like programming anything too complex.
|
||||
- **the local scripts** (`Ctrl+L`) are parts / are describing some kind of logic or process that you would like to play with. The local scripts are activated on demand by any other script (including themselves) using the `script(n)` command.
|
||||
- **the init buffer** (`Ctrl+I`) is used to initialise the state of the _universe_ when you first load the app. Think of it as a script used to set the tempo, to set some default variables or state for your composition.
|
||||
- **the note file** (`Ctrl+N`): used to document your _universe_ (project) and to take notes about your composition.
|
||||
|
||||
A **universe** is a set of files (global, init, locals and note) representing a musical composition, a song, a piece, an improvisation. You can create as many universes as you want and switch between them at any time. The application is saving the state of the universe in the browser's local storage. To switch between universes, open the selector by pressing the `Ctrl+B` . The clear button can be used to reset the currently selected universe to a blank slate.
|
||||
|
||||
## Keybindings
|
||||
|
||||
- `Ctrl+P`: start the audio playback/clock.
|
||||
- `Ctrl+S`: stop the audio playback/clock.
|
||||
- `Ctrl+R`: rewind the audio playblack/clock to the beginning.
|
||||
- `Ctrl+G`: global buffer.
|
||||
- `Ctrl+I`: initialisation buffer.
|
||||
- `Ctrl+L`: local buffers.
|
||||
- `F1...F9`: switch to one of the 9 local buffers.
|
||||
- `Ctrl` + `F1...F9`: manual trigger of a local buffer.
|
||||
- `Ctrl+B`: switch between universes.
|
||||
- `Ctrl+Shift+V`: toggle Vim editor mode.
|
||||
|
||||
To evaluate code, press `Ctrl+Enter`. The screen will flash to indicate that the code was transmitted. This is true for every script, including the note script. To stop a script from playing, just comment your code or stop calling it!
|
||||
|
||||
## Local installation
|
||||
|
||||
To run the application for dev purposes, 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 run dev`
|
||||
@ -56,54 +34,11 @@ To build the application for production, you will need to install [Node.js](http
|
||||
- `yarn run build`
|
||||
- `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!
|
||||
|
||||
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:
|
||||
|
||||
- `yarn tauri build`
|
||||
- `yarn tauri dev`
|
||||
|
||||
# Roadmap to Topos v1.0 (first release)
|
||||
|
||||
Sure you can already play music with Topos but it feels like throwing pebbles on a drumset. Help us make it better!
|
||||
|
||||
## Urgent
|
||||
|
||||
- [ ] Using `globalThis` instead of `with(this)`.
|
||||
- [ ] Evaluating once each script or on change.
|
||||
|
||||
## Application User Interface
|
||||
|
||||
- [ ] Visual feedback for script execution
|
||||
- [ ] add blinking dots in the upper-right corner of the editor corresponding to the script being executed.
|
||||
- [ ] visual warning when an error is detected (blinking red?) and reading logs directly from the interface.
|
||||
- [ ] more variety in visual signals when evaluating code (errors, warnings, etc...).
|
||||
- [ ] Animating code in rhythm! Show when code gets executed, etc...
|
||||
- [ ] Better rhythmic generators
|
||||
- [ ] Ability to write simple linear sequences
|
||||
- [ ] Ability to manipulate musical structures / objects
|
||||
- [ ] Rendering static files (MIDI, MOD, etc...)
|
||||
- [ ] Add a way to save the current universe as a file.
|
||||
- [ ] Add a way to load a universe from a file.
|
||||
- [ ] Add a way to share the universe using a link.
|
||||
|
||||
## Scheduler
|
||||
|
||||
- [ ] Stable / robust clock and script/event scheduler.
|
||||
- [ ] There is still a tiny bit of imprecision left in the scheduling mechanism used.
|
||||
- [ ] Add a way to set the clock's swing.
|
||||
- [ ] MIDI Clock In/Out support.
|
||||
|
||||
## User Interface
|
||||
|
||||
- [x] Settings menu with all options.
|
||||
- [ ] Color themes (dark/light), other colors.
|
||||
- [x] Font size.
|
||||
- [ ] Font Family
|
||||
- [x] Vim mode.
|
||||
- [ ] Optimizations for smaller screens and mobile devices.
|
||||
- [ ] Read console log without opening the browser console.
|
||||
- [ ] Fix the bug that adds a new line everytime the app is opened
|
||||
|
||||
## Web Audio
|
||||
|
||||
- [ ] Support Faut DSP integration.
|
||||
- [x] WebAudio based engine.
|
||||
The `tauri` version is only here to quickstart future developments but nothing has been done yet.
|
||||
|
||||
53
index.html
53
index.html
@ -46,13 +46,10 @@
|
||||
<header class="py-2 block text-white bg-neutral-900">
|
||||
<div class="mx-auto flex flex-wrap pl-2 py-1 flex-row items-center">
|
||||
<a class="flex title-font font-medium items-center text-black mb-0">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-10 h-10 text-black p-2 bg-white rounded-full" viewBox="0 0 24 24">
|
||||
<svg id="topos-logo" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-10 h-10 text-black p-2 bg-white rounded-full" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" />
|
||||
</svg>
|
||||
|
||||
<span id="universe-viewer" class="hidden xl:block ml-4 text-2xl text-white">Topos</span>
|
||||
|
||||
|
||||
</a>
|
||||
<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">
|
||||
@ -64,8 +61,8 @@
|
||||
</svg>
|
||||
<p id="play-label" class="hidden lg:block text-xl pl-2 text-white inline-block">Play</p>
|
||||
</a>
|
||||
<a title="Stop button (Ctrl+R)" id="stop-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-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
||||
<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">
|
||||
<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"/>
|
||||
</svg>
|
||||
@ -79,7 +76,7 @@
|
||||
</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">
|
||||
<svg class="w-7 h-7 text-gray-800 dark: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 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 20">
|
||||
<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>
|
||||
@ -93,11 +90,11 @@
|
||||
<p class="hidden lg:block text-xl pl-2 text-white inline-block">Share</p>
|
||||
</a>
|
||||
|
||||
<a title="Open Documentation (Ctrl+D)" id="doc-button-1" class="flex flex-row mr-2 hover:text-gray-900 hover:bg-gray-800 px-2 py-2 rounded-lg">
|
||||
<p class="hidden lg:block text-xl pr-2 text-white inline-block">Docs</p>
|
||||
<svg class="w-7 h-7 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
||||
<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">
|
||||
<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">
|
||||
<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>
|
||||
<p class="hidden lg:block text-xl pl-2 text-white inline-block">Docs</p>
|
||||
</a>
|
||||
|
||||
</nav>
|
||||
@ -159,6 +156,10 @@
|
||||
<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>
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-2 flex space-x-6 border-t border-gray-200 rounded-b dark:border-gray-600 border-spacing-y-4">
|
||||
<button id="close-universes-button" data-modal-hide="defaultModal" type="button" class="mt-2 hover:bg-gray-700 bg-gray-800 text-white focus:ring-4 font-medium rounded-lg text-sm px-5 py-2.5 text-center">Close</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -223,25 +224,27 @@
|
||||
dark:border-neutral-700 border-none"
|
||||
>
|
||||
<nav class="flex flex-col space-y-6">
|
||||
<a title="Local Scripts (Ctrl + L)" id="local-button" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg text-white hover:bg-gray-800">
|
||||
<svg class="w-8 h-8 text-orange-300" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M10 15a5 5 0 1 0 0-10 5 5 0 0 0 0 10Zm0-11a1 1 0 0 0 1-1V1a1 1 0 0 0-2 0v2a1 1 0 0 0 1 1Zm0 12a1 1 0 0 0-1 1v2a1 1 0 1 0 2 0v-2a1 1 0 0 0-1-1ZM4.343 5.757a1 1 0 0 0 1.414-1.414L4.343 2.929a1 1 0 0 0-1.414 1.414l1.414 1.414Zm11.314 8.486a1 1 0 0 0-1.414 1.414l1.414 1.414a1 1 0 0 0 1.414-1.414l-1.414-1.414ZM4 10a1 1 0 0 0-1-1H1a1 1 0 0 0 0 2h2a1 1 0 0 0 1-1Zm15-1h-2a1 1 0 1 0 0 2h2a1 1 0 0 0 0-2ZM4.343 14.243l-1.414 1.414a1 1 0 1 0 1.414 1.414l1.414-1.414a1 1 0 0 0-1.414-1.414ZM14.95 6.05a1 1 0 0 0 .707-.293l1.414-1.414a1 1 0 1 0-1.414-1.414l-1.414 1.414a1 1 0 0 0 .707 1.707Z"/>
|
||||
</svg>
|
||||
<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">
|
||||
<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">
|
||||
<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>
|
||||
</a>
|
||||
|
||||
<a title="Global Script (Ctrl + G)" id="global-button" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg text-white hover:bg-gray-800">
|
||||
<svg class="w-8 h-8 text-white" fill="currentColor" viewBox="0 0 18 20">
|
||||
<path d="M17.8 13.75a1 1 0 0 0-.859-.5A7.488 7.488 0 0 1 10.52 2a1 1 0 0 0 0-.969A1.035 1.035 0 0 0 9.687.5h-.113a9.5 9.5 0 1 0 8.222 14.247 1 1 0 0 0 .004-.997Z"/>
|
||||
</svg>
|
||||
</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">
|
||||
<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">
|
||||
<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"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a title="Initialisation Script (Ctrl + I)" id="init-button" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg text-white bg-gray-800">
|
||||
<svg class="w-8 h-8 text-white " fill="none" viewBox="0 0 21 20">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6.487 1.746c0 4.192 3.592 1.66 4.592 5.754 0 .828 1 1.5 2 1.5s2-.672 2-1.5a1.5 1.5 0 0 1 1.5-1.5h1.5m-16.02.471c4.02 2.248 1.776 4.216 4.878 5.645C10.18 13.61 9 19 9 19m9.366-6h-2.287a3 3 0 0 0-3 3v2m6-8a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
|
||||
</svg>
|
||||
</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">
|
||||
<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">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 1v12m0 0 4-4m-4 4L1 9"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a title="Project notes (Ctrl + N)" id="note-button" class="pl-2 p-1.5 text-gray-700 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="pl-2 p-1.5 text-white focus:outline-nones transition-colors duration-200 rounded-lg text-white hover:bg-gray-800">
|
||||
<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">
|
||||
<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>
|
||||
|
||||
36
src/API.ts
36
src/API.ts
@ -1,6 +1,6 @@
|
||||
import { seededRandom } from "zifferjs";
|
||||
import { MidiConnection } from "./IO/MidiConnection";
|
||||
import { tryEvaluate } from "./Evaluator";
|
||||
import { tryEvaluate, evaluateOnce } from "./Evaluator";
|
||||
import { DrunkWalk } from "./Utils/Drunk";
|
||||
import { scale } from "./Scales";
|
||||
import { Editor } from "./main";
|
||||
@ -66,7 +66,7 @@ export class UserAPI {
|
||||
this.app.universes,
|
||||
this.app.settings
|
||||
);
|
||||
this.app.openBuffersModal();
|
||||
this.app.updateKnownUniversesView();
|
||||
}
|
||||
|
||||
_playDocExample = (code?: string) => {
|
||||
@ -80,6 +80,13 @@ export class UserAPI {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
_playDocExampleOnce = (code?: string) => {
|
||||
this.play();
|
||||
console.log("Executing documentation example: " + this.app.selectedExample);
|
||||
evaluateOnce(this.app, code as string);
|
||||
};
|
||||
|
||||
_all_samples = (): object => {
|
||||
return soundMap.get();
|
||||
};
|
||||
@ -884,7 +891,7 @@ export class UserAPI {
|
||||
const results: boolean[] = n.map((value) => this.epulse() % value === 0);
|
||||
return results.some((value) => value === true);
|
||||
};
|
||||
pmod = this.modpulse;
|
||||
modp = this.modpulse;
|
||||
|
||||
public modbar = (...n: number[]): boolean => {
|
||||
const results: boolean[] = n.map(
|
||||
@ -908,12 +915,12 @@ export class UserAPI {
|
||||
return current_chunk % 2 === 0;
|
||||
};
|
||||
|
||||
onbar = (n: number, ...bar: number[]): boolean => {
|
||||
// n is acting as a modulo on the bar number
|
||||
const bar_list = [...Array(n).keys()].map((i) => i + 1);
|
||||
console.log(bar.some((b) => bar_list.includes(b % n)));
|
||||
return bar.some((b) => bar_list.includes(b % n));
|
||||
};
|
||||
public onbar = (bars: number[] | number, n: number = this.app.clock.time_signature[0]): boolean => {
|
||||
let current_bar = (this.bar() % n) + 1;
|
||||
return (typeof bars === "number")
|
||||
? bars === current_bar
|
||||
: bars.some((b) => b == current_bar)
|
||||
};
|
||||
|
||||
onbeat = (...beat: number[]): boolean => {
|
||||
/**
|
||||
@ -991,7 +998,16 @@ export class UserAPI {
|
||||
*/
|
||||
return this._euclidean_cycle(pulses, length, rotate)[iterator % length];
|
||||
};
|
||||
ec = this.euclid;
|
||||
ec: Function = this.euclid;
|
||||
|
||||
public rhythm = (
|
||||
div: number,
|
||||
pulses: number,
|
||||
length: number,
|
||||
rotate: number = 0
|
||||
): boolean => {
|
||||
return this.mod(div) && this._euclidean_cycle(pulses, length, rotate).div(div);
|
||||
}
|
||||
|
||||
_euclidean_cycle(
|
||||
pulses: number,
|
||||
|
||||
@ -69,7 +69,7 @@ export const template_universe = {
|
||||
};
|
||||
|
||||
export const template_universes = {
|
||||
Default: {
|
||||
Welcome: {
|
||||
global: { candidate: "", committed: "", evaluations: 0 },
|
||||
locals: {
|
||||
1: { candidate: "", committed: "", evaluations: 0 },
|
||||
@ -83,7 +83,7 @@ export const template_universes = {
|
||||
9: { candidate: "", committed: "", evaluations: 0 },
|
||||
},
|
||||
init: { candidate: "", committed: "", evaluations: 0 },
|
||||
notes: { candidate: "// NOTES" },
|
||||
notes: { candidate: "" },
|
||||
},
|
||||
Help: tutorial_universe,
|
||||
};
|
||||
|
||||
@ -3,6 +3,10 @@ export {};
|
||||
|
||||
declare global {
|
||||
interface Array<T> {
|
||||
add(amount: number): number[];
|
||||
sub(amount: number): number[];
|
||||
mult(amount: number): number[];
|
||||
division(amount: number): number[];
|
||||
palindrome(): T[];
|
||||
random(index: number): T;
|
||||
rand(index: number): T;
|
||||
@ -20,6 +24,8 @@ declare global {
|
||||
rotate(steps: number): this;
|
||||
unique(): this;
|
||||
in(value: T): boolean;
|
||||
square(): number[];
|
||||
sqrt(): number[];
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,6 +33,54 @@ export const makeArrayExtensions = (api: UserAPI) => {
|
||||
Array.prototype.in = function <T>(this: T[], value: T): boolean {
|
||||
return this.includes(value);
|
||||
};
|
||||
|
||||
Array.prototype.square = function (): number[] {
|
||||
/**
|
||||
* @returns New array with squared values.
|
||||
*/
|
||||
return this.map((x: number) => x * x);
|
||||
};
|
||||
|
||||
Array.prototype.sqrt = function (): number[] {
|
||||
/**
|
||||
* @returns New array with square roots of values. Throws if any element is negative.
|
||||
*/
|
||||
if (this.some(x => x < 0)) throw new Error('Cannot take square root of negative number');
|
||||
return this.map((x: number) => Math.sqrt(x));
|
||||
};
|
||||
|
||||
Array.prototype.add = function (amount: number): number[] {
|
||||
/**
|
||||
* @param amount - The value to add to each element in the array.
|
||||
* @returns New array with added values.
|
||||
*/
|
||||
return this.map((x: number) => x + amount);
|
||||
};
|
||||
|
||||
Array.prototype.sub = function (amount: number): number[] {
|
||||
/**
|
||||
* @param amount - The value to subtract from each element in the array.
|
||||
* @returns New array with subtracted values.
|
||||
*/
|
||||
return this.map((x: number) => x - amount);
|
||||
};
|
||||
|
||||
Array.prototype.mult = function (amount: number): number[] {
|
||||
/**
|
||||
* @param amount - The value to multiply with each element in the array.
|
||||
* @returns New array with multiplied values.
|
||||
*/
|
||||
return this.map((x: number) => x * amount);
|
||||
};
|
||||
|
||||
Array.prototype.division = function (amount: number): number[] {
|
||||
/**
|
||||
* @param amount - The value to divide each element in the array by.
|
||||
* @returns New array with divided values. Throws if division by zero.
|
||||
*/
|
||||
if (amount === 0) throw new Error('Division by zero');
|
||||
return this.map((x: number) => x / amount);
|
||||
};
|
||||
|
||||
Array.prototype.pick = function () {
|
||||
/**
|
||||
@ -37,13 +91,13 @@ export const makeArrayExtensions = (api: UserAPI) => {
|
||||
return this[Math.floor(api.randomGen() * this.length)];
|
||||
};
|
||||
|
||||
Array.prototype.beat = function () {
|
||||
Array.prototype.beat = function (beat: number = 1) {
|
||||
/**
|
||||
* Returns the element corresponding to the current beat
|
||||
*
|
||||
* @returns The element corresponding to the current beat
|
||||
*/
|
||||
return this[api.ebeat() % this.length];
|
||||
return this[(api.ebeat() / beat) % this.length];
|
||||
};
|
||||
|
||||
Array.prototype.bar = function () {
|
||||
@ -160,7 +214,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
|
||||
return this;
|
||||
};
|
||||
|
||||
Array.prototype.repeatAll = function <T>(this: T[], amount: number) {
|
||||
Array.prototype.repeatAll = function <T>(this: T[], amount: number = 1) {
|
||||
/**
|
||||
* Repeats all elements in the array n times.
|
||||
*
|
||||
@ -181,7 +235,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
|
||||
return this;
|
||||
};
|
||||
|
||||
Array.prototype.repeatPair = function <T>(this: T[], amount: number) {
|
||||
Array.prototype.repeatPair = function <T>(this: T[], amount: number = 1) {
|
||||
/**
|
||||
* Repeats all elements in the array n times, except for the
|
||||
* elements at odd indexes.
|
||||
@ -211,7 +265,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
|
||||
return this;
|
||||
};
|
||||
|
||||
Array.prototype.repeatOdd = function <T>(this: T[], amount: number) {
|
||||
Array.prototype.repeatOdd = function <T>(this: T[], amount: number = 1) {
|
||||
/**
|
||||
* Repeats all elements in the array n times, except for the
|
||||
* elements at even indexes.
|
||||
|
||||
@ -22,14 +22,14 @@ const samples_to_markdown = (application: Editor) => {
|
||||
|
||||
// Adding new examples for each sample folder!
|
||||
const codeId = `sampleExample${i}`;
|
||||
application.api.codeExamples[codeId] = `mod(.5) :: sound("${keys[i]}").n(irand(1,100)).end(1).out()`;
|
||||
application.api.codeExamples[codeId] = `sound("${keys[i]}").n(irand(1, 5)).end(1).out()`;
|
||||
// @ts-ignore
|
||||
const howMany = samples[keys[i]].data.samples.length;
|
||||
|
||||
markdownList += `
|
||||
<button
|
||||
class="hover:bg-neutral-500 inline px-4 py-2 bg-neutral-700 text-orange-300 text-2xl"
|
||||
onclick="app.api._playDocExample(app.api.codeExamples['${codeId}'])"
|
||||
class="hover:bg-neutral-500 inline px-4 py-2 bg-neutral-700 text-orange-300 text-xl"
|
||||
onclick="app.api._playDocExampleOnce(app.api.codeExamples['${codeId}'])"
|
||||
>
|
||||
${keys[i]}
|
||||
<b class="text-white">(${howMany})</b>
|
||||
@ -82,11 +82,21 @@ Welcome to the Topos documentation. These pages are offering you an introduction
|
||||
|
||||
${makeExample(
|
||||
"Welcome! Eval to get started", `
|
||||
mod([1/4,1/8,1/16].div(8)):: sound('sine')
|
||||
.freq([100,50].div(16) + 50 * ($(1)%10))
|
||||
.gain(0.5).room(0.9).size(0.9)
|
||||
.sustain(0.1).out()
|
||||
mod(1) :: sound('kick').out()`,
|
||||
|
||||
|
||||
bpm(110)
|
||||
mod(0.125) && sound('sawtooth')
|
||||
.note([60, 62, 63, 67, 70].div(.125) +
|
||||
[-12,0,12].beat() + [0, 0, 5, 7].bar())
|
||||
.sustain(0.1).fmi(0.25).fmh(2).room(0.9)
|
||||
.gain(0.75).cutoff(500 + usine(8) * [500, 1000, 2000].bar())
|
||||
.delay(0.5).delayt(0.25).delayfb(0.25)
|
||||
.out();
|
||||
mod(1) && snd('kick').out();
|
||||
mod(2) && snd('snare').out();
|
||||
mod(.5) && snd('hat').out();
|
||||
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
@ -97,7 +107,11 @@ Topos is an _algorithmic_ sequencer. Topos uses small algorithms to represent mu
|
||||
|
||||
${makeExample(
|
||||
"Small algorithms for direct musical expression",
|
||||
`mod(1) :: sound(['kick', 'hat', 'snare', 'hat'].div(1)).out()`,
|
||||
`
|
||||
mod(1) :: sound(['kick', 'hat', 'snare', 'hat'].div(1)).out()
|
||||
mod(.5) :: sound('jvbass').note(35 + [0,12].beat()).out()
|
||||
mod([0.5, 0.25, 1, 2].div(1)) :: sound('east')
|
||||
.room(.5).size(0.5).n(irand(1,5)).out()`,
|
||||
false
|
||||
)}
|
||||
|
||||
@ -112,7 +126,11 @@ ${makeExample(
|
||||
|
||||
${makeExample(
|
||||
"Making the web less dreadful, one beep at at time",
|
||||
`mod(.5) :: sound(['sid', 'crow', 'zap'].pick()).n($(1) % 10).out()`,
|
||||
`
|
||||
mod(.5) :: sound('sid').n($(2)).out()
|
||||
mod(.25) :: sound('sid').note(
|
||||
[34, 36, 41].div(.25) + [[0,-24].pick(),12].beat())
|
||||
.room(0.9).size(0.9).n(4).out()`,
|
||||
false
|
||||
)}
|
||||
|
||||
@ -127,39 +145,32 @@ Press ${key_shortcut(
|
||||
)}. You are now making music:
|
||||
|
||||
${makeExample(
|
||||
"Drums and arpeggios",
|
||||
"Obscure shenanigans",
|
||||
`
|
||||
bpm(80)
|
||||
mod(0.25) && sound('sawtooth')
|
||||
.note(seqbar(
|
||||
[60, 67, 63].pick() - 12, [60, 67, 63].pick() - 12,
|
||||
[60, 67, 63].pick() - 12 + 5, [60, 67, 63].pick() - 12 + 5,
|
||||
[60, 67, 63].pick() - 12 + 7, [60, 67, 63].pick() - 12 + 7) + (sometimes() ? 24 : 12)
|
||||
)
|
||||
.sustain(0.1).fmi(8).fmh(4).room(0.9)
|
||||
.gain(0.75).cutoff(500 + usine(8) * 10000)
|
||||
.delay(0.5).delaytime(bpm() / 60 / 4 / 3)
|
||||
.delayfeedback(0.25)
|
||||
.out();
|
||||
mod(1) && snd('kick').out();
|
||||
mod(2) && snd('snare').out();
|
||||
mod(.5) && snd('hat').out();
|
||||
`,
|
||||
mod([1/4,1/8,1/16].div(8)):: sound('sine')
|
||||
.freq([100,50].div(16) + 50 * ($(1)%10))
|
||||
.gain(0.5).room(0.9).size(0.9)
|
||||
.sustain(0.1).out()
|
||||
mod(1) :: sound('kick').out()
|
||||
mod(2) :: sound('dr').n(5).out()
|
||||
div(3) :: mod([.25,.5].div(.5)) :: sound('dr')
|
||||
.n([8,9].pick()).gain([.8,.5,.25,.1,.0].div(.25)).out()`,
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Resonant madness",
|
||||
`mod(.5)::snd('synth2')
|
||||
.freq([50,50*1.25,50*1.5,50*1.75].div(8) / 2)
|
||||
.cutoff(usine(.5) * 5000).resonance(15).end(0.8).room(0.9).size(0.9).n(7).out();
|
||||
mod(.25)::snd('synth2')
|
||||
.freq([50,50*1.25,50*1.5,50*1.75].div(.5))
|
||||
.cutoff(usine(.5) * 5000).resonance(15)
|
||||
.end(0.2).room(0.9).size(0.9).n(14).out()
|
||||
mod(1)::snd('kick').out()
|
||||
mod(2)::snd('snare').shape(0.5).out()
|
||||
mod(.75)::snd('hat').shape(0.4).out()`,
|
||||
`
|
||||
mod(.25)::snd('arpy')
|
||||
.note(30 + [0,3,7,10].beat())
|
||||
.cutoff(usine(.5) * 5000).resonance(10).gain(0.3)
|
||||
.end(0.8).room(0.9).size(0.9).n(0).out();
|
||||
mod([.25,.125].div(2))::snd('arpy')
|
||||
.note(30 + [0,3,7,10].beat())
|
||||
.cutoff(usine(.5) * 5000).resonance(20).gain(0.3)
|
||||
.end(0.8).room(0.9).size(0.9).n(3).out();
|
||||
mod(.5) :: snd('arpy').note(
|
||||
[30, 33, 35].repeatAll(4).div(1) - [12,0].div(0.5)).out()`,
|
||||
false
|
||||
)}
|
||||
`;
|
||||
@ -253,15 +264,17 @@ Let's study two very simple rhythmic functions, <icode>mod(n: ...number[])</icod
|
||||
|
||||
${makeExample(
|
||||
"Using different mod values",
|
||||
`// This code is alternating between different mod values
|
||||
mod([1,1/2,1/4,1/8,1/16].div(4)) :: sound('kick').out()
|
||||
`
|
||||
// This code is alternating between different mod values
|
||||
mod([1,1/2,1/4,1/8].div(2)) :: sound('bd').n(0).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Some sort of ringtone",
|
||||
`let blip = (freq) => {return sound('sine').sustain(0.1).freq(freq)};
|
||||
`
|
||||
let blip = (freq) => {return sound('sine').sustain(0.1).freq(freq)};
|
||||
mod(1) :: blip(200).out();
|
||||
mod(1/3) :: blip(400).out();
|
||||
div(3) :: mod(1/6) :: blip(800).out();
|
||||
@ -269,12 +282,33 @@ mod([1,0.75].div(2)) :: blip([50, 100].div(2)).out();
|
||||
`,
|
||||
false
|
||||
)}
|
||||
|
||||
|
||||
|
||||
- <icode>modp(...n: number[])</icode>: extreme version of the <icode>mod</icode> function. Instead of being normalised, this function is returning a modulo of real pulses! It can be used to break out of ratios and play with real clock pulses for unexpected results.
|
||||
|
||||
${makeExample(
|
||||
"Intriguing rhythms",
|
||||
`
|
||||
modp(36) :: snd('east')
|
||||
.n([2,4].div(1)).out()
|
||||
modp([12, 36].div(4)) :: snd('east')
|
||||
.n([2,4].add(5).div(1)).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
${makeExample(
|
||||
"modp is the OG rhythmic function in Topos",
|
||||
`
|
||||
modp([48, 24, 16].div(4)) :: sound('linnhats').out()
|
||||
mod(1)::snd('bd').out()
|
||||
`, false)};
|
||||
|
||||
- <icode>onbeat(...n: number[])</icode>: By default, the bar is set in <icode>4/4</icode> with four beats per bar. The <icode>onbeat</icode> function allows you to lock on to a specific beat to execute some code. It can accept multiple arguments. It's usage is very straightforward and not hard to understand. You can pass integers or floating point numbers.
|
||||
|
||||
${makeExample(
|
||||
"Some simple yet detailed rhythms",
|
||||
`onbeat(1,2,3,4)::snd('kick').out() // Bassdrum on each beat
|
||||
`
|
||||
onbeat(1,2,3,4)::snd('kick').out() // Bassdrum on each beat
|
||||
onbeat(2,4)::snd('snare').out() // Snare on acccentuated beats
|
||||
onbeat(1.5,2.5,3.5, 3.75)::snd('hat').out() // Cool high-hats
|
||||
`,
|
||||
@ -283,7 +317,8 @@ onbeat(1.5,2.5,3.5, 3.75)::snd('hat').out() // Cool high-hats
|
||||
|
||||
${makeExample(
|
||||
"Let's do something more complex",
|
||||
`onbeat(0.5, 1.5, 2, 3, 3.75)::snd('kick').n(2).out()
|
||||
`
|
||||
onbeat(0.5, 1.5, 2, 3, 3.75)::snd('kick').n(2).out()
|
||||
onbeat(2, [1.5, 3].pick(), 4)::snd('snare').n(7).out()
|
||||
mod([.25, 1/8].div(1.5))::snd('hat').n(2)
|
||||
.gain(rand(0.4, 0.7))
|
||||
@ -311,8 +346,10 @@ mod(.5) && euclid($(2), 2, 8) && snd('sd').out()
|
||||
${makeExample(
|
||||
"And now for more interesting rhythmic constructions",
|
||||
`
|
||||
mod(.5) && euclid($(1), 5, 9) && snd('kick').out()
|
||||
mod(.5) && euclid($(2), 2, 3, 1) && snd('pluck').end(0.5).n(5).out()
|
||||
bpm(145); // Setting a faster BPM
|
||||
mod(.5) && euclid($(1), 5, 8) :: sound('bd').out()
|
||||
mod(.5) && euclid($(2), [1,0].div(8), 8) :: sound('sd').out()
|
||||
mod(.5) && euclid($(6), [6,7].div(8), 8) :: sound('hh').out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
@ -321,15 +358,29 @@ ${makeExample(
|
||||
"Adding more rhythmic density",
|
||||
`
|
||||
mod(.5) && euclid($(1), 5, 9) && snd('kick').out()
|
||||
mod(.5) && euclid($(2), 2, 3, 1) && snd('pluck').end(0.5).n(5).out()
|
||||
mod(.5) && euclid($(3), 6, 9, 1) && snd('pluck').end(0.5).n(5).freq(200).out()
|
||||
mod(.5) && euclid($(2), 2, 3, 1) && snd('east').end(0.5).n(5).out()
|
||||
mod(.5) && euclid($(3), 6, 9, 1) && snd('east').end(0.5).n(5).freq(200).out()
|
||||
mod(.25) && euclid($(4), 7, 9, 1) && snd('hh').out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
|
||||
|
||||
|
||||
- <icode>rhythm(divisor: number, pulses: number, length: number, rotate: number): boolean</icode>: generates <icode>true</icode> or <icode>false</icode> values from an euclidian rhythm sequence. This is another version of <icode>euclid</icode> that does not take an iterator.
|
||||
${makeExample(
|
||||
"rhythm is a beginner friendly rhythmic function!",
|
||||
`
|
||||
let speed = [0.5, 0.25].div(8);
|
||||
rhythm(speed, 5, 12) :: snd('east').n(2).out()
|
||||
rhythm(speed, 2, 12) :: snd('east').out()
|
||||
rhythm(speed, 3, 12) :: snd('east').n(4).out()
|
||||
rhythm(speed, 7, 12) :: snd('east').n(9).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
|
||||
|
||||
- <icode>bin(iterator: number, n: number): boolean</icode>: a binary rhythm generator. It transforms the given number into its binary representation (_e.g_ <icode>34</icode> becomes <icode>100010</icode>). It then returns a boolean value based on the iterator in order to generate a rhythm.
|
||||
|
||||
${makeExample(
|
||||
@ -343,15 +394,17 @@ mod(.5) && bin($(2), 48) && snd('sd').out()
|
||||
|
||||
${makeExample(
|
||||
"Calling 911",
|
||||
`mod(.5) && bin($(1), 911) && snd('pluck').n(4).delay(0.5).delayt(0.25).out()
|
||||
mod(1) && sound('kick').shape(0.5).out()
|
||||
`
|
||||
mod(.5) && bin($(1), 911) && snd('subroc3d').n($(2)).delay(0.5).delayt(0.25).end(0.5).out()
|
||||
mod(.5) && sound('less').n(irand(1, 10)).out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Playing around with simple numbers",
|
||||
`mod(.5) && bin($(1), [123, 456, 789].div(4))
|
||||
`
|
||||
mod(.5) && bin($(1), [123, 456, 789].div(4))
|
||||
&& snd('tabla').n($(2)).delay(0.5).delayt(0.25).out()
|
||||
mod(1) && sound('kick').shape(0.5).out()
|
||||
`,
|
||||
@ -424,7 +477,8 @@ mod(.5)::snd(div(2) ? 'kick' : 'hat').out()
|
||||
|
||||
${makeExample(
|
||||
"div is great for pretty much everything",
|
||||
`div([1, .5].beat()) :: mod(.25) :: sound('shaker').out();
|
||||
`
|
||||
div([1, .5].beat()) :: mod(.25) :: sound('shaker').out();
|
||||
div([4, .5].beat()) :: mod(.25) :: sound('shaker').speed(2).out();
|
||||
div([1, 2].beat()) :: mod(1.75) :: sound('snare').out();
|
||||
div(4) :: mod(.5) :: sound('tom').out()
|
||||
@ -446,9 +500,16 @@ divbar(3)::mod(.5)::snd('hat').out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
${makeExample(
|
||||
"Alternating over four bars",
|
||||
`
|
||||
divbar(2)
|
||||
? mod(.5) && snd(['kick', 'hh'].div(1)).out()
|
||||
: mod(.5) && snd(['east', 'snare'].div(1)).out()
|
||||
`, false)};
|
||||
|
||||
|
||||
- <icode>onbar(n: number, ...bar: number[])</icode>: The first argument, <icode>n</icode>, is used to divide the time in a period of <icode>n</icode> consecutive bars. The following arguments are bar numbers to play on. For example, <icode>onbar(5, 1, 4)</icode> will return <icode>true</icode> on bar <icode>1</icode> and <icode>4</icode> but return <icode>false</icode> the rest of the time. You can easily divide time that way.
|
||||
|
||||
- <icode>onbar(bars: number | number[], n: number)</icode>: The second argument, <icode>n</icode>, is used to divide the time in a period of <icode>n</icode> consecutive bars. The first argument should be a bar number or a list of bar numbers to play on. For example, <icode>onbar([1, 4], 5)</icode> will return <icode>true</icode> on bar <icode>1</icode> and <icode>4</icode> but return <icode>false</icode> the rest of the time. You can easily divide time that way.
|
||||
|
||||
${makeExample(
|
||||
"Using onbar for filler drums",
|
||||
@ -457,7 +518,7 @@ ${makeExample(
|
||||
onbar(4, 4)::mod(.5)::snd('hh').out();
|
||||
|
||||
// Here comes a longer version using JavaScript normal control flow
|
||||
if (onbar(4, 1, 3)) {
|
||||
if (onbar([4, 1], 3)) {
|
||||
mod(1)::snd('kick').out();
|
||||
} else {
|
||||
mod(.5)::snd('sd').out();
|
||||
@ -465,7 +526,7 @@ if (onbar(4, 1, 3)) {
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
|
||||
## What are pulses?
|
||||
|
||||
To make a beat, you need a certain number of time grains or **pulses**. The **pulse** is also known as the [PPQN](https://en.wikipedia.org/wiki/Pulses_per_quarter_note). By default, Topos is using a _pulses per quarter note_ of 48. You can change it by using the <icode>ppqn(number)</icode> function. It means that the lowest possible rhythmic value is 1/48 of a quarter note. That's plenty of time already.
|
||||
@ -606,7 +667,8 @@ The basic function to play a sound is... <icode>sound(name: string)</icode> (you
|
||||
|
||||
${makeExample(
|
||||
"Playing sounds is easy",
|
||||
`mod(1) && sound('bd').out()
|
||||
`
|
||||
mod(1) && sound('bd').out()
|
||||
mod(0.5) && sound('hh').out()
|
||||
`,
|
||||
true
|
||||
@ -621,7 +683,8 @@ Let's make it slightly more complex:
|
||||
|
||||
${makeExample(
|
||||
"Adding some effects",
|
||||
`mod(1) && sound('bd').coarse(0.25).out();
|
||||
`
|
||||
mod(1) && sound('bd').coarse(0.25).room(0.5).orbit(2).out();
|
||||
mod(0.5) && sound('hh').delay(0.25).delaytime(0.125).out();
|
||||
`,
|
||||
true
|
||||
@ -640,12 +703,14 @@ Let's pause for a moment to explain what we just wrote. There are many things to
|
||||
|
||||
${makeExample(
|
||||
'"Composing" a sound or making a sound chain',
|
||||
`mod(1) :: sound('pad')
|
||||
`
|
||||
mod(1) :: sound('pad')
|
||||
.begin(rand(0, 0.4))
|
||||
.freq([50,52].beat())
|
||||
.size(0.9)
|
||||
.room(0.9)
|
||||
.pan(sine()).release(2).out()`,
|
||||
.velocity(0.25)
|
||||
.pan(usine()).release(2).out()`,
|
||||
true
|
||||
)}
|
||||
|
||||
@ -672,7 +737,9 @@ When you type <icode>kick</icode> in the <icode>sound('kick').out()</icode> expr
|
||||
The <icode>.n(number)</icode> method can be used to pick a sample from the currently selected sample folder. For instance, the following script will play a random sample from the _kick_ folder:
|
||||
${makeExample(
|
||||
"Picking a sample",
|
||||
`mod(1) && sound('kick').n([1,2,3,4,5,6,7,8].pick()).out()`,
|
||||
`
|
||||
mod(1) && sound('kick').n([1,2,3,4,5,6,7,8].pick()).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
@ -680,7 +747,8 @@ Don't worry about the number. If it gets too big, it will be automatically wrapp
|
||||
|
||||
${makeExample(
|
||||
"Picking a sample... with your mouse!",
|
||||
`// Move your mouse to change the sample being used!
|
||||
`
|
||||
// Move your mouse to change the sample being used!
|
||||
mod(.25) && sound('numbers').n(Math.floor(mouseX())).out()`,
|
||||
true
|
||||
)}
|
||||
@ -692,11 +760,12 @@ mod(.25) && sound('numbers').n(Math.floor(mouseX())).out()`,
|
||||
As we said earlier, the <icode>sound('sample_name')</icode> function can be chained to _specify_ a sound more. For instance, you can add a filter and some effects to your high-hat:
|
||||
${makeExample(
|
||||
"Learning through repetition",
|
||||
`mod(0.5) && sound('hh')
|
||||
.sometimes(s=>s.speed([1,5,10].pick()))
|
||||
.room(0.5)
|
||||
.cutoff(usine(2) * 5000)
|
||||
.out()`,
|
||||
`
|
||||
mod(0.5) && sound('hh')
|
||||
.sometimes(s=>s.speed([1,5,10].pick()))
|
||||
.room(0.5)
|
||||
.cutoff(usine(2) * 5000)
|
||||
.out()`,
|
||||
true
|
||||
)}
|
||||
|
||||
@ -748,11 +817,11 @@ Note that the **sustain** value is not a duration but an amplitude value (how lo
|
||||
${makeExample(
|
||||
"Simple synthesizer",
|
||||
`
|
||||
mod(4)::sound('sawtooth').note(50).decay(0.5).sustain(0.5).release(2).out();
|
||||
mod(2)::sound('sawtooth').note(50+7).decay(0.5).sustain(0.6).release(2).out();
|
||||
mod(1)::sound('sawtooth').note(50+12).decay(0.5).sustain(0.7).release(2).out();
|
||||
mod(4)::sound('sawtooth').note(50).decay(0.5).sustain(0.5).release(2).gain(0.25).out();
|
||||
mod(2)::sound('sawtooth').note(50+7).decay(0.5).sustain(0.6).release(2).gain(0.25).out();
|
||||
mod(1)::sound('sawtooth').note(50+12).decay(0.5).sustain(0.7).release(2).gain(0.25).out();
|
||||
mod(.25)::sound('sawtooth').note([50,57,62].pick() + [12, 24, 0].div(2))
|
||||
.cutoff(5000).sustain(0.5).release(0.1).out()
|
||||
.cutoff(5000).sustain(0.5).release(0.1).gain(0.25).out()
|
||||
`,
|
||||
true
|
||||
)};
|
||||
@ -804,9 +873,9 @@ ${makeExample(
|
||||
"Filter sweep using a low frequency oscillator",
|
||||
`
|
||||
mod(.5) && snd('sawtooth')
|
||||
.cutoff([2000,500].pick() + usine(.5) * 4000)
|
||||
.resonance(0.9).freq([100,150].pick())
|
||||
.out()
|
||||
.cutoff([2000,500].pick() + usine(.5) * 4000)
|
||||
.resonance(0.9).freq([100,150].pick())
|
||||
.out()
|
||||
`,
|
||||
true
|
||||
)};
|
||||
@ -895,7 +964,8 @@ JavaScript is using [Arrays](https://developer.mozilla.org/en-US/docs/Web/JavaSc
|
||||
|
||||
${makeExample(
|
||||
"Light drumming",
|
||||
`// Every bar, use a different rhythm
|
||||
`
|
||||
// Every bar, use a different rhythm
|
||||
mod([1, 0.75].div(4)) :: sound('cp').out()
|
||||
mod([0.5, 1].div(4)) :: sound('kick').out()
|
||||
mod(2)::snd('snare').shape(.5).out()
|
||||
@ -904,7 +974,8 @@ mod(2)::snd('snare').shape(.5).out()
|
||||
)}
|
||||
${makeExample(
|
||||
"Using div to create arpeggios",
|
||||
`// Arpeggio using pulse divisions
|
||||
`
|
||||
// Arpeggio using pulse divisions
|
||||
mod([.5, .25].div(2)) :: sound('sine')
|
||||
.hcutoff(400)
|
||||
.fmi([1,2].div(8))
|
||||
@ -920,7 +991,8 @@ mod([.5, .25].div(2)) :: sound('sine')
|
||||
)}
|
||||
${makeExample(
|
||||
"Cool ambiance",
|
||||
`mod(.5) :: snd(['kick', 'hat'].div(4)).out()
|
||||
`
|
||||
mod(.5) :: snd(['kick', 'hat'].div(4)).out()
|
||||
mod([2,4].div(2)) :: snd('shaker').delay(.5).delayfb(.75).delayt(0.125).out()
|
||||
div(2)::mod(1)::snd('clap').out()
|
||||
div(4)::mod(2)::snd('pad').n(2).shape(.5).orbit(2).room(0.9).size(0.9).release(0.5).out()
|
||||
@ -936,7 +1008,8 @@ div(4)::mod(2)::snd('pad').n(2).shape(.5).orbit(2).room(0.9).size(0.9).release(0
|
||||
|
||||
${makeExample(
|
||||
"A simple drumbeat in no time!",
|
||||
`mod(1)::sound(['kick', 'hat', 'snare', 'hat'].beat()).out()
|
||||
`
|
||||
mod(1)::sound(['kick', 'hat', 'snare', 'hat'].beat()).out()
|
||||
mod(1.5)::sound(['jvbass', 'clap'].beat()).out()
|
||||
`,
|
||||
true
|
||||
@ -957,7 +1030,8 @@ mod([1, 0.5].beat()) :: sound(['bass3'].bar())
|
||||
|
||||
${makeExample(
|
||||
"Palindrome filter sweep",
|
||||
`mod([1,.5,.25].beat()) :: snd('sine')
|
||||
`
|
||||
mod([1,.5,.25].beat()) :: snd('sine')
|
||||
.freq([100,200,300].div(0.25))
|
||||
.fmi([1,2,3].palindrome().div(0.5))
|
||||
.fmh([4, 8].palindrome().beat())
|
||||
@ -976,7 +1050,8 @@ ${makeExample(
|
||||
|
||||
${makeExample(
|
||||
"Sipping some gasoline at the robot bar",
|
||||
`mod(1)::snd('kick').shape(0.5).out()
|
||||
`
|
||||
mod(1)::snd('kick').shape(0.5).out()
|
||||
mod([.5, 1].random() / 2) :: snd(
|
||||
['amencutup', 'synth2'].random())
|
||||
.n(irand(4,10))
|
||||
@ -991,7 +1066,8 @@ mod([.5, 1].random() / 2) :: snd(
|
||||
|
||||
${makeExample(
|
||||
"Amen break suffering from data loss",
|
||||
`// Tweak the value to degrade this amen break even more!
|
||||
`
|
||||
// Tweak the value to degrade this amen break even more!
|
||||
mod(.25)::snd('amencutup').n([1,2,3,4,5,6,7,8,9].degrade(20).loop($(1))).out()
|
||||
`,
|
||||
true
|
||||
@ -1003,7 +1079,8 @@ mod(.25)::snd('amencutup').n([1,2,3,4,5,6,7,8,9].degrade(20).loop($(1))).out()
|
||||
|
||||
${makeExample(
|
||||
"Repeating samples a given number of times",
|
||||
`// Please take this repeat number down a bit!
|
||||
`
|
||||
// Please take this repeat number down a bit!
|
||||
mod(.25)::sound('amencutup').n([1,2,3,4,5,6,7,8].repeatAll(4).beat()).out()
|
||||
`,
|
||||
true
|
||||
@ -1013,7 +1090,9 @@ mod(.25)::sound('amencutup').n([1,2,3,4,5,6,7,8].repeatAll(4).beat()).out()
|
||||
|
||||
${makeExample(
|
||||
"Don't you know how to count up to 5?",
|
||||
`mod(1) :: sound('numbers').n([1,2,3,4,5].loop($(3, 10, 2))).out()`,
|
||||
`
|
||||
mod(1) :: sound('numbers').n([1,2,3,4,5].loop($(3, 10, 2))).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
@ -1021,7 +1100,8 @@ ${makeExample(
|
||||
|
||||
${makeExample(
|
||||
"Shuffling a list for extra randomness",
|
||||
`mod(1) :: sound('numbers').n([1,2,3,4,5].shuffle().loop($(1)).out()
|
||||
`
|
||||
mod(1) :: sound('numbers').n([1,2,3,4,5].shuffle().loop($(1)).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
@ -1030,10 +1110,11 @@ ${makeExample(
|
||||
|
||||
${makeExample(
|
||||
"To make things more complex... here you go",
|
||||
`mod(.5) :: snd('sine')
|
||||
`
|
||||
mod(.5) :: snd('sine')
|
||||
.freq([100, 150, 200, 250, ,300, 400]
|
||||
.rotate([1,2,3].bar()) // The list of frequencies is rotating
|
||||
.beat()) // while being indexed over!
|
||||
.rotate([1,2,3].bar()) // The list of frequencies is rotating
|
||||
.beat()) // while being indexed over!
|
||||
.sustain(0.1)
|
||||
.out()
|
||||
`,
|
||||
@ -1044,12 +1125,23 @@ ${makeExample(
|
||||
|
||||
${makeExample(
|
||||
"Demonstrative filtering. Final list is [100, 200]",
|
||||
`// Remove unique and 100 will repeat four times!
|
||||
`
|
||||
// Remove unique and 100 will repeat four times!
|
||||
mod(1)::snd('sine').sustain(0.1).freq([100,100,100,100,200].unique().beat()).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
- <icode>add()</icode>: add a given amount to every list element.
|
||||
- <icode>sub()</icode>: add a given amount to every list element.
|
||||
- <icode>mult()</icode>: add a given amount to every list element.
|
||||
- <icode>division()</icode>: add a given amount to every list element. The method is named <icode>division</icode> because obviously <icode>div</icode> is already taken.
|
||||
|
||||
${makeExample(
|
||||
"Simple addition",
|
||||
`[1, 2 ,3].add(2).beat()`,
|
||||
true
|
||||
)}
|
||||
|
||||
## Simple patterns
|
||||
|
||||
@ -1248,7 +1340,8 @@ The <icode>sound</icode> function can take the name of a synthesizer as first ar
|
||||
|
||||
${makeExample(
|
||||
"Simple synthesizer voice with filter",
|
||||
`mod(.5) && snd('sawtooth')
|
||||
`
|
||||
mod(.5) && snd('sawtooth')
|
||||
.cutoff([2000,500].pick() + usine(.5) * 4000)
|
||||
.resonance(0.9).freq([100,150].pick())
|
||||
.out()
|
||||
@ -1258,8 +1351,9 @@ ${makeExample(
|
||||
|
||||
${makeExample(
|
||||
"Listening to the different waveforms from the sweetest to the harshest",
|
||||
`mod(.5) && snd(['sine', 'triangle', 'sawtooth', 'square'].beat()).freq(100).out()
|
||||
.freq(50)
|
||||
`
|
||||
mod(.5) && snd(['sine', 'triangle', 'sawtooth', 'square'].beat()).freq(100).out()
|
||||
.freq(50)
|
||||
.out()
|
||||
`,
|
||||
false
|
||||
@ -1268,7 +1362,8 @@ ${makeExample(
|
||||
|
||||
${makeExample(
|
||||
"Blessed by the square wave",
|
||||
`mod(4) :: [100,101].forEach((freq) => sound('square').freq(freq).sustain(0.1).out())
|
||||
`
|
||||
mod(4) :: [100,101].forEach((freq) => sound('square').freq(freq).sustain(0.1).out())
|
||||
mod(.5) :: [100,101].forEach((freq) => sound('square').freq(freq*2).sustain(0.01).out())
|
||||
mod([.5, .75, 2].beat()) :: [100,101].forEach((freq) => sound('square')
|
||||
.freq(freq*4 + usquare(2) * 200).sustain(0.125).out())
|
||||
@ -1279,11 +1374,14 @@ mod(.25) :: sound('square').freq(100*[1,2,4,8].beat()).sustain(0.1).out()`,
|
||||
|
||||
${makeExample(
|
||||
"Ghost carillon",
|
||||
`mod(1/8)::sound('sine')
|
||||
`
|
||||
mod(1/8)::sound('sine')
|
||||
.velocity(rand(0.0, 1.0))
|
||||
.delay(0.75).delayt(.5)
|
||||
.sustain(0.4)
|
||||
.cutoff(2000)
|
||||
.freq(mouseX())
|
||||
.gain(0.25)
|
||||
.out()`,
|
||||
false
|
||||
)}
|
||||
@ -1298,7 +1396,8 @@ The same basic waveforms can take additional methods to switch to a basic two op
|
||||
|
||||
${makeExample(
|
||||
"80s nostalgia",
|
||||
`mod(.25) && snd('sine')
|
||||
`
|
||||
mod(.25) && snd('sine')
|
||||
.fmi([1,2,4,8].pick())
|
||||
.fmh([1,2,4,8].div(8))
|
||||
.freq([100,150].pick())
|
||||
@ -1310,7 +1409,8 @@ ${makeExample(
|
||||
|
||||
${makeExample(
|
||||
"Giving some love to weird ratios",
|
||||
`mod([.5, .25].bar()) :: sound('sine').fm('2.2183:3.18293').sustain(0.05).out()
|
||||
`
|
||||
mod([.5, .25].bar()) :: sound('sine').fm('2.2183:3.18293').sustain(0.05).out()
|
||||
mod([4].bar()) :: sound('sine').fm('5.2183:4.5').sustain(0.05).out()
|
||||
mod(.5) :: sound('sine')
|
||||
.fmh([1, 1.75].beat())
|
||||
@ -1321,9 +1421,10 @@ mod(.5) :: sound('sine')
|
||||
|
||||
${makeExample(
|
||||
"Some peace and serenity",
|
||||
`mod(0.25) :: sound('sine')
|
||||
.note([60, 67, 70, 72, 77].beat())
|
||||
.attack(0.2).release(0.5).gain(0.5)
|
||||
`
|
||||
mod(0.25) :: sound('sine')
|
||||
.note([60, 67, 70, 72, 77].beat() - [0,12].bar())
|
||||
.attack(0.2).release(0.5).gain(0.25)
|
||||
.room(0.9).size(0.8).sustain(0.5)
|
||||
.fmi(Math.floor(usine(.25) * 10))
|
||||
.cutoff(1500).delay(0.5).delayt(0.125)
|
||||
@ -1383,16 +1484,23 @@ There are some techniques that Topos players are using to keep their JavaScript
|
||||
|
||||
${makeExample(
|
||||
"Shortening your if conditions",
|
||||
`// The && symbol (overriden by :: in Topos) is very often used for conditions!
|
||||
mod(.75) :: snd('zap').out()
|
||||
`
|
||||
// The && symbol (overriden by :: in Topos) is very often used for conditions!
|
||||
mod(.75) :: snd('linnhats').n([1,4,5].beat()).out()
|
||||
mod(1) :: snd('bd').out()
|
||||
//if (true) && log('very true')
|
||||
// These two lines are the same:
|
||||
// mod(1) && snd('bd').out()
|
||||
//// mod(1) :: snd('bd').out()
|
||||
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"More complex conditions using ?",
|
||||
`// The ? symbol can be used to write a if/true/false condition
|
||||
`
|
||||
// The ? symbol can be used to write a if/true/false condition
|
||||
mod(4) ? snd('kick').out() : mod(2)::snd('snare').out()
|
||||
// (true) ? log('very true') : log('very false')
|
||||
`,
|
||||
@ -1402,7 +1510,8 @@ mod(4) ? snd('kick').out() : mod(2)::snd('snare').out()
|
||||
|
||||
${makeExample(
|
||||
"Using not and other short symbols",
|
||||
`// The ! symbol can be used to reverse a condition
|
||||
`
|
||||
// The ! symbol can be used to reverse a condition
|
||||
mod(4) ? snd('kick').out() : mod(2)::snd('snare').out()
|
||||
!mod(2) :: mod(0.5)::snd('clap').out()
|
||||
`,
|
||||
@ -1477,36 +1586,38 @@ Low Frequency Oscillators (_LFOs_) are an important piece in any digital audio w
|
||||
- <icode>sine(freq: number = 1, offset: number= 0): number</icode>: returns a sinusoïdal oscillation between <icode>-1</icode> and <icode>1</icode>.
|
||||
- <icode>usine(freq: number = 1, offset: number= 0): number</icode>: returns a sinusoïdal oscillation between <icode>0</icode> and <icode>1</icode>. The <icode>u</icode> stands for _unipolar_.
|
||||
|
||||
\`\`\`javascript
|
||||
mod(.25) && snd('cp').speed(1 + usine(0.25) * 2).out()
|
||||
\`\`\`
|
||||
|
||||
${makeExample(
|
||||
"Modulating the speed of a sample player using a sine LFO",
|
||||
`mod(.25) && snd('cp').speed(1 + usine(0.25) * 2).out()`, true)};
|
||||
|
||||
- <icode>triangle(freq: number = 1, offset: number= 0): number</icode>: returns a triangle oscillation between <icode>-1</icode> and <icode>1</icode>.
|
||||
- <icode>utriangle(freq: number = 1, offset: number= 0): number</icode>: returns a triangle oscillation between <icode>0</icode> and <icode>1</icode>. The <icode>u</icode> stands for _unipolar_.
|
||||
|
||||
\`\`\`javascript
|
||||
mod(.25) && snd('cp').speed(1 + utriangle(0.25) * 2).out()
|
||||
\`\`\`
|
||||
|
||||
${makeExample(
|
||||
"Modulating the speed of a sample player using a triangle LFO",
|
||||
`mod(.25) && snd('cp').speed(1 + utriangle(0.25) * 2).out()`, true)}
|
||||
|
||||
- <icode>saw(freq: number = 1, offset: number= 0): number</icode>: returns a sawtooth-like oscillation between <icode>-1</icode> and <icode>1</icode>.
|
||||
- <icode>usaw(freq: number = 1, offset: number= 0): number</icode>: returns a sawtooth-like oscillation between <icode>0</icode> and <icode>1</icode>. The <icode>u</icode> stands for _unipolar_.
|
||||
|
||||
\`\`\`javascript
|
||||
mod(.25) && snd('cp').speed(1 + usaw(0.25) * 2).out()
|
||||
\`\`\`
|
||||
|
||||
${makeExample(
|
||||
"Modulating the speed of a sample player using a saw LFO",
|
||||
`mod(.25) && snd('cp').speed(1 + usaw(0.25) * 2).out()`, true)}
|
||||
|
||||
- <icode>square(freq: number = 1, offset: number= 0, duty: number = .5): number</icode>: returns a square wave oscillation between <icode>-1</icode> and <icode>1</icode>. You can also control the duty cycle using the <icode>duty</icode> parameter.
|
||||
- <icode>usquare(freq: number = 1, offset: number= 0, duty: number = .5): number</icode>: returns a square wave oscillation between <icode>0</icode> and <icode>1</icode>. The <icode>u</icode> stands for _unipolar_. You can also control the duty cycle using the <icode>duty</icode> parameter.
|
||||
|
||||
\`\`\`javascript
|
||||
mod(.25) && snd('cp').speed(1 + usquare(0.25, 0, 0.25) * 2).out()
|
||||
\`\`\`
|
||||
${makeExample(
|
||||
"Modulating the speed of a sample player using a square LFO",
|
||||
`mod(.25) && snd('cp').speed(1 + usquare(0.25, 0, 0.25) * 2).out()`,true)};
|
||||
|
||||
- <icode>noise()</icode>: returns a random value between -1 and 1.
|
||||
|
||||
\`\`\`javascript
|
||||
mod(.25) && snd('cp').speed(1 + noise() * 2).out()
|
||||
\`\`\`
|
||||
${makeExample(
|
||||
"Modulating the speed of a sample player using noise",
|
||||
`mod(.25) && snd('cp').speed(1 + noise() * 2).out()`, true)};
|
||||
|
||||
## Probabilities
|
||||
|
||||
|
||||
@ -95,3 +95,17 @@ export const evaluate = async (
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const evaluateOnce = async (
|
||||
application: Editor,
|
||||
code: string
|
||||
): Promise<void> => {
|
||||
/**
|
||||
* Evaluates the code once without any caching or error-handling mechanisms besides the tryCatchWrapper.
|
||||
*
|
||||
* @param application - The application object that contains the Editor API.
|
||||
* @param code - The code to be evaluated.
|
||||
* @returns A promise that resolves when the code has been evaluated.
|
||||
*/
|
||||
await tryCatchWrapper(application, code);
|
||||
};
|
||||
|
||||
55
src/examples/excerpts.ts
Normal file
55
src/examples/excerpts.ts
Normal file
@ -0,0 +1,55 @@
|
||||
export const examples = [
|
||||
`
|
||||
// Ancient rhythms - Bubobubobubo
|
||||
mod(1)::snd('kick').out();
|
||||
mod(2)::snd('sd').room(0.9).size(0.9).out();
|
||||
mod(0.25)::snd('hh').out();
|
||||
mod(2)::snd('square')
|
||||
.cutoff(500).note(50-12).resonance(20).sustain(0.2).out()
|
||||
mod(1/4)::snd(divseq(1, 'sawtooth', 'triangle', 'pulse'))
|
||||
.note(divseq(4, 50, 53, 55, 50, 50, 52, 58, 50+12, 50+15) + divseq(0.5, 0, 12, 24))
|
||||
.cutoff(usine(.5)*10000).resonance(divseq(2, 10,20))
|
||||
.fmi($(1) % 10).fmh($(2) % 5)
|
||||
.room(0.8).size(0.9)
|
||||
.delay(0.5).delaytime(0.25)
|
||||
.delayfb(0.6)
|
||||
.sustain(0.01 + usine(.25) / 10).out()
|
||||
mod(4)::snd('amencutup').n($(19)).cut(1).orbit(2).pan(rand(0.0,1.0)).out()
|
||||
log(bar(), beat(), pulse())`,
|
||||
`
|
||||
// Crazy arpeggios - Bubobubobubo
|
||||
bpm(110)
|
||||
mod(0.125) && sound('sawtooth')
|
||||
.note([60, 62, 63, 67, 70].div(.125) +
|
||||
[-12,0,12].beat() + [0, 0, 5, 7].bar())
|
||||
.sustain(0.1).fmi(0.25).fmh(2).room(0.9)
|
||||
.gain(0.75).cutoff(500 + usine(8) * [500, 1000, 2000].bar())
|
||||
.delay(0.5).delayt(0.25).delayfb(0.25)
|
||||
.out();
|
||||
mod(1) && snd('kick').out();
|
||||
mod(2) && snd('snare').out();
|
||||
mod(.5) && snd('hat').out();
|
||||
`, `
|
||||
// Obscure Shenanigans - Bubobubobubo
|
||||
mod([1/4,1/8,1/16].div(8)):: sound('sine')
|
||||
.freq([100,50].div(16) + 50 * ($(1)%10))
|
||||
.gain(0.5).room(0.9).size(0.9)
|
||||
.sustain(0.1).out()
|
||||
mod(1) :: sound('kick').out()
|
||||
mod(2) :: sound('dr').n(5).out()
|
||||
div(3) :: mod([.25,.5].div(.5)) :: sound('dr')
|
||||
.n([8,9].pick()).gain([.8,.5,.25,.1,.0].div(.25)).out()
|
||||
`, `
|
||||
// Resonance bliss - Bubobubobubo
|
||||
mod(.25)::snd('arpy')
|
||||
.note(30 + [0,3,7,10].beat())
|
||||
.cutoff(usine(.5) * 5000).resonance(10).gain(0.3)
|
||||
.end(0.8).room(0.9).size(0.9).n(0).out();
|
||||
mod([.25,.125].div(2))::snd('arpy')
|
||||
.note(30 + [0,3,7,10].beat())
|
||||
.cutoff(usine(.5) * 5000).resonance(20).gain(0.3)
|
||||
.end(0.8).room(0.9).size(0.9).n(3).out();
|
||||
mod(.5) :: snd('arpy').note(
|
||||
[30, 33, 35].repeatAll(4).div(1) - [12,0].div(0.5)).out()
|
||||
`
|
||||
]
|
||||
69
src/main.ts
69
src/main.ts
@ -3,6 +3,7 @@ import {
|
||||
colors,
|
||||
animals,
|
||||
} from "unique-names-generator";
|
||||
import { examples } from "./examples/excerpts";
|
||||
import { EditorState, Compartment } from "@codemirror/state";
|
||||
import { ViewUpdate, lineNumbers, keymap } from "@codemirror/view";
|
||||
import { javascript } from "@codemirror/lang-javascript";
|
||||
@ -90,6 +91,9 @@ export class Editor {
|
||||
public _mouseX: number = 0;
|
||||
public _mouseY: number = 0;
|
||||
|
||||
// Topos Logo
|
||||
topos_logo: HTMLElement = document.getElementById('topos-logo') as HTMLElement;
|
||||
|
||||
// Transport elements
|
||||
play_buttons: HTMLButtonElement[] = [
|
||||
document.getElementById("play-button-1") as HTMLButtonElement,
|
||||
@ -130,6 +134,10 @@ export class Editor {
|
||||
close_settings_button: HTMLButtonElement = document.getElementById(
|
||||
"close-settings-button"
|
||||
) as HTMLButtonElement;
|
||||
close_universes_button: HTMLButtonElement = document.getElementById(
|
||||
"close-universes-button"
|
||||
) as HTMLButtonElement;
|
||||
|
||||
universe_viewer: HTMLDivElement = document.getElementById(
|
||||
"universe-viewer"
|
||||
) as HTMLDivElement;
|
||||
@ -184,9 +192,12 @@ export class Editor {
|
||||
// Loading the universe from local storage
|
||||
// ================================================================================
|
||||
|
||||
this.selected_universe = this.settings.selected_universe;
|
||||
this.universe_viewer.innerHTML = `Topos: ${this.selected_universe}`;
|
||||
this.universes = { ...template_universes, ...this.settings.universes };
|
||||
this.selected_universe = "Welcome";
|
||||
this.universe_viewer.innerHTML = `Topos: ${this.selected_universe}`;
|
||||
let random_example = examples[Math.floor(Math.random() * examples.length)];
|
||||
this.universes[this.selected_universe].global.committed = random_example;
|
||||
this.universes[this.selected_universe].global.candidate = random_example;
|
||||
|
||||
// ================================================================================
|
||||
// Audio context and clock
|
||||
@ -237,9 +248,6 @@ export class Editor {
|
||||
|
||||
// ================================================================================
|
||||
// Building the documentation
|
||||
// loadSamples().then(() => {
|
||||
// this.docs = documentation_factory(this);
|
||||
// });
|
||||
let pre_loading = async () => { await loadSamples(); };
|
||||
pre_loading();
|
||||
this.docs = documentation_factory(this);
|
||||
@ -307,18 +315,7 @@ export class Editor {
|
||||
// This is the modal to switch between universes
|
||||
if (event.ctrlKey && event.key === "b") {
|
||||
this.hideDocumentation();
|
||||
let existing_universes = document.getElementById("existing-universes");
|
||||
let known_universes = Object.keys(this.universes);
|
||||
let final_html = "<ul class='lg:h-80 lg:w-80 lg:pb-2 lg:pt-2 overflow-y-scroll text-white lg:mb-4 border rounded-lg bg-gray-800'>";
|
||||
known_universes.forEach((name) => {
|
||||
final_html += `
|
||||
<li onclick="_loadUniverseFromInterface('${name}')" class="hover:fill-black hover:bg-white py-2 hover:text-black flex justify-between px-4">
|
||||
<p >${name}</p>
|
||||
<button onclick=_deleteUniverseFromInterface('${name}')>🗑</button>
|
||||
</li>`;
|
||||
});
|
||||
final_html = final_html + "</ul>";
|
||||
existing_universes!.innerHTML = final_html;
|
||||
this.updateKnownUniversesView();
|
||||
this.openBuffersModal();
|
||||
}
|
||||
|
||||
@ -408,6 +405,12 @@ export class Editor {
|
||||
});
|
||||
}
|
||||
|
||||
this.topos_logo.addEventListener("click", () => {
|
||||
this.hideDocumentation();
|
||||
this.updateKnownUniversesView();
|
||||
this.openBuffersModal();
|
||||
})
|
||||
|
||||
this.play_buttons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
if (this.isPlaying) {
|
||||
@ -495,6 +498,10 @@ export class Editor {
|
||||
editor?.classList.remove("invisible");
|
||||
});
|
||||
|
||||
this.close_universes_button.addEventListener("click", () => {
|
||||
this.openBuffersModal();
|
||||
});
|
||||
|
||||
this.font_size_slider.addEventListener("input", () => {
|
||||
const new_value = this.font_size_slider.value;
|
||||
this.settings.font_size = parseInt(new_value);
|
||||
@ -665,6 +672,21 @@ export class Editor {
|
||||
return JSON.parse(hash);
|
||||
};
|
||||
|
||||
updateKnownUniversesView = () => {
|
||||
let existing_universes = document.getElementById("existing-universes");
|
||||
let known_universes = Object.keys(this.universes);
|
||||
let final_html = "<ul class='lg:h-80 lg:w-80 lg:pb-2 lg:pt-2 overflow-y-scroll text-white lg:mb-4 border rounded-lg bg-gray-800'>";
|
||||
known_universes.forEach((name) => {
|
||||
final_html += `
|
||||
<li onclick="_loadUniverseFromInterface('${name}')" class="hover:fill-black hover:bg-white py-2 hover:text-black flex justify-between px-4">
|
||||
<p >${name}</p>
|
||||
<button onclick=_deleteUniverseFromInterface('${name}')>🗑</button>
|
||||
</li>`;
|
||||
});
|
||||
final_html = final_html + "</ul>";
|
||||
existing_universes!.innerHTML = final_html;
|
||||
}
|
||||
|
||||
share() {
|
||||
const hashed_table = btoa(
|
||||
JSON.stringify({
|
||||
@ -744,6 +766,7 @@ export class Editor {
|
||||
button.children[0].classList.remove("text-white");
|
||||
button.children[0].classList.add("text-orange-300");
|
||||
button.classList.add("text-orange-300");
|
||||
button.classList.add("fill-orange-300");
|
||||
};
|
||||
|
||||
switch (mode) {
|
||||
@ -968,12 +991,8 @@ export class Editor {
|
||||
}
|
||||
}
|
||||
|
||||
// Creating the application
|
||||
const app = new Editor();
|
||||
|
||||
|
||||
|
||||
// When the user leaves the page, all the universes should be saved in the localStorage
|
||||
window.addEventListener("beforeunload", () => {
|
||||
// @ts-ignore
|
||||
event.preventDefault();
|
||||
@ -984,11 +1003,3 @@ window.addEventListener("beforeunload", () => {
|
||||
app.clock.stop();
|
||||
return null;
|
||||
});
|
||||
|
||||
// function reportMouseCoordinates(event: MouseEvent) {
|
||||
// app._mouseX = event.clientX;
|
||||
// app._mouseY = event.clientY;
|
||||
// }
|
||||
|
||||
// onmousemove = function(e){console.log("mouse location:", e.clientX, e.clientY)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user