Generating documentation using factory

This commit is contained in:
2023-08-25 12:36:37 +02:00
parent 94acee819a
commit 58c0a2cf66
3 changed files with 407 additions and 370 deletions

View File

@ -51,7 +51,7 @@ Array.prototype.in = function <T>(this: T[], value: T): boolean {
return this.includes(value); return this.includes(value);
}; };
async function loadSamples() { export async function loadSamples() {
// const ds = "https://raw.githubusercontent.com/felixroos/dough-samples/main/"; // const ds = "https://raw.githubusercontent.com/felixroos/dough-samples/main/";
return Promise.all([ return Promise.all([
initAudioOnFirstClick(), initAudioOnFirstClick(),
@ -62,8 +62,6 @@ async function loadSamples() {
]); ]);
} }
loadSamples();
export const generateCacheKey = (...args: any[]): string => { export const generateCacheKey = (...args: any[]): string => {
return args.map((arg) => JSON.stringify(arg)).join(","); return args.map((arg) => JSON.stringify(arg)).join(",");
}; };
@ -92,6 +90,10 @@ export class UserAPI {
//this.load = samples("github:tidalcycles/Dirt-Samples/master"); //this.load = samples("github:tidalcycles/Dirt-Samples/master");
} }
_all_samples = (): object => {
return soundMap.get();
};
_reportError = (error: any): void => { _reportError = (error: any): void => {
console.log(error); console.log(error);
clearTimeout(this.errorTimeoutID); clearTimeout(this.errorTimeoutID);
@ -236,7 +238,7 @@ export class UserAPI {
} }
}; };
public midi = (value: number|object = 60): NoteEvent => { public midi = (value: number | object = 60): NoteEvent => {
/** /**
* Sends a MIDI note to the current MIDI output. * Sends a MIDI note to the current MIDI output.
* *
@ -1235,7 +1237,7 @@ export class UserAPI {
// Trivial functions // Trivial functions
// ============================================================= // =============================================================
sound = (sound: string|object) => { sound = (sound: string | object) => {
return new SoundEvent(sound, this.app); return new SoundEvent(sound, this.app);
}; };

View File

@ -1,17 +1,47 @@
import { type Editor } from "./main";
const key_shortcut = (shortcut: string): string => { const key_shortcut = (shortcut: string): string => {
return `<kbd class="lg:px-2 lg:py-1.5 px-1 py-1 lg:text-sm text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">${shortcut}</kbd>`; return `<kbd class="lg:px-2 lg:py-1.5 px-1 py-1 lg:text-sm text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">${shortcut}</kbd>`;
}; };
const injectAvailableSamples = (): string => { const samples_to_markdown = (samples: object) => {
return ""; let markdownList = "";
let keys = Object.keys(samples);
let i = -1;
while (i++ < keys.length - 1) {
//@ts-ignore
if (!samples[keys[i]].data) continue;
//@ts-ignore
if (!samples[keys[i]].data.samples) continue;
markdownList += `**${keys[i]}** (_${
//@ts-ignore
samples[keys[i]].data.samples.length
}_) `;
// let i2 = -1;
// while (i2++ < samples[keys[i]].data.samples.length - 1) {
// console.log(samples[keys[i]].data.samples[i2]);
// markdownList += `\t- <audio controls> <source src="${
// samples[keys[i]].data.samples[i2]
// }" type="audio/wav">\n
// </audio>
// `;
// }
}
return markdownList;
}; };
const introduction: string = ` const injectAvailableSamples = (application: Editor): string => {
let test = samples_to_markdown(application.api._all_samples());
return test;
};
export const documentation_factory = (application: Editor) => {
const introduction: string = `
# Welcome # Welcome
Welcome to the Topos documentation. These pages are offering you an introduction to the software and to the ideas behind it. You can jump here anytime by pressing ${key_shortcut( Welcome to the Topos documentation. These pages are offering you an introduction to the software and to the ideas behind it. You can jump here anytime by pressing ${key_shortcut(
"Ctrl + D" "Ctrl + D"
)}. Press again to make the documentation disappear. All your contributions are welcome! )}. Press again to make the documentation disappear. All your contributions are welcome!
## What is Topos? ## What is Topos?
@ -24,10 +54,9 @@ Topos is deeply inspired by the [Monome Teletype](https://monome.org/). The Tele
Press ${key_shortcut( Press ${key_shortcut(
"Ctrl + G" "Ctrl + G"
)} to switch to the global file. This is where everything starts! Evaluate the following script there by pasting and pressing ${key_shortcut( )} to switch to the global file. This is where everything starts! Evaluate the following script there by pasting and pressing ${key_shortcut(
"Ctrl + Enter" "Ctrl + Enter"
)}. You are now making music: )}. You are now making music:
<pre><code class="language-javascript"> <pre><code class="language-javascript">
bpm(80) bpm(80)
mod(0.25) :: sound('sawtooth') mod(0.25) :: sound('sawtooth')
@ -40,13 +69,13 @@ mod(0.25) :: sound('sawtooth')
.delay(0.5).delaytime(bpm() / 60 / 4 / 3) .delay(0.5).delaytime(bpm() / 60 / 4 / 3)
.delayfeedback(0.25) .delayfeedback(0.25)
.out() .out()
mod(1) && snd('kick').out() mod(1) && snd('kick').out()
mod(2) && snd('snare').out() mod(2) && snd('snare').out()
mod(.5) && snd('hat').out() mod(.5) && snd('hat').out()
</code></pre> </code></pre>
`; `;
const software_interface: string = ` const software_interface: string = `
# Interface # Interface
The Topos interface is entirely dedicated to highlight the core concepts at play: _scripts_ and _universes_. By understanding the interface, you will already understand quite a lot about Topos and how to play music with it. Make sure to learn the dedicated keybindings as well and you will fly! The Topos interface is entirely dedicated to highlight the core concepts at play: _scripts_ and _universes_. By understanding the interface, you will already understand quite a lot about Topos and how to play music with it. Make sure to learn the dedicated keybindings as well and you will fly!
@ -57,28 +86,28 @@ Every Topos session is composed of several small scripts. A set of scripts is ca
- **the global script** (${key_shortcut( - **the global script** (${key_shortcut(
"Ctrl + G" "Ctrl + G"
)}): _Evaluated for every clock pulse_. The central piece, acting as the conductor for all the other scripts. You can also jam directly from the global script to test your ideas before pushing them to a separate script. You can also access that script using the ${key_shortcut( )}): _Evaluated for every clock pulse_. The central piece, acting as the conductor for all the other scripts. You can also jam directly from the global script to test your ideas before pushing them to a separate script. You can also access that script using the ${key_shortcut(
"F10" "F10"
)} key. )} key.
- **the local scripts** (${key_shortcut( - **the local scripts** (${key_shortcut(
"Ctrl + L" "Ctrl + L"
)}): _Evaluated on demand_. Local scripts are used to store anything too complex to sit in the global script. It can be a musical process, a whole section of your composition, a complex controller that you've built for your hardware, etc... You can also switch to one of the local scripts by using the function keys (${key_shortcut( )}): _Evaluated on demand_. Local scripts are used to store anything too complex to sit in the global script. It can be a musical process, a whole section of your composition, a complex controller that you've built for your hardware, etc... You can also switch to one of the local scripts by using the function keys (${key_shortcut(
"F1" "F1"
)} to ${key_shortcut("F9")}). )} to ${key_shortcut("F9")}).
- **the init script** (${key_shortcut( - **the init script** (${key_shortcut(
"Ctrl + I" "Ctrl + I"
)}): _Evaluated on program load_. Used to set up the software the session to the desired state before playing (_bpm_, etc...). You can also access that script using the ${key_shortcut( )}): _Evaluated on program load_. Used to set up the software the session to the desired state before playing (_bpm_, etc...). You can also access that script using the ${key_shortcut(
"F11" "F11"
)} key. )} key.
- **the note file** (${key_shortcut( - **the note file** (${key_shortcut(
"Ctrl + N" "Ctrl + N"
)}): _Not evaluated_. Used to store your thoughts or commentaries about the session you are currently playing. It is nothing more than a scratchpad really! )}): _Not evaluated_. Used to store your thoughts or commentaries about the session you are currently playing. It is nothing more than a scratchpad really!
## Universes ## Universes
A set of files is called a _universe_. Topos can store several universes and switch immediately from one to another. You can switch between universes by pressing ${key_shortcut( A set of files is called a _universe_. Topos can store several universes and switch immediately from one to another. You can switch between universes by pressing ${key_shortcut(
"Ctrl + B" "Ctrl + B"
)}. You can also create a new universe by entering a name that has never been used before. _Universes_ are only referenced by their names. Once a universe is loaded, it is not possible to call any data/code from any other universe. )}. You can also create a new universe by entering a name that has never been used before. _Universes_ are only referenced by their names. Once a universe is loaded, it is not possible to call any data/code from any other universe.
Switching between universes will not stop the transport nor reset the clock. You are switching the context but time keeps flowing. This can be useful to prepare immediate transitions between songs and parts. Think of universes as an algorithmic set of music. All scripts in a given universe are aware about how many times they have been runned already. You can reset that value programatically. Switching between universes will not stop the transport nor reset the clock. You are switching the context but time keeps flowing. This can be useful to prepare immediate transitions between songs and parts. Think of universes as an algorithmic set of music. All scripts in a given universe are aware about how many times they have been runned already. You can reset that value programatically.
@ -92,7 +121,7 @@ Click on the Topos logo in the top bar. Your URL will change to something much l
- Topos will automatically fetch and switch to the universe that was sent to you. Your previous universe is still accessible if you switch to it, don't worry! - Topos will automatically fetch and switch to the universe that was sent to you. Your previous universe is still accessible if you switch to it, don't worry!
`; `;
const time: string = ` const time: string = `
# Time # Time
Time in Topos is flowing just like on a drum machine. Topos is counting bars, beats and pulses. The time can be **paused**, **resumed** and/or **resetted**. Pulses are flowing at a given **BPM** (_beats per minute_). There are three core values that you will often interact with in one form or another: Time in Topos is flowing just like on a drum machine. Topos is counting bars, beats and pulses. The time can be **paused**, **resumed** and/or **resetted**. Pulses are flowing at a given **BPM** (_beats per minute_). There are three core values that you will often interact with in one form or another:
@ -113,20 +142,20 @@ Let's study two very simple rhythmic functions, <icode>mod(n: ...number[])</icod
\`\`\`javascript \`\`\`javascript
mod(1) :: sound('kick').out() // A kickdrum played every beat mod(1) :: sound('kick').out() // A kickdrum played every beat
mod(.5) :: sound('kick').out() // A kickdrum played twice per beat mod(.5) :: sound('kick').out() // A kickdrum played twice per beat
mod(.25) :: sound('kick').out() // A kickdrum played four times every beat mod(.25) :: sound('kick').out() // A kickdrum played four times every beat
mod(1/3) :: sound('kick').out() // A funnier ratio! mod(1/3) :: sound('kick').out() // A funnier ratio!
mod(1, 2.5)::sound('hh').out() // A great high-hat pattern mod(1, 2.5)::sound('hh').out() // A great high-hat pattern
mod(1,3.25,2.5)::snd('hh').out() // A somewhat weirder pattern mod(1,3.25,2.5)::snd('hh').out() // A somewhat weirder pattern
\`\`\` \`\`\`
- <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.
\`\`\`javascript \`\`\`javascript
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
\`\`\` \`\`\`
## Rhythm generators ## Rhythm generators
@ -136,23 +165,23 @@ We included a bunch of popular rhythm generators in Topos such as the euclidian
- <icode>euclid(iterator: number, pulses: number, length: number, rotate: number): boolean</icode>: generates <icode>true</icode> or <icode>false</icode> values from an euclidian rhythm sequence. This algorithm is very popular in the electronic music making world. - <icode>euclid(iterator: number, pulses: number, length: number, rotate: number): boolean</icode>: generates <icode>true</icode> or <icode>false</icode> values from an euclidian rhythm sequence. This algorithm is very popular in the electronic music making world.
\`\`\`javascript \`\`\`javascript
mod(.5) && euclid($(1), 5, 8) && snd('kick').out() mod(.5) && euclid($(1), 5, 8) && snd('kick').out()
mod(.5) && euclid($(2), 2, 8) && snd('sd').out() mod(.5) && euclid($(2), 2, 8) && snd('sd').out()
\`\`\` \`\`\`
- <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.
\`\`\`javascript \`\`\`javascript
mod(.5) && bin($(1), 34) && snd('kick').out() mod(.5) && bin($(1), 34) && snd('kick').out()
mod(.5) && bin($(2), 48) && snd('sd').out() mod(.5) && bin($(2), 48) && snd('sd').out()
\`\`\` \`\`\`
If you don't find it spicy enough, you can add some more probabilities to your rhythms by taking advantage of the probability functions. See the functions documentation page to learn more about them. If you don't find it spicy enough, you can add some more probabilities to your rhythms by taking advantage of the probability functions. See the functions documentation page to learn more about them.
\`\`\`javascript \`\`\`javascript
prob(60)::mod(.5) && euclid($(1), 5, 8) && snd('kick').out() prob(60)::mod(.5) && euclid($(1), 5, 8) && snd('kick').out()
prob(60)::mod(.5) && euclid($(2), 3, 8) && snd('sd').out() prob(60)::mod(.5) && euclid($(2), 3, 8) && snd('sd').out()
prob(80)::mod(.5) && sound('hh').out() prob(80)::mod(.5) && sound('hh').out()
\`\`\` \`\`\`
## Larger time divisions ## Larger time divisions
@ -162,20 +191,20 @@ Now you know how to play some basic rhythmic music but you are a bit stuck in a
- <icode>div(n: number)</icode>: the <icode>div</icode> is a temporal switch. If the value <icode>2</icode> is given, the function will return <icode>true</icode> for two beats and <icode>false</icode> for two beats. There are multiple ways to use it effectively. You can pass an integer or a floating point number. Here are some examples. - <icode>div(n: number)</icode>: the <icode>div</icode> is a temporal switch. If the value <icode>2</icode> is given, the function will return <icode>true</icode> for two beats and <icode>false</icode> for two beats. There are multiple ways to use it effectively. You can pass an integer or a floating point number. Here are some examples.
\`\`\`javascript \`\`\`javascript
mod(1)::snd('kick').out(); // Playing on every beat mod(1)::snd('kick').out(); // Playing on every beat
div(2)::mod(.75)::snd('hat').out(); // Playing only every two beats div(2)::mod(.75)::snd('hat').out(); // Playing only every two beats
\`\`\` \`\`\`
You can also use it to think about **longer durations** spanning over multiple bars. You can also use it to think about **longer durations** spanning over multiple bars.
\`\`\`javascript \`\`\`javascript
// Rap God VS Lil Wild -- Adel Faure // Rap God VS Lil Wild -- Adel Faure
if (div(16)) { if (div(16)) {
// Playing this part for two bars // Playing this part for two bars
mod(1.5)::snd('kick').out() mod(1.5)::snd('kick').out()
mod(2)::snd('snare').out() mod(2)::snd('snare').out()
mod(.5)::snd('hh').out() mod(.5)::snd('hh').out()
} else { } else {
// Now adding some birds and tablas // Now adding some birds and tablas
mod(1.5)::snd('kick').out() mod(1.5)::snd('kick').out()
mod(2)::snd('snare').out() mod(2)::snd('snare').out()
@ -184,7 +213,7 @@ You can also use it to think about **longer durations** spanning over multiple b
mod(2.34)::snd('birds').n(irand(1,10)) mod(2.34)::snd('birds').n(irand(1,10))
.delay(0.5).delaytime(0.5).delayfb(0.25).out() .delay(0.5).delaytime(0.5).delayfb(0.25).out()
mod(.5)::snd('diphone').end(0.5).n(pick(1,2,3,4)).out() mod(.5)::snd('diphone').end(0.5).n(pick(1,2,3,4)).out()
} }
\`\`\` \`\`\`
And you can use it for other things inside a method parameter: And you can use it for other things inside a method parameter:
@ -242,19 +271,19 @@ Every script can access the current time by using the following functions:
These values are **extremely useful** to craft more complex syntax or to write musical scores. However, Topos is also offering more high-level sequencing functions to make it easier to play music. You can use the time functions as conditionals. The following example will play a pattern A for 2 bars and a pattern B for 2 bars: These values are **extremely useful** to craft more complex syntax or to write musical scores. However, Topos is also offering more high-level sequencing functions to make it easier to play music. You can use the time functions as conditionals. The following example will play a pattern A for 2 bars and a pattern B for 2 bars:
\`\`\`javascript \`\`\`javascript
if((bar() % 4) > 1) { if((bar() % 4) > 1) {
mod(1) && sound('kick').out() mod(1) && sound('kick').out()
rarely() && mod(.5) && sound('sd').out() rarely() && mod(.5) && sound('sd').out()
mod(.5) && sound('jvbass').freq(500).out() mod(.5) && sound('jvbass').freq(500).out()
} else { } else {
mod(.5) && sound('hh').out() mod(.5) && sound('hh').out()
mod(.75) && sound('cp').out() mod(.75) && sound('cp').out()
mod(.5) && sound('jvbass').freq(250).out() mod(.5) && sound('jvbass').freq(250).out()
} }
\`\`\` \`\`\`
`; `;
const midi: string = ` const midi: string = `
# MIDI # MIDI
You can use Topos to play MIDI thanks to the [WebMIDI API](https://developer.mozilla.org/en-US/docs/Web/API/Web_MIDI_API). You can currently send notes, control change, program change and so on. You can also send a MIDI Clock to your MIDI devices or favorite DAW. Note that Topos is also capable of playing MIDI using **Ziffers** which provides a better syntax for melodic expression. You can use Topos to play MIDI thanks to the [WebMIDI API](https://developer.mozilla.org/en-US/docs/Web/API/Web_MIDI_API). You can currently send notes, control change, program change and so on. You can also send a MIDI Clock to your MIDI devices or favorite DAW. Note that Topos is also capable of playing MIDI using **Ziffers** which provides a better syntax for melodic expression.
@ -263,11 +292,11 @@ You can use Topos to play MIDI thanks to the [WebMIDI API](https://developer.moz
- <icode>midi(note: number|object)</icode>: send a MIDI Note. Object can take parameters {note: number, channel: number, port: number|string, velocity: number}. - <icode>midi(note: number|object)</icode>: send a MIDI Note. Object can take parameters {note: number, channel: number, port: number|string, velocity: number}.
\`\`\`javascript \`\`\`javascript
bpm(80) // Setting a default BPM bpm(80) // Setting a default BPM
mod(.5) && midi(36 + seqbeat(0,12)).sustain(0.02).out() mod(.5) && midi(36 + seqbeat(0,12)).sustain(0.02).out()
mod(.25) && midi(pick(64, 76)).sustain(0.05).out() mod(.25) && midi(pick(64, 76)).sustain(0.05).out()
mod(.75) && midi(seqbeat(64, 67, 69)).sustain(0.05).out() mod(.75) && midi(seqbeat(64, 67, 69)).sustain(0.05).out()
sometimes() && mod(.25) && midi(seqbeat(64, 67, 69) + 24).sustain(0.05).out() sometimes() && mod(.25) && midi(seqbeat(64, 67, 69) + 24).sustain(0.05).out()
\`\`\` \`\`\`
## Note chaining ## Note chaining
@ -296,7 +325,7 @@ mod(0.25) && midi(60)
- <icode>program_change(program: number, channel: number)</icode>: send a MIDI Program Change. This function takes two arguments to specify the program and the channel (_e.g._ <icode>program_change(1, 1)</icode>). - <icode>program_change(program: number, channel: number)</icode>: send a MIDI Program Change. This function takes two arguments to specify the program and the channel (_e.g._ <icode>program_change(1, 1)</icode>).
\`\`\`javascript \`\`\`javascript
program_change(pick(1,2,3,4,5,6,7,8), 1) program_change(pick(1,2,3,4,5,6,7,8), 1)
\`\`\` \`\`\`
@ -309,19 +338,18 @@ mod(0.25) && midi(60)
- <icode>midi_clock()</icode>: send a MIDI Clock message. This function is used to synchronize Topos with other MIDI devices or DAWs. - <icode>midi_clock()</icode>: send a MIDI Clock message. This function is used to synchronize Topos with other MIDI devices or DAWs.
\`\`\`javascript \`\`\`javascript
mod(.25) && midi_clock() // Sending clock to MIDI device from the global buffer mod(.25) && midi_clock() // Sending clock to MIDI device from the global buffer
\`\`\` \`\`\`
## MIDI Output Selection ## MIDI Output Selection
- <icode>midi_outputs()</icode>: Prints a list of available MIDI outputs. You can then use any output name to select the MIDI output you wish to use. **Note:** this function will print to the console. You can open the console by pressing ${key_shortcut( - <icode>midi_outputs()</icode>: Prints a list of available MIDI outputs. You can then use any output name to select the MIDI output you wish to use. **Note:** this function will print to the console. You can open the console by pressing ${key_shortcut(
"Ctrl + Shift + I" "Ctrl + Shift + I"
)} in many web browsers. )} in many web browsers.
- <icode>midi_output(output_name: string)</icode>: Selects the MIDI output to use. You can use the <icode>midi_outputs()</icode> function to get a list of available MIDI outputs first. If the MIDI output is not available, the function will do nothing and keep on with the currently selected MIDI Port. - <icode>midi_output(output_name: string)</icode>: Selects the MIDI output to use. You can use the <icode>midi_outputs()</icode> function to get a list of available MIDI outputs first. If the MIDI output is not available, the function will do nothing and keep on with the currently selected MIDI Port.
`; `;
const sound: string = ` const sound: string = `
# Audio engine # Audio engine
The Topos audio engine is based on the [SuperDough](https://www.npmjs.com/package/superdough) audio backend, leveraging the [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API). The engine is capable of playing multiple samples, synths and effects at once. It is a very powerful and almost limitless tool to create complex sounds and textures. A set of default sounds are already provided but you can also load your own audio samples and synths! The Topos audio engine is based on the [SuperDough](https://www.npmjs.com/package/superdough) audio backend, leveraging the [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API). The engine is capable of playing multiple samples, synths and effects at once. It is a very powerful and almost limitless tool to create complex sounds and textures. A set of default sounds are already provided but you can also load your own audio samples and synths!
@ -544,16 +572,20 @@ mod(.5)::snd('pad').crush(divseq(2, 16, 8, 4)).clip(.5).out()
\`\`\` \`\`\`
`; `;
const samples: string = ` const samples: string = `
# Audio Samples # Audio Samples
Audio samples are dynamically loaded from the web. By default, Topos is providing some samples coming from the classic [Dirt-Samples](https://github.com/tidalcycles/Dirt-Samples) but also from the [Topos-Samples](https://github.com/Bubobubobubobubo/Topos-Samples) repository. You can contribute to the latter if you want to share your samples with the community! For each sample folder, we are indicating how many of them are available in parentheses.
- **sample_folder** (_how_many_)
## Available audio samples ## Available audio samples
${injectAvailableSamples()} ${injectAvailableSamples(application)}
`; `;
const patterns: string = ` const patterns: string = `
# Patterns # Patterns
## Simple patterns ## Simple patterns
@ -561,7 +593,7 @@ const patterns: string = `
- <icode>divseq(div: number, ...values:any[])</icode> - <icode>divseq(div: number, ...values:any[])</icode>
`; `;
const synths: string = ` const synths: string = `
# Synthesizers # Synthesizers
Topos comes with a small number of basic synthesizers. These synths are based on a basic [WebAudio](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API) design. For heavy synthesis duties, please use MIDI and speak to more complex instruments. Topos comes with a small number of basic synthesizers. These synths are based on a basic [WebAudio](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API) design. For heavy synthesis duties, please use MIDI and speak to more complex instruments.
@ -606,25 +638,25 @@ mod(.25) && snd('sine')
`; `;
const about: string = ` const about: string = `
# About Topos # About Topos
## The Topos Project ## The Topos Project
Topos is an experimental web based algorithmic sequencer programmed by **BuboBubo** ([Raphaël Forment](https://raphaelforment.fr) and **Amiika** ([Miika Alonen](https//github.com/amiika). It is written using [TypeScript](https://google.fr) and [Vite](https://google.fr). Many thanks to Felix Roos for making the [Superdough](https://www.npmjs.com/package/superdough) audio backend available for experimentation. This project is based on the [Monome Teletype](https://monome.org) by [Brian Crabtree](https://nnnnnnnn.co/) and [Kelli Cain](https://kellicain.com/). We hope to follow and honor the same spirit of sharing and experimentation. How much can the Teletype be extended while staying accessible and installation-free? Topos is an experimental web based algorithmic sequencer programmed by **BuboBubo** ([Raphaël Forment](https://raphaelforment.fr) and **Amiika** ([Miika Alonen](https//github.com/amiika). It is written using [TypeScript](https://google.fr) and [Vite](https://google.fr). Many thanks to Felix Roos for making the [Superdough](https://www.npmjs.com/package/superdough) audio backend available for experimentation. This project is based on the [Monome Teletype](https://monome.org) by [Brian Crabtree](https://nnnnnnnn.co/) and [Kelli Cain](https://kellicain.com/). We hope to follow and honor the same spirit of sharing and experimentation. How much can the Teletype be extended while staying accessible and installation-free?
## About Live Coding ## About Live Coding
**Amiika** and I are both very involved in the [TOPLAP](https://toplap.org) and [Algorave](https://algorave.com) scenes. We previously worked on the [Sardine](https://sardine.raphaelforment.fr) live coding environment for Python. **Amiika** has been working hard on its own algorithmic pattern language called [Ziffers](https://github.com/amiika/ziffers). A version of it is available in Topos! **Raphaël** is doing live coding with other folks from the [Cookie Collective](https://cookie.paris) and from the city of Lyon (France). **Amiika** and I are both very involved in the [TOPLAP](https://toplap.org) and [Algorave](https://algorave.com) scenes. We previously worked on the [Sardine](https://sardine.raphaelforment.fr) live coding environment for Python. **Amiika** has been working hard on its own algorithmic pattern language called [Ziffers](https://github.com/amiika/ziffers). A version of it is available in Topos! **Raphaël** is doing live coding with other folks from the [Cookie Collective](https://cookie.paris) and from the city of Lyon (France).
## Free and open-source software ## Free and open-source software
Topos is a free and open-source software distributed under [GPL-3.0](https://github.com/Bubobubobubobubo/Topos/blob/main/LICENSE) licence. We welcome all contributions and ideas. You can find the source code on [GitHub](https://github.com/Bubobubobubobubo/topos). You can also join us on [Discord](https://discord.gg/8Q2QV6Z6) to discuss about the project and live coding in general. Topos is a free and open-source software distributed under [GPL-3.0](https://github.com/Bubobubobubobubo/Topos/blob/main/LICENSE) licence. We welcome all contributions and ideas. You can find the source code on [GitHub](https://github.com/Bubobubobubobubo/topos). You can also join us on [Discord](https://discord.gg/8Q2QV6Z6) to discuss about the project and live coding in general.
**Have fun!** **Have fun!**
`; `;
const code: string = ` const code: string = `
# Code # Code
Topos is using the [JavaScript](https://en.wikipedia.org/wiki/JavaScript) syntax because it lives in a web browser where JS is the default programming language. It is also a language that you can learn to speak quite fast if you are already familiar with other programming languages. You are not going to write a lot of code anyway but familiarity with the language can help. Here are some good resources: Topos is using the [JavaScript](https://en.wikipedia.org/wiki/JavaScript) syntax because it lives in a web browser where JS is the default programming language. It is also a language that you can learn to speak quite fast if you are already familiar with other programming languages. You are not going to write a lot of code anyway but familiarity with the language can help. Here are some good resources:
@ -644,7 +676,7 @@ The code you enter in any of the scripts is evaluated in strict mode. This tells
- **about variables:** the state of your variables is not kept between iterations. If you write <icode>let a = 2</icode> and change the value later on, the value will be reset to <icode>2</icode> after each run! There are other ways to deal with variables and to share variables between scripts! Some variables like **iterators** can keep their state between iterations because they are saved **with the file itself**. - **about variables:** the state of your variables is not kept between iterations. If you write <icode>let a = 2</icode> and change the value later on, the value will be reset to <icode>2</icode> after each run! There are other ways to deal with variables and to share variables between scripts! Some variables like **iterators** can keep their state between iterations because they are saved **with the file itself**.
- **about errors and printing:** your code will crash! Don't worry, it will hopefully try to crash in the most gracious way possible. To check if your code is erroring, you will have to open the dev console with ${key_shortcut( - **about errors and printing:** your code will crash! Don't worry, it will hopefully try to crash in the most gracious way possible. To check if your code is erroring, you will have to open the dev console with ${key_shortcut(
"Ctrl + Shift + I" "Ctrl + Shift + I"
)}. You cannot directly use <icode>console.log('hello, world')</icode> in the interface. You will have to open the console as well to see your messages being printed there! )}. You cannot directly use <icode>console.log('hello, world')</icode> in the interface. You will have to open the console as well to see your messages being printed there!
- **about new syntax:** sometimes, we have taken liberties with the JavaScript syntax in order to make it easier/faster to write on stage. <icode>&&</icode> can also be written <icode>::</icode> or <icode>-></icode> because it is faster to type or better for the eyes! - **about new syntax:** sometimes, we have taken liberties with the JavaScript syntax in order to make it easier/faster to write on stage. <icode>&&</icode> can also be written <icode>::</icode> or <icode>-></icode> because it is faster to type or better for the eyes!
## About crashes and bugs ## About crashes and bugs
@ -652,7 +684,7 @@ The code you enter in any of the scripts is evaluated in strict mode. This tells
Things will crash, that's also part of the show. You will learn progressively to avoid mistakes and to write safer code. Do not hesitate to kill the page or to stop the transport if you feel overwhelmed by an algorithm blowing up. There are no safeties in place to save you. This is to ensure that you have all the available possible room to write bespoke code and experiment with your ideas through code. Things will crash, that's also part of the show. You will learn progressively to avoid mistakes and to write safer code. Do not hesitate to kill the page or to stop the transport if you feel overwhelmed by an algorithm blowing up. There are no safeties in place to save you. This is to ensure that you have all the available possible room to write bespoke code and experiment with your ideas through code.
`; `;
const functions: string = ` const functions: string = `
# Functions # Functions
## Global Shared Variables ## Global Shared Variables
@ -676,8 +708,6 @@ You will often need to use iterators and/or counters to index over data structur
- <icode>drunk_min(min: number)</icode>: sets the minimum value. - <icode>drunk_min(min: number)</icode>: sets the minimum value.
- <icode>drunk_wrap(wrap: boolean)</icode>: whether to wrap the drunk walk to 0 once the upper limit is reached or not. - <icode>drunk_wrap(wrap: boolean)</icode>: whether to wrap the drunk walk to 0 once the upper limit is reached or not.
## Scripts ## Scripts
You can control scripts programatically. This is the core concept of Topos after all! You can control scripts programatically. This is the core concept of Topos after all!
@ -707,34 +737,34 @@ Low Frequency Oscillators (_LFOs_) are an important piece in any digital audio w
- <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 \`\`\`javascript
mod(.25) && snd('cp').speed(1 + usine(0.25) * 2).out() mod(.25) && snd('cp').speed(1 + usine(0.25) * 2).out()
\`\`\` \`\`\`
- <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 \`\`\`javascript
mod(.25) && snd('cp').speed(1 + utriangle(0.25) * 2).out() mod(.25) && snd('cp').speed(1 + utriangle(0.25) * 2).out()
\`\`\` \`\`\`
- <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 \`\`\`javascript
mod(.25) && snd('cp').speed(1 + usaw(0.25) * 2).out() mod(.25) && snd('cp').speed(1 + usaw(0.25) * 2).out()
\`\`\` \`\`\`
- <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 \`\`\`javascript
mod(.25) && snd('cp').speed(1 + usquare(0.25, 0, 0.25) * 2).out() mod(.25) && snd('cp').speed(1 + usquare(0.25, 0, 0.25) * 2).out()
\`\`\` \`\`\`
- <icode>noise()</icode>: returns a random value between -1 and 1. - <icode>noise()</icode>: returns a random value between -1 and 1.
\`\`\`javascript \`\`\`javascript
mod(.25) && snd('cp').speed(1 + noise() * 2).out() mod(.25) && snd('cp').speed(1 + noise() * 2).out()
\`\`\` \`\`\`
## Probabilities ## Probabilities
@ -769,15 +799,13 @@ Chance operators returning a boolean value are also available:
- <icode>delay(ms: number, func: Function): void</icode>: Delays the execution of a function by a given number of milliseconds. - <icode>delay(ms: number, func: Function): void</icode>: Delays the execution of a function by a given number of milliseconds.
- <icode>delayr(ms: number, nb: number, func: Function): void</icode>: Delays the execution of a function by a given number of milliseconds, repeated a given number of times. - <icode>delayr(ms: number, nb: number, func: Function): void</icode>: Delays the execution of a function by a given number of milliseconds, repeated a given number of times.
`; `;
const reference: string = ` const reference: string = `
# Reference # Reference
`; `;
const shortcuts: string = ` const shortcuts: string = `
# Keybindings # Keybindings
Topos is made to be controlled entirely with a keyboard. It is recommanded to stop using the mouse as much as possible when you are _live coding_. Here is a list of the most important keybindings: Topos is made to be controlled entirely with a keyboard. It is recommanded to stop using the mouse as much as possible when you are _live coding_. Here is a list of the most important keybindings:
@ -793,10 +821,10 @@ Topos is made to be controlled entirely with a keyboard. It is recommanded to st
- Switch to a different universe: ${key_shortcut("Ctrl + B")}. - Switch to a different universe: ${key_shortcut("Ctrl + B")}.
- Switch to the global script: ${key_shortcut("Ctrl + G")} or ${key_shortcut( - Switch to the global script: ${key_shortcut("Ctrl + G")} or ${key_shortcut(
"F10" "F10"
)}. )}.
- Switch to the local scripts: ${key_shortcut("Ctrl + L")} or ${key_shortcut( - Switch to the local scripts: ${key_shortcut("Ctrl + L")} or ${key_shortcut(
"F11" "F11"
)}. )}.
- Switch to the init script: ${key_shortcut("Ctrl + L")}. - Switch to the init script: ${key_shortcut("Ctrl + L")}.
- Switch to the note file: ${key_shortcut("Ctrl + N")}. - Switch to the note file: ${key_shortcut("Ctrl + N")}.
- Switch to a local file: ${key_shortcut("F1")} to ${key_shortcut("F9")}. - Switch to a local file: ${key_shortcut("F1")} to ${key_shortcut("F9")}.
@ -807,14 +835,14 @@ Topos is made to be controlled entirely with a keyboard. It is recommanded to st
- Evaluate the current script: ${key_shortcut("Ctrl + Enter")}. - Evaluate the current script: ${key_shortcut("Ctrl + Enter")}.
- Evaluate a local script: ${key_shortcut("Ctrl + F1")} to ${key_shortcut( - Evaluate a local script: ${key_shortcut("Ctrl + F1")} to ${key_shortcut(
"Ctrl + F9" "Ctrl + F9"
)}. )}.
## Special ## Special
- Switch the editor to Vim Mode: ${key_shortcut("Ctrl + V")}. - Switch the editor to Vim Mode: ${key_shortcut("Ctrl + V")}.
`; `;
export const documentation = { return {
introduction: introduction, introduction: introduction,
interface: software_interface, interface: software_interface,
code: code, code: code,
@ -828,4 +856,5 @@ export const documentation = {
reference: reference, reference: reference,
shortcuts: shortcuts, shortcuts: shortcuts,
about: about, about: about,
};
}; };

View File

@ -14,10 +14,10 @@ import { indentWithTab } from "@codemirror/commands";
import { vim } from "@replit/codemirror-vim"; import { vim } from "@replit/codemirror-vim";
import { AppSettings, Universe } from "./AppSettings"; import { AppSettings, Universe } from "./AppSettings";
import { editorSetup } from "./EditorSetup"; import { editorSetup } from "./EditorSetup";
import { documentation } from "./Documentation"; import { documentation_factory } from "./Documentation";
import { EditorView } from "codemirror"; import { EditorView } from "codemirror";
import { Clock } from "./Clock"; import { Clock } from "./Clock";
import { UserAPI } from "./API"; import { loadSamples, UserAPI } from "./API";
import "./style.css"; import "./style.css";
import { import {
Universes, Universes,
@ -27,9 +27,6 @@ import {
} from "./AppSettings"; } from "./AppSettings";
import { tryEvaluate } from "./Evaluator"; import { tryEvaluate } from "./Evaluator";
type Documentation = { [key: string]: string };
const Docs: Documentation = documentation;
// Importing showdown and setting up the markdown converter // Importing showdown and setting up the markdown converter
import showdown from "showdown"; import showdown from "showdown";
showdown.setFlavor("github"); showdown.setFlavor("github");
@ -78,6 +75,7 @@ export class Editor {
userPlugins: Extension[] = []; userPlugins: Extension[] = [];
state: EditorState; state: EditorState;
api: UserAPI; api: UserAPI;
docs: { [key: string]: string } = {};
// Audio stuff // Audio stuff
audioContext: AudioContext; audioContext: AudioContext;
@ -236,6 +234,13 @@ export class Editor {
let dynamicPlugins = new Compartment(); let dynamicPlugins = new Compartment();
// ================================================================================
// Building the documentation
loadSamples().then(() => {
this.docs = documentation_factory(this);
});
// ================================================================================
// ================================================================================ // ================================================================================
// Application event listeners // Application event listeners
// ================================================================================ // ================================================================================
@ -662,10 +667,11 @@ export class Editor {
const converter = new showdown.Converter({ const converter = new showdown.Converter({
emoji: true, emoji: true,
moreStyling: true, moreStyling: true,
backslashEscapesHTMLTags: true,
extensions: [showdownHighlight({ auto_detection: true }), ...bindings], extensions: [showdownHighlight({ auto_detection: true }), ...bindings],
}); });
const converted_markdown = converter.makeHtml( const converted_markdown = converter.makeHtml(
Docs[this.currentDocumentationPane] this.docs[this.currentDocumentationPane]
); );
function wrapCodeWithPre(inputString: string): string { function wrapCodeWithPre(inputString: string): string {
let newString = inputString.replace(/<code>/g, "<pre><code>"); let newString = inputString.replace(/<code>/g, "<pre><code>");