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

This commit is contained in:
2023-08-28 18:23:40 +02:00
9 changed files with 476 additions and 277 deletions

111
README.md
View File

@ -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.
![Screenshot](https://github.com/Bubobubobubobubo/Topos/blob/main/img/screnshot.png) ![Screenshot](https://github.com/Bubobubobubobubo/Topos/blob/main/img/screnshot.png)
## Disclaimer ## 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. ## Installation (for devs and contributors)
- Do not expect stable features and/or support!
- Contributors are welcome!
## Presentation 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:
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:
- `yarn install` - `yarn install`
- `yarn run dev` - `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 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!
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: 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 build`
- `yarn tauri dev` - `yarn tauri dev`
# Roadmap to Topos v1.0 (first release) The `tauri` version is only here to quickstart future developments but nothing has been done yet.
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.

View File

@ -46,13 +46,10 @@
<header class="py-2 block text-white bg-neutral-900"> <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"> <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"> <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" /> <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> </svg>
<span id="universe-viewer" class="hidden xl:block ml-4 text-2xl text-white">Topos</span> <span id="universe-viewer" class="hidden xl:block ml-4 text-2xl text-white">Topos</span>
</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"> <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> </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 text-white inline-block">Play</p>
</a> </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"> <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-gray-800 dark: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 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"/> <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="black" rx="1" ry="1"/>
</svg> </svg>
@ -79,7 +76,7 @@
</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="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"/> <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> </svg>
<p class="hidden lg:block text-xl pl-2 text-white inline-block">Clear</p> <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> <p class="hidden lg:block text-xl pl-2 text-white inline-block">Share</p>
</a> </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"> <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">
<p class="hidden lg:block text-xl pr-2 text-white inline-block">Docs</p> <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 text-gray-800 dark: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"/> <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>
</a> </a>
</nav> </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> <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> </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">
<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> </div>
</div> </div>
@ -223,25 +224,27 @@
dark:border-neutral-700 border-none" dark: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 text-gray-700 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="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-orange-300" fill="currentColor" viewBox="0 0 20 20"> <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="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"/> <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>
</a> </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"> <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" fill="currentColor" viewBox="0 0 18 20"> <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="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"/> <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"/>
</svg> <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"/>
</a> </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"> <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 " fill="none" viewBox="0 0 21 20"> <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="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"/> <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-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"> <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"/> <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>

View File

@ -1,6 +1,6 @@
import { seededRandom } from "zifferjs"; import { seededRandom } from "zifferjs";
import { MidiConnection } from "./IO/MidiConnection"; import { MidiConnection } from "./IO/MidiConnection";
import { tryEvaluate } from "./Evaluator"; import { tryEvaluate, evaluateOnce } from "./Evaluator";
import { DrunkWalk } from "./Utils/Drunk"; import { DrunkWalk } from "./Utils/Drunk";
import { scale } from "./Scales"; import { scale } from "./Scales";
import { Editor } from "./main"; import { Editor } from "./main";
@ -66,7 +66,7 @@ export class UserAPI {
this.app.universes, this.app.universes,
this.app.settings this.app.settings
); );
this.app.openBuffersModal(); this.app.updateKnownUniversesView();
} }
_playDocExample = (code?: string) => { _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 => { _all_samples = (): object => {
return soundMap.get(); return soundMap.get();
}; };
@ -884,7 +891,7 @@ export class UserAPI {
const results: boolean[] = n.map((value) => this.epulse() % value === 0); const results: boolean[] = n.map((value) => this.epulse() % value === 0);
return results.some((value) => value === true); return results.some((value) => value === true);
}; };
pmod = this.modpulse; modp = this.modpulse;
public modbar = (...n: number[]): boolean => { public modbar = (...n: number[]): boolean => {
const results: boolean[] = n.map( const results: boolean[] = n.map(
@ -908,12 +915,12 @@ export class UserAPI {
return current_chunk % 2 === 0; return current_chunk % 2 === 0;
}; };
onbar = (n: number, ...bar: number[]): boolean => { public onbar = (bars: number[] | number, n: number = this.app.clock.time_signature[0]): boolean => {
// n is acting as a modulo on the bar number let current_bar = (this.bar() % n) + 1;
const bar_list = [...Array(n).keys()].map((i) => i + 1); return (typeof bars === "number")
console.log(bar.some((b) => bar_list.includes(b % n))); ? bars === current_bar
return bar.some((b) => bar_list.includes(b % n)); : bars.some((b) => b == current_bar)
}; };
onbeat = (...beat: number[]): boolean => { onbeat = (...beat: number[]): boolean => {
/** /**
@ -991,7 +998,16 @@ export class UserAPI {
*/ */
return this._euclidean_cycle(pulses, length, rotate)[iterator % length]; 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( _euclidean_cycle(
pulses: number, pulses: number,

View File

@ -69,7 +69,7 @@ export const template_universe = {
}; };
export const template_universes = { export const template_universes = {
Default: { Welcome: {
global: { candidate: "", committed: "", evaluations: 0 }, global: { candidate: "", committed: "", evaluations: 0 },
locals: { locals: {
1: { candidate: "", committed: "", evaluations: 0 }, 1: { candidate: "", committed: "", evaluations: 0 },
@ -83,7 +83,7 @@ export const template_universes = {
9: { candidate: "", committed: "", evaluations: 0 }, 9: { candidate: "", committed: "", evaluations: 0 },
}, },
init: { candidate: "", committed: "", evaluations: 0 }, init: { candidate: "", committed: "", evaluations: 0 },
notes: { candidate: "// NOTES" }, notes: { candidate: "" },
}, },
Help: tutorial_universe, Help: tutorial_universe,
}; };

View File

@ -3,6 +3,10 @@ export {};
declare global { declare global {
interface Array<T> { interface Array<T> {
add(amount: number): number[];
sub(amount: number): number[];
mult(amount: number): number[];
division(amount: number): number[];
palindrome(): T[]; palindrome(): T[];
random(index: number): T; random(index: number): T;
rand(index: number): T; rand(index: number): T;
@ -20,6 +24,8 @@ declare global {
rotate(steps: number): this; rotate(steps: number): this;
unique(): this; unique(): this;
in(value: T): boolean; 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 { Array.prototype.in = function <T>(this: T[], value: T): boolean {
return this.includes(value); 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 () { Array.prototype.pick = function () {
/** /**
@ -37,13 +91,13 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this[Math.floor(api.randomGen() * this.length)]; 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
* *
* @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 () { Array.prototype.bar = function () {
@ -160,7 +214,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this; 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. * Repeats all elements in the array n times.
* *
@ -181,7 +235,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this; 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 * Repeats all elements in the array n times, except for the
* elements at odd indexes. * elements at odd indexes.
@ -211,7 +265,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this; 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 * Repeats all elements in the array n times, except for the
* elements at even indexes. * elements at even indexes.

View File

@ -22,14 +22,14 @@ const samples_to_markdown = (application: Editor) => {
// Adding new examples for each sample folder! // Adding new examples for each sample folder!
const codeId = `sampleExample${i}`; 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 // @ts-ignore
const howMany = samples[keys[i]].data.samples.length; const howMany = samples[keys[i]].data.samples.length;
markdownList += ` markdownList += `
<button <button
class="hover:bg-neutral-500 inline px-4 py-2 bg-neutral-700 text-orange-300 text-2xl" class="hover:bg-neutral-500 inline px-4 py-2 bg-neutral-700 text-orange-300 text-xl"
onclick="app.api._playDocExample(app.api.codeExamples['${codeId}'])" onclick="app.api._playDocExampleOnce(app.api.codeExamples['${codeId}'])"
> >
${keys[i]} ${keys[i]}
<b class="text-white">(${howMany})</b> <b class="text-white">(${howMany})</b>
@ -82,11 +82,21 @@ Welcome to the Topos documentation. These pages are offering you an introduction
${makeExample( ${makeExample(
"Welcome! Eval to get started", ` "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) bpm(110)
.sustain(0.1).out() mod(0.125) && sound('sawtooth')
mod(1) :: sound('kick').out()`, .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 true
)} )}
@ -97,7 +107,11 @@ Topos is an _algorithmic_ sequencer. Topos uses small algorithms to represent mu
${makeExample( ${makeExample(
"Small algorithms for direct musical expression", "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 false
)} )}
@ -112,7 +126,11 @@ ${makeExample(
${makeExample( ${makeExample(
"Making the web less dreadful, one beep at at time", "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 false
)} )}
@ -127,39 +145,32 @@ Press ${key_shortcut(
)}. You are now making music: )}. You are now making music:
${makeExample( ${makeExample(
"Drums and arpeggios", "Obscure shenanigans",
` `
bpm(80) mod([1/4,1/8,1/16].div(8)):: sound('sine')
mod(0.25) && sound('sawtooth') .freq([100,50].div(16) + 50 * ($(1)%10))
.note(seqbar( .gain(0.5).room(0.9).size(0.9)
[60, 67, 63].pick() - 12, [60, 67, 63].pick() - 12, .sustain(0.1).out()
[60, 67, 63].pick() - 12 + 5, [60, 67, 63].pick() - 12 + 5, mod(1) :: sound('kick').out()
[60, 67, 63].pick() - 12 + 7, [60, 67, 63].pick() - 12 + 7) + (sometimes() ? 24 : 12) mod(2) :: sound('dr').n(5).out()
) div(3) :: mod([.25,.5].div(.5)) :: sound('dr')
.sustain(0.1).fmi(8).fmh(4).room(0.9) .n([8,9].pick()).gain([.8,.5,.25,.1,.0].div(.25)).out()`,
.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();
`,
true true
)} )}
${makeExample( ${makeExample(
"Resonant madness", "Resonant madness",
`mod(.5)::snd('synth2') `
.freq([50,50*1.25,50*1.5,50*1.75].div(8) / 2) mod(.25)::snd('arpy')
.cutoff(usine(.5) * 5000).resonance(15).end(0.8).room(0.9).size(0.9).n(7).out(); .note(30 + [0,3,7,10].beat())
mod(.25)::snd('synth2') .cutoff(usine(.5) * 5000).resonance(10).gain(0.3)
.freq([50,50*1.25,50*1.5,50*1.75].div(.5)) .end(0.8).room(0.9).size(0.9).n(0).out();
.cutoff(usine(.5) * 5000).resonance(15) mod([.25,.125].div(2))::snd('arpy')
.end(0.2).room(0.9).size(0.9).n(14).out() .note(30 + [0,3,7,10].beat())
mod(1)::snd('kick').out() .cutoff(usine(.5) * 5000).resonance(20).gain(0.3)
mod(2)::snd('snare').shape(0.5).out() .end(0.8).room(0.9).size(0.9).n(3).out();
mod(.75)::snd('hat').shape(0.4).out()`, mod(.5) :: snd('arpy').note(
[30, 33, 35].repeatAll(4).div(1) - [12,0].div(0.5)).out()`,
false false
)} )}
`; `;
@ -253,15 +264,17 @@ Let's study two very simple rhythmic functions, <icode>mod(n: ...number[])</icod
${makeExample( ${makeExample(
"Using different mod values", "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 true
)} )}
${makeExample( ${makeExample(
"Some sort of ringtone", "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) :: blip(200).out();
mod(1/3) :: blip(400).out(); mod(1/3) :: blip(400).out();
div(3) :: mod(1/6) :: blip(800).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 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. - <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( ${makeExample(
"Some simple yet detailed rhythms", "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(2,4)::snd('snare').out() // Snare on acccentuated beats
onbeat(1.5,2.5,3.5, 3.75)::snd('hat').out() // Cool high-hats 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( ${makeExample(
"Let's do something more complex", "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() onbeat(2, [1.5, 3].pick(), 4)::snd('snare').n(7).out()
mod([.25, 1/8].div(1.5))::snd('hat').n(2) mod([.25, 1/8].div(1.5))::snd('hat').n(2)
.gain(rand(0.4, 0.7)) .gain(rand(0.4, 0.7))
@ -311,8 +346,10 @@ mod(.5) && euclid($(2), 2, 8) && snd('sd').out()
${makeExample( ${makeExample(
"And now for more interesting rhythmic constructions", "And now for more interesting rhythmic constructions",
` `
mod(.5) && euclid($(1), 5, 9) && snd('kick').out() bpm(145); // Setting a faster BPM
mod(.5) && euclid($(2), 2, 3, 1) && snd('pluck').end(0.5).n(5).out() 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 false
)} )}
@ -321,15 +358,29 @@ ${makeExample(
"Adding more rhythmic density", "Adding more rhythmic density",
` `
mod(.5) && euclid($(1), 5, 9) && snd('kick').out() 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($(2), 2, 3, 1) && snd('east').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($(3), 6, 9, 1) && snd('east').end(0.5).n(5).freq(200).out()
mod(.25) && euclid($(4), 7, 9, 1) && snd('hh').out() mod(.25) && euclid($(4), 7, 9, 1) && snd('hh').out()
`, `,
false 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. - <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( ${makeExample(
@ -343,15 +394,17 @@ mod(.5) && bin($(2), 48) && snd('sd').out()
${makeExample( ${makeExample(
"Calling 911", "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 false
)} )}
${makeExample( ${makeExample(
"Playing around with simple numbers", "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() && snd('tabla').n($(2)).delay(0.5).delayt(0.25).out()
mod(1) && sound('kick').shape(0.5).out() mod(1) && sound('kick').shape(0.5).out()
`, `,
@ -424,7 +477,8 @@ mod(.5)::snd(div(2) ? 'kick' : 'hat').out()
${makeExample( ${makeExample(
"div is great for pretty much everything", "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([4, .5].beat()) :: mod(.25) :: sound('shaker').speed(2).out();
div([1, 2].beat()) :: mod(1.75) :: sound('snare').out(); div([1, 2].beat()) :: mod(1.75) :: sound('snare').out();
div(4) :: mod(.5) :: sound('tom').out() div(4) :: mod(.5) :: sound('tom').out()
@ -446,9 +500,16 @@ divbar(3)::mod(.5)::snd('hat').out()
`, `,
true 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( ${makeExample(
"Using onbar for filler drums", "Using onbar for filler drums",
@ -457,7 +518,7 @@ ${makeExample(
onbar(4, 4)::mod(.5)::snd('hh').out(); onbar(4, 4)::mod(.5)::snd('hh').out();
// Here comes a longer version using JavaScript normal control flow // 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(); mod(1)::snd('kick').out();
} else { } else {
mod(.5)::snd('sd').out(); mod(.5)::snd('sd').out();
@ -465,7 +526,7 @@ if (onbar(4, 1, 3)) {
`, `,
true true
)} )}
## What are pulses? ## 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. 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( ${makeExample(
"Playing sounds is easy", "Playing sounds is easy",
`mod(1) && sound('bd').out() `
mod(1) && sound('bd').out()
mod(0.5) && sound('hh').out() mod(0.5) && sound('hh').out()
`, `,
true true
@ -621,7 +683,8 @@ Let's make it slightly more complex:
${makeExample( ${makeExample(
"Adding some effects", "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(); mod(0.5) && sound('hh').delay(0.25).delaytime(0.125).out();
`, `,
true true
@ -640,12 +703,14 @@ Let's pause for a moment to explain what we just wrote. There are many things to
${makeExample( ${makeExample(
'"Composing" a sound or making a sound chain', '"Composing" a sound or making a sound chain',
`mod(1) :: sound('pad') `
mod(1) :: sound('pad')
.begin(rand(0, 0.4)) .begin(rand(0, 0.4))
.freq([50,52].beat()) .freq([50,52].beat())
.size(0.9) .size(0.9)
.room(0.9) .room(0.9)
.pan(sine()).release(2).out()`, .velocity(0.25)
.pan(usine()).release(2).out()`,
true 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: 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( ${makeExample(
"Picking a sample", "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 true
)} )}
@ -680,7 +747,8 @@ Don't worry about the number. If it gets too big, it will be automatically wrapp
${makeExample( ${makeExample(
"Picking a sample... with your mouse!", "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()`, mod(.25) && sound('numbers').n(Math.floor(mouseX())).out()`,
true 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: 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( ${makeExample(
"Learning through repetition", "Learning through repetition",
`mod(0.5) && sound('hh') `
.sometimes(s=>s.speed([1,5,10].pick())) mod(0.5) && sound('hh')
.room(0.5) .sometimes(s=>s.speed([1,5,10].pick()))
.cutoff(usine(2) * 5000) .room(0.5)
.out()`, .cutoff(usine(2) * 5000)
.out()`,
true true
)} )}
@ -748,11 +817,11 @@ Note that the **sustain** value is not a duration but an amplitude value (how lo
${makeExample( ${makeExample(
"Simple synthesizer", "Simple synthesizer",
` `
mod(4)::sound('sawtooth').note(50).decay(0.5).sustain(0.5).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).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).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)) 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 true
)}; )};
@ -804,9 +873,9 @@ ${makeExample(
"Filter sweep using a low frequency oscillator", "Filter sweep using a low frequency oscillator",
` `
mod(.5) && snd('sawtooth') mod(.5) && snd('sawtooth')
.cutoff([2000,500].pick() + usine(.5) * 4000) .cutoff([2000,500].pick() + usine(.5) * 4000)
.resonance(0.9).freq([100,150].pick()) .resonance(0.9).freq([100,150].pick())
.out() .out()
`, `,
true true
)}; )};
@ -895,7 +964,8 @@ JavaScript is using [Arrays](https://developer.mozilla.org/en-US/docs/Web/JavaSc
${makeExample( ${makeExample(
"Light drumming", "Light drumming",
`// Every bar, use a different rhythm `
// Every bar, use a different rhythm
mod([1, 0.75].div(4)) :: sound('cp').out() mod([1, 0.75].div(4)) :: sound('cp').out()
mod([0.5, 1].div(4)) :: sound('kick').out() mod([0.5, 1].div(4)) :: sound('kick').out()
mod(2)::snd('snare').shape(.5).out() mod(2)::snd('snare').shape(.5).out()
@ -904,7 +974,8 @@ mod(2)::snd('snare').shape(.5).out()
)} )}
${makeExample( ${makeExample(
"Using div to create arpeggios", "Using div to create arpeggios",
`// Arpeggio using pulse divisions `
// Arpeggio using pulse divisions
mod([.5, .25].div(2)) :: sound('sine') mod([.5, .25].div(2)) :: sound('sine')
.hcutoff(400) .hcutoff(400)
.fmi([1,2].div(8)) .fmi([1,2].div(8))
@ -920,7 +991,8 @@ mod([.5, .25].div(2)) :: sound('sine')
)} )}
${makeExample( ${makeExample(
"Cool ambiance", "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() mod([2,4].div(2)) :: snd('shaker').delay(.5).delayfb(.75).delayt(0.125).out()
div(2)::mod(1)::snd('clap').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() 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( ${makeExample(
"A simple drumbeat in no time!", "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() mod(1.5)::sound(['jvbass', 'clap'].beat()).out()
`, `,
true true
@ -957,7 +1030,8 @@ mod([1, 0.5].beat()) :: sound(['bass3'].bar())
${makeExample( ${makeExample(
"Palindrome filter sweep", "Palindrome filter sweep",
`mod([1,.5,.25].beat()) :: snd('sine') `
mod([1,.5,.25].beat()) :: snd('sine')
.freq([100,200,300].div(0.25)) .freq([100,200,300].div(0.25))
.fmi([1,2,3].palindrome().div(0.5)) .fmi([1,2,3].palindrome().div(0.5))
.fmh([4, 8].palindrome().beat()) .fmh([4, 8].palindrome().beat())
@ -976,7 +1050,8 @@ ${makeExample(
${makeExample( ${makeExample(
"Sipping some gasoline at the robot bar", "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( mod([.5, 1].random() / 2) :: snd(
['amencutup', 'synth2'].random()) ['amencutup', 'synth2'].random())
.n(irand(4,10)) .n(irand(4,10))
@ -991,7 +1066,8 @@ mod([.5, 1].random() / 2) :: snd(
${makeExample( ${makeExample(
"Amen break suffering from data loss", "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() mod(.25)::snd('amencutup').n([1,2,3,4,5,6,7,8,9].degrade(20).loop($(1))).out()
`, `,
true 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( ${makeExample(
"Repeating samples a given number of times", "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() mod(.25)::sound('amencutup').n([1,2,3,4,5,6,7,8].repeatAll(4).beat()).out()
`, `,
true true
@ -1013,7 +1090,9 @@ mod(.25)::sound('amencutup').n([1,2,3,4,5,6,7,8].repeatAll(4).beat()).out()
${makeExample( ${makeExample(
"Don't you know how to count up to 5?", "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 true
)} )}
@ -1021,7 +1100,8 @@ ${makeExample(
${makeExample( ${makeExample(
"Shuffling a list for extra randomness", "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 true
)} )}
@ -1030,10 +1110,11 @@ ${makeExample(
${makeExample( ${makeExample(
"To make things more complex... here you go", "To make things more complex... here you go",
`mod(.5) :: snd('sine') `
mod(.5) :: snd('sine')
.freq([100, 150, 200, 250, ,300, 400] .freq([100, 150, 200, 250, ,300, 400]
.rotate([1,2,3].bar()) // The list of frequencies is rotating .rotate([1,2,3].bar()) // The list of frequencies is rotating
.beat()) // while being indexed over! .beat()) // while being indexed over!
.sustain(0.1) .sustain(0.1)
.out() .out()
`, `,
@ -1044,12 +1125,23 @@ ${makeExample(
${makeExample( ${makeExample(
"Demonstrative filtering. Final list is [100, 200]", "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() mod(1)::snd('sine').sustain(0.1).freq([100,100,100,100,200].unique().beat()).out()
`, `,
true 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 ## Simple patterns
@ -1248,7 +1340,8 @@ The <icode>sound</icode> function can take the name of a synthesizer as first ar
${makeExample( ${makeExample(
"Simple synthesizer voice with filter", "Simple synthesizer voice with filter",
`mod(.5) && snd('sawtooth') `
mod(.5) && snd('sawtooth')
.cutoff([2000,500].pick() + usine(.5) * 4000) .cutoff([2000,500].pick() + usine(.5) * 4000)
.resonance(0.9).freq([100,150].pick()) .resonance(0.9).freq([100,150].pick())
.out() .out()
@ -1258,8 +1351,9 @@ ${makeExample(
${makeExample( ${makeExample(
"Listening to the different waveforms from the sweetest to the harshest", "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() .out()
`, `,
false false
@ -1268,7 +1362,8 @@ ${makeExample(
${makeExample( ${makeExample(
"Blessed by the square wave", "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) :: [100,101].forEach((freq) => sound('square').freq(freq*2).sustain(0.01).out())
mod([.5, .75, 2].beat()) :: [100,101].forEach((freq) => sound('square') mod([.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())
@ -1279,11 +1374,14 @@ mod(.25) :: sound('square').freq(100*[1,2,4,8].beat()).sustain(0.1).out()`,
${makeExample( ${makeExample(
"Ghost carillon", "Ghost carillon",
`mod(1/8)::sound('sine') `
mod(1/8)::sound('sine')
.velocity(rand(0.0, 1.0)) .velocity(rand(0.0, 1.0))
.delay(0.75).delayt(.5) .delay(0.75).delayt(.5)
.sustain(0.4) .sustain(0.4)
.cutoff(2000)
.freq(mouseX()) .freq(mouseX())
.gain(0.25)
.out()`, .out()`,
false false
)} )}
@ -1298,7 +1396,8 @@ The same basic waveforms can take additional methods to switch to a basic two op
${makeExample( ${makeExample(
"80s nostalgia", "80s nostalgia",
`mod(.25) && snd('sine') `
mod(.25) && snd('sine')
.fmi([1,2,4,8].pick()) .fmi([1,2,4,8].pick())
.fmh([1,2,4,8].div(8)) .fmh([1,2,4,8].div(8))
.freq([100,150].pick()) .freq([100,150].pick())
@ -1310,7 +1409,8 @@ ${makeExample(
${makeExample( ${makeExample(
"Giving some love to weird ratios", "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([4].bar()) :: sound('sine').fm('5.2183:4.5').sustain(0.05).out()
mod(.5) :: sound('sine') mod(.5) :: sound('sine')
.fmh([1, 1.75].beat()) .fmh([1, 1.75].beat())
@ -1321,9 +1421,10 @@ mod(.5) :: sound('sine')
${makeExample( ${makeExample(
"Some peace and serenity", "Some peace and serenity",
`mod(0.25) :: sound('sine') `
.note([60, 67, 70, 72, 77].beat()) mod(0.25) :: sound('sine')
.attack(0.2).release(0.5).gain(0.5) .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) .room(0.9).size(0.8).sustain(0.5)
.fmi(Math.floor(usine(.25) * 10)) .fmi(Math.floor(usine(.25) * 10))
.cutoff(1500).delay(0.5).delayt(0.125) .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( ${makeExample(
"Shortening your if conditions", "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') //if (true) && log('very true')
// These two lines are the same:
// mod(1) && snd('bd').out()
//// mod(1) :: snd('bd').out()
`, `,
true true
)} )}
${makeExample( ${makeExample(
"More complex conditions using ?", "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() mod(4) ? snd('kick').out() : mod(2)::snd('snare').out()
// (true) ? log('very true') : log('very false') // (true) ? log('very true') : log('very false')
`, `,
@ -1402,7 +1510,8 @@ mod(4) ? snd('kick').out() : mod(2)::snd('snare').out()
${makeExample( ${makeExample(
"Using not and other short symbols", "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(4) ? snd('kick').out() : mod(2)::snd('snare').out()
!mod(2) :: mod(0.5)::snd('clap').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>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_. - <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 ${makeExample(
mod(.25) && snd('cp').speed(1 + usine(0.25) * 2).out() "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>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_. - <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>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_. - <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>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. - <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 ${makeExample(
mod(.25) && snd('cp').speed(1 + usquare(0.25, 0, 0.25) * 2).out() "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. - <icode>noise()</icode>: returns a random value between -1 and 1.
\`\`\`javascript ${makeExample(
mod(.25) && snd('cp').speed(1 + noise() * 2).out() "Modulating the speed of a sample player using noise", 
\`\`\` `mod(.25) && snd('cp').speed(1 + noise() * 2).out()`, true)};
## Probabilities ## Probabilities

View File

@ -95,3 +95,17 @@ export const evaluate = async (
console.log(error); 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
View 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()
`
]

View File

@ -3,6 +3,7 @@ import {
colors, colors,
animals, animals,
} from "unique-names-generator"; } from "unique-names-generator";
import { examples } from "./examples/excerpts";
import { EditorState, Compartment } from "@codemirror/state"; import { EditorState, Compartment } from "@codemirror/state";
import { ViewUpdate, lineNumbers, keymap } from "@codemirror/view"; import { ViewUpdate, lineNumbers, keymap } from "@codemirror/view";
import { javascript } from "@codemirror/lang-javascript"; import { javascript } from "@codemirror/lang-javascript";
@ -90,6 +91,9 @@ export class Editor {
public _mouseX: number = 0; public _mouseX: number = 0;
public _mouseY: number = 0; public _mouseY: number = 0;
// Topos Logo
topos_logo: HTMLElement = document.getElementById('topos-logo') as HTMLElement;
// Transport elements // Transport elements
play_buttons: HTMLButtonElement[] = [ play_buttons: HTMLButtonElement[] = [
document.getElementById("play-button-1") as HTMLButtonElement, document.getElementById("play-button-1") as HTMLButtonElement,
@ -130,6 +134,10 @@ export class Editor {
close_settings_button: HTMLButtonElement = document.getElementById( close_settings_button: HTMLButtonElement = document.getElementById(
"close-settings-button" "close-settings-button"
) as HTMLButtonElement; ) as HTMLButtonElement;
close_universes_button: HTMLButtonElement = document.getElementById(
"close-universes-button"
) as HTMLButtonElement;
universe_viewer: HTMLDivElement = document.getElementById( universe_viewer: HTMLDivElement = document.getElementById(
"universe-viewer" "universe-viewer"
) as HTMLDivElement; ) as HTMLDivElement;
@ -184,9 +192,12 @@ export class Editor {
// Loading the universe from local storage // 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.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 // Audio context and clock
@ -237,9 +248,6 @@ export class Editor {
// ================================================================================ // ================================================================================
// Building the documentation // Building the documentation
// loadSamples().then(() => {
// this.docs = documentation_factory(this);
// });
let pre_loading = async () => { await loadSamples(); }; let pre_loading = async () => { await loadSamples(); };
pre_loading(); pre_loading();
this.docs = documentation_factory(this); this.docs = documentation_factory(this);
@ -307,18 +315,7 @@ export class Editor {
// This is the modal to switch between universes // This is the modal to switch between universes
if (event.ctrlKey && event.key === "b") { if (event.ctrlKey && event.key === "b") {
this.hideDocumentation(); this.hideDocumentation();
let existing_universes = document.getElementById("existing-universes"); this.updateKnownUniversesView();
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.openBuffersModal(); 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) => { this.play_buttons.forEach((button) => {
button.addEventListener("click", () => { button.addEventListener("click", () => {
if (this.isPlaying) { if (this.isPlaying) {
@ -495,6 +498,10 @@ export class Editor {
editor?.classList.remove("invisible"); editor?.classList.remove("invisible");
}); });
this.close_universes_button.addEventListener("click", () => {
this.openBuffersModal();
});
this.font_size_slider.addEventListener("input", () => { this.font_size_slider.addEventListener("input", () => {
const new_value = this.font_size_slider.value; const new_value = this.font_size_slider.value;
this.settings.font_size = parseInt(new_value); this.settings.font_size = parseInt(new_value);
@ -665,6 +672,21 @@ export class Editor {
return JSON.parse(hash); 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() { share() {
const hashed_table = btoa( const hashed_table = btoa(
JSON.stringify({ JSON.stringify({
@ -744,6 +766,7 @@ export class Editor {
button.children[0].classList.remove("text-white"); button.children[0].classList.remove("text-white");
button.children[0].classList.add("text-orange-300"); button.children[0].classList.add("text-orange-300");
button.classList.add("text-orange-300"); button.classList.add("text-orange-300");
button.classList.add("fill-orange-300");
}; };
switch (mode) { switch (mode) {
@ -968,12 +991,8 @@ export class Editor {
} }
} }
// Creating the application
const app = new Editor(); const app = new Editor();
// When the user leaves the page, all the universes should be saved in the localStorage
window.addEventListener("beforeunload", () => { window.addEventListener("beforeunload", () => {
// @ts-ignore // @ts-ignore
event.preventDefault(); event.preventDefault();
@ -984,11 +1003,3 @@ window.addEventListener("beforeunload", () => {
app.clock.stop(); app.clock.stop();
return null; 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)}