Push current state of documentation refactoring
This commit is contained in:
11
index.html
11
index.html
@ -140,7 +140,16 @@
|
||||
<details class="space-y-2" open=true>
|
||||
<summary class="font-semibold lg:text-xl pb-1 pt-1 text-orange-300">Learning</summary>
|
||||
<div class="flex flex-col">
|
||||
<p rel="noopener noreferrer" id="docs_time" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Time/Rhythm</p>
|
||||
<details class="space-y-2" open=false>
|
||||
<summary class="ml-2 lg:text-xl pb-1 pt-1 text-white">Time</summary>
|
||||
<div class="flex flex-col">
|
||||
<p rel="noopener noreferrer" id="docs_time" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Dealing with time</p>
|
||||
<p rel="noopener noreferrer" id="docs_linear" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Time & Transport</p>
|
||||
<p rel="noopener noreferrer" id="docs_cyclic" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Time & Cycles</p>
|
||||
<p rel="noopener noreferrer" id="docs_longform" class="ml-8 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Time & Structure</p>
|
||||
|
||||
</div>
|
||||
</details>
|
||||
<p rel="noopener noreferrer" id="docs_sound" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Audio Engine</p>
|
||||
<p rel="noopener noreferrer" id="docs_samples" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Samples</p>
|
||||
<p rel="noopener noreferrer" id="docs_synths" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Synths</p>
|
||||
|
||||
70
src/API.ts
70
src/API.ts
@ -404,7 +404,7 @@ export class UserAPI {
|
||||
* { channel: 0, velocity: 100, duration: 0.5 }
|
||||
*/
|
||||
|
||||
const event = {note: value, velocity, channel, port} as MidiParams
|
||||
const event = { note: value, velocity, channel, port } as MidiParams
|
||||
|
||||
return new MidiEvent(event, this.app);
|
||||
};
|
||||
@ -1881,7 +1881,7 @@ export class UserAPI {
|
||||
// =============================================================
|
||||
|
||||
sound = (sound: string | string[] | null | undefined) => {
|
||||
if(sound) return new SoundEvent(sound, this.app);
|
||||
if (sound) return new SoundEvent(sound, this.app);
|
||||
else return new SkipEvent();
|
||||
};
|
||||
|
||||
@ -2041,4 +2041,70 @@ export class UserAPI {
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// =============================================================
|
||||
// Resolution
|
||||
// =============================================================
|
||||
//
|
||||
|
||||
public showGif = (options: any) => {
|
||||
/**
|
||||
* Displays a GIF on the webpage with various customizable options including rotation and fading.
|
||||
* @param {Object} options - The configuration object for displaying the GIF.
|
||||
* @param {string} options.gifUrl - The URL of the GIF to display.
|
||||
* @param {number} [options.posX=0] - The X-coordinate to place the GIF at.
|
||||
* @param {number} [options.posY=0] - The Y-coordinate to place the GIF at.
|
||||
* @param {number} [options.opacity=1] - The opacity level of the GIF.
|
||||
* @param {string} [options.size='auto'] - The size of the GIF (can be 'cover', 'contain', or specific dimensions).
|
||||
* @param {number} [options.zIndex=1000] - The z-index of the GIF.
|
||||
* @param {boolean} [options.center=false] - Whether to center the GIF in the window.
|
||||
* @param {number} [options.rotation=0] - The rotation angle of the GIF in degrees.
|
||||
* @param {string} [options.filter='none'] - The CSS filter function to apply for color alterations.
|
||||
* @param {number} [options.fadeDuration=0] - The duration of the fade-in effect in seconds.
|
||||
*/
|
||||
const {
|
||||
gifUrl,
|
||||
posX = 0,
|
||||
posY = 0,
|
||||
opacity = 1,
|
||||
size = 'auto',
|
||||
zIndex = 1000,
|
||||
center = false,
|
||||
rotation = 0,
|
||||
filter = 'none',
|
||||
fadeDuration = 0
|
||||
} = options;
|
||||
|
||||
const gifElement = document.createElement('img');
|
||||
gifElement.src = gifUrl;
|
||||
// @ts-ignore
|
||||
gifElement.style = `
|
||||
position: fixed;
|
||||
left: ${center ? '50%' : `${posX}px`};
|
||||
top: ${center ? '50%' : `${posY}px`};
|
||||
opacity: ${fadeDuration > 0 ? 0 : opacity}; /* Start with opacity 0 if fading in */
|
||||
z-index: ${zIndex};
|
||||
${size !== 'auto' ? `width: ${size}; height: ${size};` : ''}
|
||||
${center ? 'transform: translate(-50%, -50%) rotate(${rotation}deg);' : `transform: rotate(${rotation}deg);`}
|
||||
filter: ${filter};
|
||||
transition: opacity ${fadeDuration}s ease;
|
||||
`;
|
||||
gifElement.classList.add('top-level-gif'); // Add a class for easier removal
|
||||
|
||||
document.body.appendChild(gifElement);
|
||||
|
||||
// If fadeDuration is specified, fade the image in
|
||||
if (fadeDuration > 0) {
|
||||
setTimeout(() => {
|
||||
gifElement.style.opacity = opacity;
|
||||
}, 10); // Timeout to ensure the element is rendered before starting the transition
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public clearGif = () => {
|
||||
const gifs = document.querySelectorAll('.top-level-gif');
|
||||
gifs.forEach(gif => gif.remove());
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,9 @@ import { chaining } from "./documentation/chaining";
|
||||
import { software_interface } from "./documentation/interface";
|
||||
import { interaction } from "./documentation/interaction";
|
||||
import { time } from "./documentation/time";
|
||||
import { linear_time } from "./documentation/linear_time";
|
||||
import { cyclical_time } from "./documentation/cyclical_time";
|
||||
import { long_forms } from "./documentation/long_forms";
|
||||
import { midi } from "./documentation/midi";
|
||||
import { code } from "./documentation/code";
|
||||
import { about } from "./documentation/about";
|
||||
@ -46,9 +49,9 @@ export const makeExampleFactory = (application: Editor): Function => {
|
||||
return `
|
||||
<details ${open ? "open" : ""}>
|
||||
<summary >${description}
|
||||
<button class="py-1 align-top text-base rounded-lg pl-2 pr-2 hover:bg-green-700 bg-green-600 inline-block" onclick="app.api._playDocExample(app.api.codeExamples['${codeId}'])">▶️ Play</button>
|
||||
<button class="py-1 text-base rounded-lg pr-2 hover:bg-neutral-600 bg-neutral-500 inline-block pl-2" onclick="app.api._stopDocExample()">⏸️ Pause</button>
|
||||
<button class="py-1 text-base rounded-lg pr-2 hover:bg-neutral-900 bg-neutral-800 inline-block " onclick="navigator.clipboard.writeText(app.api.codeExamples['${codeId}'])">📎 Copy</button>
|
||||
<button class="ml-4 py-1 align-top text-base px-4 hover:bg-green-700 bg-emerald-600 inline-block" onclick="app.api._playDocExample(app.api.codeExamples['${codeId}'])">▶️ Play</button>
|
||||
<button class="py-1 text-base px-4 hover:bg-neutral-600 bg-neutral-500 inline-block " onclick="app.api._stopDocExample()">⏸️ Pause</button>
|
||||
<button class="py-1 text-base px-4 hover:bg-neutral-900 bg-neutral-800 inline-block " onclick="navigator.clipboard.writeText(app.api.codeExamples['${codeId}'])">📎 Copy</button>
|
||||
</summary>
|
||||
\`\`\`javascript
|
||||
${code}
|
||||
@ -69,6 +72,9 @@ export const documentation_factory = (application: Editor) => {
|
||||
interaction: interaction(application),
|
||||
code: code(application),
|
||||
time: time(application),
|
||||
linear: linear_time(application),
|
||||
cyclic: cyclical_time(application),
|
||||
longform: long_forms(application),
|
||||
sound: sound(application),
|
||||
samples: samples(application),
|
||||
synths: synths(application),
|
||||
|
||||
@ -64,10 +64,10 @@ export const buttonGroups = {
|
||||
//@ts-ignore
|
||||
export const createDocumentationStyle = (app: Editor) => {
|
||||
return {
|
||||
h1: "text-white lg:text-4xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-4 mb-4 bg-neutral-900 rounded-lg underline underline-offset-8 pt-2 pb-3 px-2",
|
||||
h2: "text-white lg:text-3xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-4 mb-4 bg-neutral-900 rounded-lg underline underline-offset-8 pt-2 pb-3 px-2",
|
||||
h3: "text-white lg:text-2xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-4 mb-4 bg-neutral-900 rounded-lg underline underline-offset-8 pt-2 pb-3 px-2 lg:mt-16",
|
||||
ul: "text-underline pl-6",
|
||||
h1: "text-white lg:text-4xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-4 mb-4 border-b-4 pt-4 pb-3 px-2",
|
||||
h2: "text-white lg:text-3xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-4 mb-4 border-b-2 pt-12 pb-3 px-2",
|
||||
h3: "text-white lg:text-2xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 border-l-2 border-b-2 lg:mb-4 mb-4 pb-2 px-2 lg:mt-16",
|
||||
ul: "text-underline ml-12",
|
||||
li: "list-disc lg:text-2xl text-base text-white lg:mx-4 mx-2 my-4 my-2 leading-normal",
|
||||
p: "lg:text-2xl text-base text-white lg:mx-6 mx-2 my-4 leading-normal",
|
||||
warning:
|
||||
@ -79,7 +79,7 @@ export const createDocumentationStyle = (app: Editor) => {
|
||||
ic: "lg:my-1 my-1 lg:text-xl sm:text-xs text-white font-mono bg-neutral-600",
|
||||
blockquote: "text-neutral-200 border-l-4 border-neutral-500 pl-4 my-4 mx-4",
|
||||
details:
|
||||
"lg:mx-12 py-2 px-6 lg:text-2xl text-white rounded-lg bg-neutral-600",
|
||||
"lg:mx-20 py-2 px-6 lg:text-2xl text-white border-l-8 box-border bg-neutral-900",
|
||||
summary: "font-semibold text-xl",
|
||||
table:
|
||||
"justify-center lg:my-12 my-2 lg:mx-12 mx-2 lg:text-2xl text-base w-full text-left text-white border-collapse",
|
||||
@ -88,6 +88,7 @@ export const createDocumentationStyle = (app: Editor) => {
|
||||
th: "",
|
||||
td: "",
|
||||
tr: "",
|
||||
box: "border bg-red-500",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -460,6 +460,9 @@ export const installInterfaceLogic = (app: Editor) => {
|
||||
"interaction",
|
||||
"code",
|
||||
"time",
|
||||
"linear",
|
||||
"cyclic",
|
||||
"longform",
|
||||
"sound",
|
||||
"samples",
|
||||
"synths",
|
||||
|
||||
@ -6,29 +6,29 @@ export const code = (application: Editor): string => {
|
||||
return `
|
||||
# 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 scripts are using the [JavaScript](https://en.wikipedia.org/wiki/JavaScript) syntax. This is the language used to write pretty much anything in a web browser. JavaScript is easy to learn, and even faster to learn if you are already familiar with other programming languages. Here are some good resources if you want to learn more about it:
|
||||
|
||||
- [MDN (Mozilla Web Docs)](https://developer.mozilla.org/): it covers pretty much anything and is considered to be a reliable source to learn how the web currently works. We use it quite a lot to develop Topos.
|
||||
- [MDN (Mozilla Web Docs)](https://developer.mozilla.org/): it covers pretty much anything and is considered to be a reliable source to learn how the web currently works. Any web developer knows about it.
|
||||
|
||||
- [Learn JS in Y Minutes](https://learnxinyminutes.com/docs/javascript/): a good tour of the language. Can be useful as a refresher.
|
||||
|
||||
- [The Modern JavaScript Tutorial](https://javascript.info/): another well known source to learn the language.
|
||||
|
||||
You **do not need to have any prior knowledge of programming** to use Topos. It can also be used as a **valuable resource** to learn some basic programming.
|
||||
**You do not need to have any prior knowledge of programming** to use Topos**.
|
||||
|
||||
## How is the code evaluated?
|
||||
# How is the code evaluated?
|
||||
|
||||
The code you enter in any of the scripts is evaluated in strict mode. This tells your browser that the code you run can be optimized quite agressively. We need this because by default, **the global script is evaluated 48 times per beat**. It also means that you can crash at the speed of light :smile:. The local and initialisation scripts are evaluated on demand, one run at a time. There are some things to keep in mind:
|
||||
The code you enter in any of the scripts is evaluated in strict mode. This tells your browser that the code you run can be optimized quite agressively. We need this because by default, **the global script is evaluated 48 times per beat**. It also means that you can crash at the speed of light :smile:. There are some things to keep in mind:
|
||||
|
||||
- **about variables:** the state of your variables is not kept between iterations. If you write <ic>let a = 2</ic> and change the value later on, the value will be reset to <ic>2</ic> 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 variables:** the state of your variables is not kept between iterations. If you write <ic>let a = 2</ic> and remove that value from your script, **it will crash**! Variable and state is not preserved between each run of the script. There are other ways to deal with variables and to share variables between scripts! Some variables like **iterators** can keep their state between iterations because they are saved **with the file itself**. There is also **global variables**.
|
||||
- **about errors and printing:** your code will crash! Don't worry, we do our best to make it crash in the most gracious way possible. Most errors are caught and displayed in the interface. For weirder bugs, open the dev console with ${key_shortcut(
|
||||
"Ctrl + Shift + I"
|
||||
)}. You cannot directly use <ic>console.log('hello, world')</ic> in the interface but you can use <ic>log(message)</ic> to print a one line message. You will have to open the console as well to see your messages being printed there!
|
||||
- **about new syntax:** sometimes, we have taken liberties with the JavaScript syntax in order to make it easier/faster to write on stage. <ic>&&</ic> can also be written <ic>::</ic> or <ic>-></ic> because it is faster to type or better for the eyes!
|
||||
- **about new syntax:** sometimes, we had some fun with JavaScript's syntax in order to make it easier/faster to write on stage. <ic>&&</ic> can also be written <ic>::</ic> or <ic>-></ic> because it is faster to type or better for the eyes!
|
||||
|
||||
## Common idioms
|
||||
# Common idioms
|
||||
|
||||
There are some techniques that Topos players are using to keep their JavaScript short and tidy. Don't try to write the shortest possible code but use shortcuts when it makes sense. It's sometimes very comforting to take time to write utilities and scripts that you will often reuse. Take a look at the following examples:
|
||||
There are some techniques to keep code short and tidy. Don't try to write the shortest possible code! Use shortcuts when it makes sense. Take a look at the following examples:
|
||||
|
||||
${makeExample(
|
||||
"Shortening your if conditions",
|
||||
@ -66,16 +66,17 @@ beat(4) ? snd('kick').out() : beat(2) :: snd('snare').out()
|
||||
false
|
||||
)}
|
||||
|
||||
## About crashes and bugs
|
||||
# About crashes and bugs
|
||||
|
||||
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! It's 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 is no safeguard to stop you from doing most things. This is to ensure that you have all the available possible room to write bespoke code and experiment with your ideas through code.
|
||||
|
||||
${makeExample(
|
||||
"This example will crash! Who cares?",
|
||||
`// This is crashing. Open your console!
|
||||
`
|
||||
// This is crashing. See? No harm!
|
||||
qjldfqsdklqsjdlkqjsdlqkjdlksjd
|
||||
`,
|
||||
false
|
||||
true
|
||||
)}
|
||||
|
||||
`;
|
||||
|
||||
204
src/documentation/cyclical_time.ts
Normal file
204
src/documentation/cyclical_time.ts
Normal file
@ -0,0 +1,204 @@
|
||||
import { type Editor } from "../main";
|
||||
import { makeExampleFactory } from "../Documentation";
|
||||
|
||||
export const cyclical_time = (app: Editor): string => {
|
||||
// @ts-ignore
|
||||
let makeExample = makeExampleFactory(app);
|
||||
return `
|
||||
# Cyclical time
|
||||
|
||||
Time as a cycle. A cycle can be quite long (a few bars) or very short (a few pulses). Cyclical time is extremely interesting for _live coders_ since it allows you to control a process that will eventually repeat. If your time constructs are repeating, you are able to hear them again and again. Since you can react and alter the code to change the loops, you become part of a complex feedback system between the computer and yourself.
|
||||
|
||||
## Simple rhythms
|
||||
|
||||
- <ic>beat(n: number | number[] = 1, offset: number = 1)</ic>: return true every _n_ beats.
|
||||
- <ic>number</ic>: if <ic>number = 1</ic>, the function will return <ic>true</ic> every beat. Lists can be used too.
|
||||
- <ic>offset</ic>: offset (in beats) to apply. An offset of <ic>0.5</ic> will return true against the beat.
|
||||
|
||||
${makeExample(
|
||||
"Using different mod values",
|
||||
`
|
||||
// This code is alternating between different mod values
|
||||
beat([1,1/2,1/4,1/8].beat(2)) :: sound('hat').n(0).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Some sort of ringtone",
|
||||
`
|
||||
// Blip generator :)
|
||||
let blip = (freq) => {
|
||||
return sound('wt_piano')
|
||||
.gain(1)
|
||||
.sustain(0.1)
|
||||
.freq(freq)
|
||||
.cutoff(1500)
|
||||
.lpadsr(4, 0, .25, 0, 0)
|
||||
};
|
||||
beat(1) :: blip(200).pan(r(0,1)).vib(0.5).vibmod(2).out();
|
||||
beat(1/3) :: blip(400).pan(r(0,1)).out();
|
||||
flip(3) :: beat(1/6) :: blip(800).pan(r(0,1)).out();
|
||||
beat([1,0.75].beat(2)) :: blip([50, 100].beat(2)).pan(r(0,1)).out();
|
||||
`,
|
||||
false
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Beat can match multiple values",
|
||||
`
|
||||
beat([.5, 1.25])::sound('hat').out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
|
||||
- <ic>pulse(n: number | number[] = 1, offset: number = 1)</ic>: return true every _n_ pulses. A pulse is the tiniest possible rhythmic value.
|
||||
- <ic>number</ic>: if <ic>number = 1</ic>, the function will return <ic>true</ic> every pulse. Lists can be used too.
|
||||
- <ic>offset</ic>: offset (in pulses) to apply.
|
||||
|
||||
|
||||
${makeExample(
|
||||
"Intriguing rhythms",
|
||||
`
|
||||
pulse([24, 16])::sound('hat').ad(0, .02).out()
|
||||
pulse([48, [36,24].dur(4, 1)])::sound('fhardkick').ad(0, .1).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
${makeExample(
|
||||
"pulse is the OG rhythmic function in Topos",
|
||||
`
|
||||
pulse([48, 24, 16].beat(4)) :: sound('linnhats').out()
|
||||
beat(1)::snd(['bd', '808oh'].beat(1)).out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
|
||||
|
||||
- <ic>bar(n: number | number[] = 1, offset: number = 1)</ic>: return true every _n_ bars.
|
||||
- <ic>number</ic>: if <ic>number = 1</ic>, the function will return <ic>true</ic> every bar. Lists can be used too.
|
||||
- <ic>offset</ic>: offset (in bars) to apply.
|
||||
|
||||
${makeExample(
|
||||
"Four beats per bar: proof",
|
||||
`
|
||||
bar(1)::sound('kick').out()
|
||||
beat(1)::sound('hat').speed(2).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
|
||||
${makeExample(
|
||||
"Offsetting beat and bar",
|
||||
`
|
||||
bar(1)::sound('kick').out()
|
||||
beat(1)::sound('hat').speed(2).out()
|
||||
beat(1, 0.5)::sound('hat').speed(4).out()
|
||||
bar(1, 0.5)::sound('sn').out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
|
||||
- <ic>onbeat(...n: number[])</ic>: The <ic>onbeat</ic> function allows you to lock on to a specific beat from the clock to execute code. It can accept multiple arguments. It's usage is very straightforward and not hard to understand. You can pass either integers or floating point numbers. By default, topos is using a <ic>4/4</ic> bar meaning that you can target any of these beats (or in-between) with this function.
|
||||
|
||||
${makeExample(
|
||||
"Some simple yet detailed rhythms",
|
||||
`
|
||||
onbeat(1,2,3,4)::snd('kick').out() // Bassdrum on each beat
|
||||
onbeat(2,4)::snd('snare').n([8,4].beat(4)).out() // Snare on acccentuated beats
|
||||
onbeat(1.5,2.5,3.5, 3.75)::snd('hat').gain(r(0.9,1.1)).out() // Cool high-hats
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
## Cyclical rhythm generators
|
||||
|
||||
We included a bunch of popular rhythm generators in Topos such as the euclidian rhythms algorithms or the one to generate rhythms based on a binary sequence. They all work using _iterators_ that you will gradually learn to use for iterating over lists. Note that they are levaraging <ic>mod(...n:number[])</ic> that you just learned about!
|
||||
|
||||
|
||||
- <ic>rhythm(divisor: number, pulses: number, length: number, rotate: number): boolean</ic>: generates <ic>true</ic> or <ic>false</ic> values from an euclidian rhythm sequence. This is another version of <ic>euclid</ic> that does not take an iterator.
|
||||
${makeExample(
|
||||
"rhythm is a beginner friendly rhythmic function!",
|
||||
`
|
||||
rhythm(.5, 4, 8)::sound('sine')
|
||||
.fmi(2)
|
||||
.room(0.5).size(8)
|
||||
.freq(250).ad(0, .2).out()
|
||||
rhythm(.5, 7, 8)::sound('sine')
|
||||
.freq(125).ad(0, .2).out()
|
||||
rhythm(.5, 3, 8)::sound('sine').freq(500).ad(0, .5).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
|
||||
- <ic>oneuclid(pulses: number, length: number, rotate: number): boolean</ic>: generates <ic>true</ic> or <ic>false</ic> values from an euclidian rhythm sequence. This is another version of <ic>euclid</ic> that does not take an iterator.
|
||||
${makeExample(
|
||||
"Using oneuclid to create a rhythm without iterators",
|
||||
`
|
||||
// Change speed using bpm
|
||||
bpm(250)
|
||||
oneuclid(5, 9) :: snd('kick').out()
|
||||
oneuclid(7,16) :: snd('east').end(0.5).n(irand(3,5)).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
- <ic>bin(iterator: number, n: number): boolean</ic>: a binary rhythm generator. It transforms the given number into its binary representation (_e.g_ <ic>34</ic> becomes <ic>100010</ic>). It then returns a boolean value based on the iterator in order to generate a rhythm.
|
||||
- <ic>binrhythm(divisor: number, n: number): boolean: boolean</ic>: iterator-less version of the binary rhythm generator.
|
||||
|
||||
${makeExample(
|
||||
"Change the integers for a surprise rhythm!",
|
||||
`
|
||||
bpm(135);
|
||||
beat(.5) && bin($(1), 12) && snd('kick').n([4,9].beat(1.5)).out()
|
||||
beat(.5) && bin($(2), 34) && snd('snare').n([3,5].beat(1)).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"binrhythm for fast cool binary rhythms!",
|
||||
`
|
||||
let a = 0;
|
||||
a = beat(4) ? irand(1,20) : a;
|
||||
binrhythm(.5, 6) && snd(['kick', 'snare'].beat(0.5)).n(11).out()
|
||||
binrhythm([.5, .25].beat(1), 30) && snd('wt_granular').n(a)
|
||||
.cutoff(800).lpadsr(4, 0, 0.125, 0.5, 0.25)
|
||||
.adsr(0, r(.1, .4), 0, 0).freq([50, 60, 72].beat(4))
|
||||
.room(1).size(1).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Submarine jungle music",
|
||||
`
|
||||
bpm(145);
|
||||
beat(.5) && bin($(1), 911) && snd('ST69').n([2,3,4].beat())
|
||||
.delay(0.125).delayt(0.25).end(0.25).speed(1/3)
|
||||
.room(1).size(1).out()
|
||||
beat(.5) && sound('amencutup').n(irand(2,7)).shape(0.3).out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
|
||||
If you don't find it spicy enough, you can add some more probabilities to your rhythms by taking advantage of the probability functions. See the functions documentation page to learn more about them.
|
||||
|
||||
${makeExample(
|
||||
"Probablistic drums in one line!",
|
||||
`
|
||||
prob(60)::beat(.5) && euclid($(1), 5, 8) && snd('kick').out()
|
||||
prob(60)::beat(.5) && euclid($(2), 3, 8) && snd('mash')
|
||||
.n([1,2,3].beat(1))
|
||||
.pan(usine(1/4)).out()
|
||||
prob(80)::beat(.5) && sound(['hh', 'hat'].pick()).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
|
||||
|
||||
`;
|
||||
};
|
||||
@ -8,14 +8,14 @@ export const software_interface = (application: Editor): string => {
|
||||
return `
|
||||
# Interface
|
||||
|
||||
The Topos interface is modeled around 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 designed on a simple concept: _scripts_ and _universes_. By understanding how the interface works, you will already understand quite a lot. Make sure to learn the dedicated keybindings as well. They will give you extra powers!
|
||||
|
||||
<object type="image/svg+xml" data=${topos_arch} style="width: 100%; height: auto; background-color: transparent"></object>
|
||||
|
||||
|
||||
## Scripts
|
||||
# Scripts
|
||||
|
||||
Every Topos session is composed of several scripts: **local**, **global** and **init** scripts. Considered as a whole, these scripts form a structure called a "_universe_". A "universe" is a set of functions, a song, a sketch from an improvisation, or whatever you would like to do with **Topos**. All the scripts are written using the JavaScript programming language. They describe a musical or algorithmic process. You can call them anytime.
|
||||
Every Topos session is composed of **local**, **global** and **init** scripts. These scripts form a structure called a "_universe_". The scripts can describe whatever you want: songs, sketches, small tools, or whatever. All the scripts are written using the JavaScript programming language. They describe a musical or algorithmic process. You can call them anytime.
|
||||
|
||||
- **the global script** (${key_shortcut(
|
||||
"Ctrl + G"
|
||||
@ -38,27 +38,26 @@ Every Topos session is composed of several scripts: **local**, **global** and **
|
||||
|
||||
|
||||
${makeExample(
|
||||
"Calling scripts to form a musical piece",
|
||||
`
|
||||
// Write your code in multiple scripts.
|
||||
// Use all the code buffers!
|
||||
beat(1) :: script(1)
|
||||
flip(4) :: beat(.5) :: script(2)
|
||||
"Calling scripts to form a musical piece",
|
||||
`
|
||||
beat(1) :: script(1) // Calling local script n°1
|
||||
flip(4) :: beat(.5) :: script(2) // Calling script n°2
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Script execution can become musical too!",
|
||||
`
|
||||
// You can play your scripts... algorithmically.
|
||||
beat(1) :: script([1,3,5].pick())
|
||||
flip(4) :: beat([.5, .25].beat(16)) :: script([5,6,7,8].beat())
|
||||
"Script execution can become musical too!",
|
||||
`
|
||||
// Use algorithms to pick a script.
|
||||
beat(1) :: script([1, 3, 5].pick())
|
||||
flip(4) :: beat([.5, .25].beat(16)) :: script(
|
||||
[5, 6, 7, 8].beat())
|
||||
`,
|
||||
false
|
||||
)}
|
||||
false
|
||||
)}
|
||||
|
||||
## Navigating the interface
|
||||
### Navigating the interface
|
||||
|
||||
The interface is centered around the manipulation of scripts. Take a look at the left bar:
|
||||
- **pencil icon:** notes. Used to take project notes about your "_universe_".
|
||||
@ -66,35 +65,7 @@ The interface is centered around the manipulation of scripts. Take a look at the
|
||||
- **text with note**: global script, it acts as the **conductor** for your piece.
|
||||
- **folder icon**: local scripts (from 1 to 9).
|
||||
|
||||
|
||||
## Universes
|
||||
|
||||
<object type="image/svg+xml" data=${many_universes} style="width: 100%; height: auto; background-color: transparent"></object>
|
||||
|
||||
|
||||
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"
|
||||
)}. 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.
|
||||
|
||||
You can clear the current universe by pressing the flame button on the top right corner of the interface. This will clear all the scripts and the note file. **Note:** there is no shortcut for clearing a universe. We do not want to loose your work by mistake!
|
||||
|
||||
There are some useful functions to help you manage your universes:
|
||||
|
||||
- <ic>copy_universe(from: string, to: string)</ic>: copy the content of a universe to another. This is useful to create a backup of your work.
|
||||
- <ic>delete_universe(name: string)</ic>: delete a universe. Warning: this is irreversible!
|
||||
|
||||
# Sharing your work
|
||||
|
||||
**Click on the Topos logo in the top bar**. Your URL will change to something much longer and complex. The same URL will be copied to your clipboard. Send this link to your friends to share the universe you are currently working on with them.
|
||||
|
||||
- The imported universe will always get a randomly generated name such as: <ic>random_silly_llama</ic>.
|
||||
- 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!
|
||||
|
||||
**Note:** links are currently super long and unsharable! Sorry about that, minifying takes a server and we don't have one yet. We will fix that soon. In the meantime, you can use a service like [tinyurl](https://tinyurl.com/) to shorten your links.
|
||||
|
||||
## More
|
||||
### Managing scripts programatically
|
||||
|
||||
There are some useful functions to help you manage your scripts:
|
||||
|
||||
@ -102,5 +73,30 @@ There are some useful functions to help you manage your scripts:
|
||||
- <ic>delete_script(index: number)</ic>: clear the content of a script. Warning: this is irreversible!
|
||||
|
||||
|
||||
|
||||
|
||||
# Universes
|
||||
|
||||
<object type="image/svg+xml" data=${many_universes} style="width: 100%; height: auto; background-color: transparent"></object>
|
||||
|
||||
|
||||
A set of files is called a _universe_. You can switch between universes immediately immediately by pressing ${key_shortcut(
|
||||
"Ctrl + B"
|
||||
)}. You can also create a new universe by entering a name. Load a universe by typing its name. Once a universe is loaded, it is not possible to call any data/code from any other universe. Switching between universes does not stop the transport nor reset the clock. The context switches but time keeps flowing. This can be useful for transitioning between songs / parts.
|
||||
|
||||
There are some useful functions to help you manage your universes:
|
||||
|
||||
- <ic>copy_universe(from: string, to: string)</ic>: copy the content of a universe to another.
|
||||
- <ic>delete_universe(name: string)</ic>: delete a universe. Warning: this is irreversible!
|
||||
|
||||
# Sharing your work
|
||||
|
||||
**Click the share button**. The URL of the website will change to something much longer. This URL will automatically be copied to your clipboard. Send this link to your friends to share the universe you are currently working on with them. You can use a service like [tinyurl](https://tinyurl.com/) to shorten your links.
|
||||
|
||||
|
||||
- The imported universe will always get a randomly generated name such as: <ic>random_silly_llama</ic>.
|
||||
- Topos will automatically fetch and switch to the universe that was sent to you.
|
||||
|
||||
|
||||
`;
|
||||
};
|
||||
|
||||
@ -12,46 +12,54 @@ Welcome to the **Topos** documentation. You can jump here anytime by pressing ${
|
||||
)}. Press again to make the documentation disappear. Contributions are much appreciated! The documentation [lives here](https://github.com/Bubobubobubobubo/topos/tree/main/src/documentation).
|
||||
|
||||
${makeExample(
|
||||
"Welcome! Eval to get started",
|
||||
examples[Math.floor(Math.random() * examples.length)],
|
||||
true
|
||||
)}
|
||||
"Welcome! Eval to get started",
|
||||
examples[Math.floor(Math.random() * examples.length)],
|
||||
true
|
||||
)}
|
||||
|
||||
## What is Topos?
|
||||
# What is Topos?
|
||||
|
||||
Topos is an _algorithmic_ sequencer. Topos is also a _live coding_ environment. To sum it up, think: "making music in real time through code". Code used as an expressive medium for musical improvisation! Topos uses small algorithms to represent musical sequences and processes.
|
||||
|
||||
The syntax is voluntarily terse. The software is designed to encourage the user to write complex musical expressions very fast while on stage. The _live coder_ strives for the constant interaction with algorithms and sound during a musical performance. Topos is aiming to be a digital playground for live algorithmic music.
|
||||
Topos is an _algorithmic_ sequencer. Topos is also a _live coding_ environment. To sum it up, think: "_making music in real time through code_". Code used as an expressive medium for musical improvisation! Topos uses small algorithms to represent musical sequences and processes.
|
||||
|
||||
${makeExample(
|
||||
"Small algorithms for direct musical expression",
|
||||
`
|
||||
beat(1) :: sound(['kick', 'hat', 'snare', 'hat'].beat(1)).out()
|
||||
beat(.5) :: sound('jvbass').note(35 + [0,12].beat()).out()
|
||||
beat([0.5, 0.25].beat(1)) :: sound('east')
|
||||
.room(.9).speed(flip(4) ? 1 : 0.95).size(0.9).o(2).n($(1)).out()`,
|
||||
false
|
||||
)}
|
||||
"Small algorithms for direct musical expression",
|
||||
`
|
||||
rhythm(.5, 4, 8) :: sound('drum').out()
|
||||
rhythm(.25, [5, 7].beat(2), 8) :: sound(['hc', 'fikea', 'hat'].pick(1))
|
||||
.lpf([500, 4000+usine(1/2)*2000]).pan(r(0, 1)).ad(0, [1, .5])
|
||||
.db(-ir(1,8)).speed([1,[0.5, 2].pick()]).room(0.5).size(3).o(4).out()
|
||||
beat([2,0.5].dur(13.5, 0.5))::snd('fsoftsnare')
|
||||
.n(0).speed([1, 0.5]).o(4).out()`,
|
||||
false
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Computer music should be immediate and intuitive",
|
||||
`beat(.5)::snd('sine')
|
||||
.delay(0.5).delayt(0.25).delayfb(0.7)
|
||||
.room(0.8).size(0.8)
|
||||
.freq(mouseX()).out()`,
|
||||
false
|
||||
)}
|
||||
"Computer music should be immediate and intuitive",
|
||||
`
|
||||
let chord_prog = [0, 0, 5].bar() // Chord progression
|
||||
beat(.25)::snd('sine')
|
||||
.note(chord_prog + [60, 64, 67, 71].mouseX()
|
||||
+ [-12,0,12].beat(0.25)) // Notes
|
||||
.fmi([1, 1.5, 2, 4].beat()) // FM synthesis
|
||||
.ad(0, r(0.1, .25)) // Envelope
|
||||
.lpf(500 + usine(1/4)*1500) // Filter Envelope
|
||||
.lpad(4, 0, .125)
|
||||
.delay(0.5).delayt(0.25).delayfb(0.7) // Delay
|
||||
.room(0.5).size(8) // Reverb
|
||||
.out()`,
|
||||
false
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Making the web less dreadful, one beep at at time",
|
||||
`
|
||||
"Making the web less dreadful, one beep at at time",
|
||||
`
|
||||
beat(.5) :: sound('sid').n($(2))
|
||||
.room(1).speed([1,2].pick()).out()
|
||||
beat(.25) :: sound('sid').note(
|
||||
[34, 36, 41].beat(.25) + [[0,-24].pick(),12].beat())
|
||||
.room(0.9).size(0.9).n(4).out()`,
|
||||
false
|
||||
)}
|
||||
false
|
||||
)}
|
||||
|
||||
Topos is deeply inspired by the [Monome Teletype](https://monome.org/). The Teletype is/was an open source hardware module for Eurorack synthesizers. While the Teletype was initially born as an hardware module, Topos aims to be a web-browser based cousin of it! It is a sequencer, a scriptable interface, a companion for algorithmic music-making. Topos wishes to fullfill the same goal as the Teletype, keeping the same spirit alive on the web. It is free, open-source, and made to be shared and used by everyone. Learn more about live coding on [livecoding.fr](https://livecoding.fr).
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ export const shortcuts = (app: Editor): string => {
|
||||
|
||||
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:
|
||||
|
||||
## Transport
|
||||
### Transport
|
||||
|
||||
| Shortcut | Key | Description |
|
||||
|----------|-------|------------------------------------------------------------|
|
||||
@ -20,7 +20,7 @@ Topos is made to be controlled entirely with a keyboard. It is recommanded to st
|
||||
"Ctrl + S"
|
||||
)}|Stop and rewind audio playback|
|
||||
|
||||
## Moving in the interface
|
||||
### Moving in the interface
|
||||
|
||||
| Shortcut | Key | Description |
|
||||
|----------|-------|------------------------------------------------------------|
|
||||
@ -38,7 +38,7 @@ Topos is made to be controlled entirely with a keyboard. It is recommanded to st
|
||||
)}|Switch to a specific local script|
|
||||
|Documentation|${key_shortcut("Ctrl + D")}|Open the documentation|
|
||||
|
||||
## Evaluating code
|
||||
### Evaluating code
|
||||
|
||||
| Shortcut | Key | Description |
|
||||
|----------|-------|------------------------------------------------------------|
|
||||
@ -50,13 +50,13 @@ Topos is made to be controlled entirely with a keyboard. It is recommanded to st
|
||||
"Ctrl + Shift + Enter"
|
||||
)}|Force evaluation of the current script|
|
||||
|
||||
## Special
|
||||
### Special
|
||||
|
||||
| Shortcut | Key | Description |
|
||||
|----------|-------|------------------------------------------------------------|
|
||||
|Vim Mode|${key_shortcut("Ctrl + V")}| Switch between Vim and Normal Mode|
|
||||
|
||||
## Keyboard Fill
|
||||
# Keyboard Fill
|
||||
|
||||
By pressing the ${key_shortcut(
|
||||
"Alt"
|
||||
@ -65,12 +65,12 @@ By pressing the ${key_shortcut(
|
||||
)} when playing this example:
|
||||
|
||||
${makeExample(
|
||||
"Claping twice as fast with fill",
|
||||
`
|
||||
"Claping twice as fast with fill",
|
||||
`
|
||||
beat(fill() ? 1/4 : 1/2)::sound('cp').out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
`;
|
||||
};
|
||||
|
||||
191
src/documentation/linear_time.ts
Normal file
191
src/documentation/linear_time.ts
Normal file
@ -0,0 +1,191 @@
|
||||
import { type Editor } from "../main";
|
||||
import { makeExampleFactory } from "../Documentation";
|
||||
import pulses from "./pulses.svg";
|
||||
|
||||
export const linear_time = (app: Editor): string => {
|
||||
// @ts-ignore
|
||||
let makeExample = makeExampleFactory(app);
|
||||
return `
|
||||
# Linear time
|
||||
|
||||
**Topos** time is flowing just like in your typical computer music program, with _bars_, _beats_, _pulses_ and so on. The transport can be **paused**, **resumed** and/or **stopped**. There are interface buttons to handle these tasks. The tiniest unit of time is the **pulse**. There is a finite number of **pulses** per **beat** (by default, <ic>48</ic> **PPQN**). Beats are passing at a given **BPM** (_beats per minute_). You can change the **BPM** anytime you want. You can also change the granularity of time.
|
||||
|
||||
<object type="image/svg+xml" data=${pulses} style="width: 100%; height: auto; background-color: transparent"></object>
|
||||
|
||||
### Beats, bar, pulses
|
||||
|
||||
**Topos** is using three core values to deal with time:
|
||||
- **bars**: how many bars have elapsed since the origin of time.
|
||||
- **beats**: how many beats have elapsed since the beginning of the bar.
|
||||
- **pulse**: how many pulses have elapsed since the last beat.
|
||||
|
||||
There is a tiny widget at the bottom right of the screen showing you the current BPM and the status of the transport. You can turn it on or off in the settings menu.
|
||||
|
||||
${makeExample(
|
||||
"Printing the transport",
|
||||
`
|
||||
log(\`\$\{cbar()}\, \$\{cbeat()\}, \$\{cpulse()\}\`)
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
### BPM and PPQN
|
||||
|
||||
The base functions to control time are:
|
||||
- <ic>bpm(number?)</ic> : get or set the current tempo.
|
||||
- <ic>ppqn(number?)</ic> : get or set the granularity of time
|
||||
- The function name comes from [PPQN](https://en.wikipedia.org/wiki/Pulses_per_quarter_note) (_pulses per quarter notes_).
|
||||
|
||||
### Controlling time
|
||||
|
||||
Note that it is preferable to use the keyboard shortcuts to manipulate the transport system. You can also use the <ic>play()</ic>, <ic>pause()</ic> and <ic>stop()</ic> functions. It is generally preferable to program things instead of relying on the interface!
|
||||
<br>
|
||||
## Time Primitives
|
||||
|
||||
Every script can access the current time by using the following functions:
|
||||
|
||||
- <ic>cbar(n: number)</ic>: current bar since the origin of time.
|
||||
|
||||
- <ic>cbeat(n: number)</ic>: current beat since the beginning of the bar.
|
||||
|
||||
- <ic>ebeat()</ic>: current beat since the origin of time (counting from 1).
|
||||
|
||||
- <ic>cpulse()</ic>: current bar since the origin of the beat.
|
||||
|
||||
- <ic>ppqn()</ic>: current **PPQN** (see above).
|
||||
|
||||
- <ic>bpm()</ic>: current **BPM** (see above).
|
||||
|
||||
- <ic>time()</ic>: current wall clock time, the real time of the system.
|
||||
|
||||
These values are **extremely useful** to craft more complex syntax or to write musical scores. However, it means that you have to write more to be more precise. There is a tradeoff between _live-codeability_ and dealing with time manually (verbose). Topos is offering high-level functions to deal with that issue, don't worry :)
|
||||
|
||||
You can use time primitives as conditionals. The following example will play a pattern A for 2 bars and a pattern B for 2 bars:
|
||||
|
||||
${makeExample(
|
||||
"Manual mode: using time primitives!",
|
||||
`
|
||||
// Manual time condition
|
||||
if((cbar() % 4) > 1) {
|
||||
beat(2) && sound('kick').out()
|
||||
rarely() && beat(.5) && sound('sd').out()
|
||||
beat([.5, .25].beat()) && sound('jvbass')
|
||||
.freq(100 * [2, 1].pick()).dec(2)
|
||||
.room(0.9).size(0.9).orbit(2).out()
|
||||
} else {
|
||||
beat(.5) && sound('hh').out()
|
||||
beat(2) && sound('cp').out()
|
||||
beat([.5, .5, .25].beat(.5)) && sound('jvbass')
|
||||
.freq(100 * [3, 1].pick()).dec(2)
|
||||
.room(0.9).size(0.9).orbit(2).out()
|
||||
}
|
||||
// This is always playing no matter what happens
|
||||
beat([.5, .5, 1, .25].beat(0.5)) :: sound('shaker').out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
## Time Warping
|
||||
|
||||
Time generally flows from the past to the future. However, you can manipulate it to jump back and forth. Think about looping a specific part of your current pattern or song or jumping all of the sudden in the future. This is entirely possible thanks to two simple functions: <ic>warp(n: number)</ic> and <ic>beat_warp(n: number)</ic>. They are both very easy to use and very powerful. Let's see how they work.
|
||||
|
||||
- <ic>warp(n: number)</ic>: this function jumps to the _n_ tick of the clock. <ic>1</ic> is the first pulsation ever and the number keeps increasing indefinitely. You are most likely currently listening to tick n°<ic>12838123</ic>.
|
||||
|
||||
|
||||
${makeExample(
|
||||
"Time is now super elastic!",
|
||||
`
|
||||
// Obscure Shenanigans - Bubobubobubo
|
||||
beat([1/4,1/8,1/16].beat(8)):: sound('sine')
|
||||
.freq([100,50].beat(16) + 50 * ($(1)%10))
|
||||
.gain(0.5).room(0.9).size(0.9)
|
||||
.sustain(0.1).out()
|
||||
beat(1) :: sound('kick').out()
|
||||
beat(2) :: sound('dr').n(5).out()
|
||||
flip(3) :: beat([.25,.5].beat(.5)) :: sound('dr')
|
||||
.n([8,9].pick()).gain([.8,.5,.25,.1,.0].beat(.25)).out()
|
||||
// Jumping back and forth in time
|
||||
beat(.25) :: warp([12, 48, 24, 1, 120, 30].pick())
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
- <ic>beat_warp(beat: number)</ic>: this function jumps to the _n_ beat of the clock. The first beat is <ic>1</ic>.
|
||||
|
||||
${makeExample(
|
||||
"Jumping back and forth with beats",
|
||||
`
|
||||
// Resonance bliss - Bubobubobubo
|
||||
beat(.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();
|
||||
beat([.25,.125].beat(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();
|
||||
beat(.5) :: snd('arpy').note(
|
||||
[30, 33, 35].repeatAll(4).beat(1) - [12,0].beat(0.5)).out()
|
||||
// Comment me to stop warping!
|
||||
beat(1) :: beat_warp([2,4,5,10,11].pick())
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
## Transport-based rhythm generators
|
||||
|
||||
- <ic>onbeat(...n: number[])</ic>: The <ic>onbeat</ic> function allows you to lock on to a specific beat from the clock to execute code. It can accept multiple arguments. It's usage is very straightforward and not hard to understand. You can pass either integers or floating point numbers. By default, topos is using a <ic>4/4</ic> bar meaning that you can target any of these beats (or in-between) with this function.
|
||||
|
||||
${makeExample(
|
||||
"Some simple yet detailed rhythms",
|
||||
`
|
||||
onbeat(1,2,3,4)::snd('kick').out() // Bassdrum on each beat
|
||||
onbeat(2,4)::snd('snare').n([8,4].beat(4)).out() // Snare on acccentuated beats
|
||||
onbeat(1.5,2.5,3.5, 3.75)::snd('hat').gain(r(0.9,1.1)).out() // Cool high-hats
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Let's do something more complex",
|
||||
`
|
||||
onbeat(0.5, 2, 3, 3.75)::snd('kick').n(2).out()
|
||||
onbeat(2, [1.5, 3, 4].pick(), 4)::snd('snare').n(8).out()
|
||||
beat([.25, 1/8].beat(1.5))::snd('hat').n(2)
|
||||
.gain(rand(0.4, 0.7)).end(0.05)
|
||||
.pan(usine()).out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
|
||||
- <ic>oncount(beats: number[], meter: number)</ic>: This function is similar to <ic>onbeat</ic> but it allows you to specify a custom number of beats as the last argument.
|
||||
|
||||
${makeExample(
|
||||
"Using oncount to create more variation in the rhythm",
|
||||
`
|
||||
z1('1/16 (0 2 3 4)+(0 2 4 6)').scale('pentatonic').sound('sawtooth')
|
||||
.cutoff([400,500,1000,2000].beat(1))
|
||||
.lpadsr(2, 0, .2, 0, 0)
|
||||
.delay(0.5).delayt(0.25).room(0.9).size(0.9).out()
|
||||
onbeat(1,1.5,2,3,4) :: sound('bd').gain(2.0).out()
|
||||
oncount([1,3,5.5,7,7.5,8],8) :: sound('hh').gain(irand(1.0,4.0)).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Using oncount to create rhythms with a custom meter",
|
||||
`
|
||||
bpm(200)
|
||||
oncount([1, 5, 9, 13],16) :: sound('808bd').n(4).shape(0.5).gain(1.0).out()
|
||||
oncount([5, 6, 13],16) :: sound('shaker').room(0.25).gain(0.9).out()
|
||||
oncount([2, 3, 3.5, 6, 7, 10, 15],16) :: sound('hh').n(8).gain(0.8).out()
|
||||
oncount([1, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16],16) :: sound('hh').out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
|
||||
|
||||
`;
|
||||
};
|
||||
162
src/documentation/long_forms.ts
Normal file
162
src/documentation/long_forms.ts
Normal file
@ -0,0 +1,162 @@
|
||||
import { type Editor } from "../main";
|
||||
import { makeExampleFactory } from "../Documentation";
|
||||
|
||||
export const long_forms = (app: Editor): string => {
|
||||
// @ts-ignore
|
||||
let makeExample = makeExampleFactory(app);
|
||||
return `
|
||||
# Long forms
|
||||
|
||||
Now you know how to play some basic rhythms but in any case, you are stuck in a loop. It's time to learn how to compose larger/longer musical structures. The functions you are going to learn are all about mastering the flow of time on longer periods. **Read and experiment a lot with the following examples**.
|
||||
|
||||
## Using scripts and universes
|
||||
|
||||
- **Use the nine local scripts as containers** for sections of your composition. When you start playing with **Topos**, it's easy to forget that there are multiple scripts you can play with. Each script can store a different section or part from your composition. Here is a simple example:
|
||||
|
||||
${makeExample(
|
||||
"Eight bars per section",
|
||||
`
|
||||
// Playing each script for 8 bars in succession
|
||||
script([1,2,3,4].bar(8))
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
You can also give a specific duration to each section using <ic>.dur</ic>:
|
||||
|
||||
${makeExample(
|
||||
"N beats per section",
|
||||
`
|
||||
script([1,2,3,4].dur(8, 2, 16, 4))
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
- **Use universes as well**. Transitions between universes are _seamless_, instantaneous. Just switch to different content if you ever hit the limitations of the current _universe_.
|
||||
|
||||
## Long-form Functions
|
||||
|
||||
- <ic>flip(n: number, ratio: number = 50)</ic>: the <ic>flip</ic> method is a temporal switch. If the value <ic>2</ic> is given, the function will return <ic>true</ic> for two beats and <ic>false</ic> for two beats. There are multiple ways to use it effectively. You can pass an integer or a floating point number.
|
||||
- <ic>ratio: number = 50</ic>: this argument is ratio expressed in %. It determines how much of the period should be true or false. A ratio of <ic>75</ic> means that 75% of the period will be true. A ratio of <ic>25</ic> means that 25% of the period will be true.
|
||||
|
||||
${makeExample(
|
||||
"Two beats of silence, two beats of playing",
|
||||
`
|
||||
flip(4) :: beat(1) :: snd('kick').out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Clapping on the edge",
|
||||
`
|
||||
flip(2.5, 10) :: beat(.25) :: snd('cp').out()
|
||||
flip(2.5, 75) :: beat(.25) :: snd('click')
|
||||
.speed(2).end(0.2).out()
|
||||
flip(2.5) :: beat(.5) :: snd('bd').out()
|
||||
beat(.25) :: sound('hat').end(0.1).cutoff(1200).pan(usine(1/4)).out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Good old true and false",
|
||||
`
|
||||
if (flip(4, 75)) {
|
||||
beat(1) :: snd('kick').out()
|
||||
} else {
|
||||
beat(.5) :: snd('snare').out()
|
||||
}
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
<ic>flip</ic> is extremely powerful and is used internally for a lot of other Topos functions. You can also use it to think about **longer durations** spanning over multiple bars. Here is a silly composition that is using <ic>flip</ic> to generate a 4 bars long pattern.
|
||||
|
||||
${makeExample(
|
||||
"Clunky algorithmic rap music",
|
||||
`
|
||||
// Rap God VS Lil Wild -- Adel Faure
|
||||
if (flip(8)) {
|
||||
// Playing this part for two bars
|
||||
beat(1.5)::snd('kick').out()
|
||||
beat(2)::snd('snare').out()
|
||||
beat(.5)::snd('hh').out()
|
||||
} else {
|
||||
// Now adding some birds and tablas
|
||||
beat(1.5)::snd('kick').out()
|
||||
beat(2)::snd('snare').out()
|
||||
beat(.5)::snd('hh').out()
|
||||
beat(.5)::snd('tabla').speed([1,2].pick()).end(0.5).out()
|
||||
beat(2.34)::snd('birds').n(irand(1,10))
|
||||
.delay(0.5).delaytime(0.5).delayfb(0.25).out()
|
||||
beat(.5)::snd('diphone').end(0.5).n([1,2,3,4].pick()).out()
|
||||
}
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
You can use it everywhere to spice things up, including as a method parameter picker:
|
||||
|
||||
${makeExample(
|
||||
"flip is great for parameter variation",
|
||||
`
|
||||
beat(.5)::snd(flip(2) ? 'kick' : 'hat').out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
- <ic>flipbar(n: number = 1)</ic>: this method works just like <ic>flip</ic> but counts in bars instead of beats. It allows you to think about even larger time cycles. You can also pair it with regular <ic>flip</ic> for writing complex and long-spanning algorithmic beats.
|
||||
|
||||
${makeExample(
|
||||
"Thinking music over bars",
|
||||
`
|
||||
let roomy = (n) => n.room(1).size(1).cutoff(500 + usaw(1/8) * 5000);
|
||||
function a() {
|
||||
beat(1) && roomy(sound('kick')).out()
|
||||
beat(.5) && roomy(sound('hat')).out()
|
||||
}
|
||||
function b() {
|
||||
beat(1/4) && roomy(sound('shaker')).out()
|
||||
}
|
||||
flipbar(2) && a()
|
||||
flipbar(3) && b()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
${makeExample(
|
||||
"Alternating over four bars",
|
||||
`
|
||||
flipbar(2)
|
||||
? beat(.5) && snd(['kick', 'hh'].beat(1)).out()
|
||||
: beat(.5) && snd(['east', 'east:2'].beat(1)).out()
|
||||
`,
|
||||
false
|
||||
)};
|
||||
|
||||
|
||||
- <ic>onbar(bars: number | number[], n: number)</ic>: The second argument, <ic>n</ic>, is used to divide the time in a period of <ic>n</ic> consecutive bars. The first argument should be a bar number or a list of bar numbers to play on. For example, <ic>onbar([1, 4], 5)</ic> will return <ic>true</ic> on bar <ic>1</ic> and <ic>4</ic> but return <ic>false</ic> the rest of the time. You can easily divide time that way.
|
||||
|
||||
${makeExample(
|
||||
"Using onbar for filler drums",
|
||||
`
|
||||
bpm(150);
|
||||
// Only play on the third and fourth bar of the cycle.
|
||||
onbar([3,4], 4)::beat(.25)::snd('hh').out();
|
||||
// Using JavaScript regular control flow
|
||||
if (onbar([1,2], 4)) {
|
||||
beat(.5) :: sometimes() :: sound('east').out()
|
||||
rhythm(.5, 3, 7) :: snd('kick').out();
|
||||
rhythm(.5, 1, 7) :: snd('jvbass').out();
|
||||
rhythm(.5, 2, 7) :: snd('snare').n(5).out();
|
||||
} else {
|
||||
beat(.5) :: rarely() :: sound('east').n($(1)).out()
|
||||
rhythm(.5, 3, 7) :: snd('kick').n(4).out();
|
||||
rhythm(.5, 1, 7) :: snd('jvbass').n(2).out();
|
||||
rhythm(.5, 2, 7) :: snd('snare').n(3).out();
|
||||
}`,
|
||||
true
|
||||
)}
|
||||
|
||||
`;
|
||||
};
|
||||
@ -4,9 +4,9 @@ import { makeExampleFactory } from "../Documentation";
|
||||
export const mouse = (app: Editor): string => {
|
||||
let makeExample = makeExampleFactory(app);
|
||||
return `
|
||||
## Mouse
|
||||
# Mouse
|
||||
|
||||
Using the mouse is a fun way to control reactive code. It's basically an X/Y controller that you don't have to pay for! There are clever actions you can do with the mouse from generating notes to activating scripts conditionally!
|
||||
Using the mouse is a fun way to control your code. It's basically an X/Y controller that you don't have to pay for! There are clever actions you can do with the mouse from generating notes to activating scripts conditionally!
|
||||
|
||||
## Mouse position
|
||||
|
||||
@ -16,16 +16,18 @@ You can get the current position of the mouse on the screen by using the followi
|
||||
- <ic>mouseY()</ic>: the vertical position of the mouse on the screen (as a floating point number).
|
||||
|
||||
${makeExample(
|
||||
"FM Synthesizer controlled using the mouse",
|
||||
`
|
||||
"FM Synthesizer controlled using the mouse",
|
||||
`
|
||||
beat(.25) :: sound('sine')
|
||||
.fmi(mouseX() / 100)
|
||||
.fmh(mouseY() / 100)
|
||||
.vel(0.2)
|
||||
.room(0.9).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
<br>
|
||||
|
||||
Current mouse position can also be used to generate notes:
|
||||
|
||||
@ -34,8 +36,8 @@ Current mouse position can also be used to generate notes:
|
||||
|
||||
|
||||
${makeExample(
|
||||
"The same synthesizer, with note control!",
|
||||
`
|
||||
"The same synthesizer, with note control!",
|
||||
`
|
||||
beat(.25) :: sound('sine')
|
||||
.fmi(mouseX() / 100)
|
||||
.note(noteX())
|
||||
@ -43,8 +45,8 @@ beat(.25) :: sound('sine')
|
||||
.vel(0.2)
|
||||
.room(0.9).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
## Mouse and Arrays
|
||||
|
||||
@ -55,14 +57,14 @@ You can use the mouse to explore the valuesq contained in an Array:
|
||||
|
||||
|
||||
${makeExample(
|
||||
"Taking values out of an Array with the mouse",
|
||||
`
|
||||
"Taking values out of an Array with the mouse",
|
||||
`
|
||||
log([1,2,3,4].mouseX())
|
||||
log([4,5,6,7].mouseY())
|
||||
|
||||
`,
|
||||
true
|
||||
)}
|
||||
true
|
||||
)}
|
||||
|
||||
|
||||
|
||||
|
||||
4
src/documentation/pulses.svg
Normal file
4
src/documentation/pulses.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.4 KiB |
@ -1,511 +1,35 @@
|
||||
import { makeExampleFactory } from "../Documentation";
|
||||
import { type Editor } from "../main";
|
||||
import times from "./times.svg";
|
||||
|
||||
export const time = (application: Editor): string => {
|
||||
//@ts-ignore
|
||||
const makeExample = makeExampleFactory(application);
|
||||
return `
|
||||
# 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:
|
||||
|
||||
- **bars**: how many bars have elapsed since the origin of time.
|
||||
- **beats**: how many beats have elapsed since the beginning of the bar.
|
||||
- **pulse**: how many pulses have elapsed since the last beat.
|
||||
|
||||
To change the tempo, use the <ic>bpm(number)</ic> function. The transport is controlled by the interface buttons, by the keyboard shortcuts or using the <ic>play()</ic>, <ic>pause()</ic> and <ic>stop()</ic> functions. You will soon learn how to manipulate time to your liking for backtracking, jumping forward, etc... The traditional timeline model has little value when you can script everything.
|
||||
|
||||
**Note:** the <ic>bpm(number)</ic> function can serve both for getting and setting the **BPM** value.
|
||||
|
||||
## Simple rhythms
|
||||
|
||||
- <ic>beat(n: number | number[] = 1, offset: number = 1)</ic>: return true every _n_ beats.
|
||||
- <ic>number</ic>: if <ic>number = 1</ic>, the function will return <ic>true</ic> every beat. Lists can be used too.
|
||||
- <ic>offset</ic>: offset (in beats) to apply. An offset of <ic>0.5</ic> will return true against the beat.
|
||||
|
||||
${makeExample(
|
||||
"Using different mod values",
|
||||
`
|
||||
// This code is alternating between different mod values
|
||||
beat([1,1/2,1/4,1/8].beat(2)) :: sound('hat').n(0).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
# What is time?
|
||||
|
||||
${makeExample(
|
||||
"Some sort of ringtone",
|
||||
`
|
||||
// Blip generator :)
|
||||
let blip = (freq) => {
|
||||
return sound('wt_piano')
|
||||
.gain(1)
|
||||
.sustain(0.1)
|
||||
.freq(freq)
|
||||
.cutoff(1500)
|
||||
.lpadsr(4, 0, .25, 0, 0)
|
||||
};
|
||||
beat(1) :: blip(200).pan(r(0,1)).vib(0.5).vibmod(2).out();
|
||||
beat(1/3) :: blip(400).pan(r(0,1)).out();
|
||||
flip(3) :: beat(1/6) :: blip(800).pan(r(0,1)).out();
|
||||
beat([1,0.75].beat(2)) :: blip([50, 100].beat(2)).pan(r(0,1)).out();
|
||||
`,
|
||||
false
|
||||
)}
|
||||
There are two ways to think _intuitively_ about time:
|
||||
|
||||
${makeExample(
|
||||
"Multiple values for creating rhythms", `
|
||||
beat([1,2.5,3.25], 0)::sound('hat').out()
|
||||
beat([1,2.25,4])::sound('kick').speed(
|
||||
[1.25,1].beat(0.5)).out()
|
||||
onbeat(3, 3.25, 3.125)::sound('shaker').out()
|
||||
`, false,
|
||||
)}
|
||||
- **linear time:** the _arrow_ of time, minutes/days/years passing. Time moving forward. In musical terms, a _piece_, _song_.
|
||||
- **cyclical time:** seasons, cycles, etc. In musical terms, repetitions, _beats_, _sections_, etc.
|
||||
|
||||
- <ic>pulse(n: number | number[] = 1, offset: number = 1)</ic>: return true every _n_ pulses. A pulse is the tiniest possible rhythmic value.
|
||||
- <ic>number</ic>: if <ic>number = 1</ic>, the function will return <ic>true</ic> every pulse. Lists can be used too.
|
||||
- <ic>offset</ic>: offset (in pulses) to apply.
|
||||
A musician's job is to interweave cyclical and linear time, repetition and continuity.
|
||||
|
||||
<object type="image/svg+xml" data=${times} style="width: 100%; height: auto; background-color: transparent"></object>
|
||||
|
||||
|
||||
${makeExample(
|
||||
"Intriguing rhythms",
|
||||
`
|
||||
pulse([24,48].beat(2)) :: snd('hand')
|
||||
.cut(1).room(0.9).size(0.9)
|
||||
.n([2,4].beat(2)).out()
|
||||
pulse([48/2, 48/3].beat(4)) :: snd('hand')
|
||||
.n([2,4].add(5).beat(1)).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
${makeExample(
|
||||
"pulse is the OG rhythmic function in Topos",
|
||||
`
|
||||
pulse([48, 24, 16].beat(4)) :: sound('linnhats').out()
|
||||
beat(1)::snd(['bd', '808oh'].beat(1)).out()
|
||||
`,
|
||||
false
|
||||
)};
|
||||
# Time and programming
|
||||
|
||||
When you program on a computer, you can adopt a similar mindset to think about time, where time is sometimes cyclic, sometimes linear:
|
||||
- **linear:** _runtime_, _process time_, _wall clock time_, etc...
|
||||
- **cyclic:** _recursion_, repeating function, routine, etc.
|
||||
|
||||
# Time and Topos
|
||||
|
||||
By making music with **Topos**, you will mingle repetitive structures of different scale and deal with composition as well:
|
||||
- **linear time:** using _bars_, _beats_, _pulses_, transport (_start_/_pause_/_stop_), etc...
|
||||
- **cyclical time:** euclidean rhythms, beats, pulsed time, etc...
|
||||
|
||||
|
||||
- <ic>bar(n: number | number[] = 1, offset: number = 1)</ic>: return true every _n_ bars.
|
||||
- <ic>number</ic>: if <ic>number = 1</ic>, the function will return <ic>true</ic> every bar. Lists can be used too.
|
||||
- <ic>offset</ic>: offset (in bars) to apply.
|
||||
|
||||
${makeExample(
|
||||
"Four beats per bar: proof",
|
||||
`
|
||||
bar(1)::sound('kick').out()
|
||||
beat(1)::sound('hat').speed(2).out()
|
||||
`, true
|
||||
)}
|
||||
|
||||
|
||||
${makeExample(
|
||||
"Offsettings trigger signal",
|
||||
`
|
||||
bar(1)::sound('kick').out()
|
||||
beat(1)::sound('hat').speed(2).out()
|
||||
beat(1,0.5)::sound('hat').speed(4).out()
|
||||
bar(1,0.5)::sound('sn').out()
|
||||
`, false
|
||||
)}
|
||||
|
||||
- <ic>onbeat(...n: number[])</ic>: The <ic>onbeat</ic> function allows you to lock on to a specific beat from the clock to execute code. It can accept multiple arguments. It's usage is very straightforward and not hard to understand. You can pass either integers or floating point numbers. By default, topos is using a <ic>4/4</ic> bar meaning that you can target any of these beats (or in-between) with this function.
|
||||
|
||||
${makeExample(
|
||||
"Some simple yet detailed rhythms",
|
||||
`
|
||||
onbeat(1,2,3,4)::snd('kick').out() // Bassdrum on each beat
|
||||
onbeat(2,4)::snd('snare').n([8,4].beat(4)).out() // Snare on acccentuated beats
|
||||
onbeat(1.5,2.5,3.5, 3.75)::snd('hat').gain(r(0.9,1.1)).out() // Cool high-hats
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Let's do something more complex",
|
||||
`
|
||||
onbeat(0.5, 2, 3, 3.75)::snd('kick').n(2).out()
|
||||
onbeat(2, [1.5, 3, 4].pick(), 4)::snd('snare').n(8).out()
|
||||
beat([.25, 1/8].beat(1.5))::snd('hat').n(2)
|
||||
.gain(rand(0.4, 0.7)).end(0.05)
|
||||
.pan(usine()).out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
|
||||
- <ic>oncount(beats: number[], meter: number)</ic>: This function is similar to <ic>onbeat</ic> but it allows you to specify a custom number of beats as the last argument.
|
||||
|
||||
${makeExample(
|
||||
"Using oncount to create more variation in the rhythm",
|
||||
`
|
||||
z1('1/16 (0 2 3 4)+(0 2 4 6)').scale('pentatonic').sound('sawtooth')
|
||||
.cutoff([400,500,1000,2000].beat(1))
|
||||
.lpadsr(2, 0, .2, 0, 0)
|
||||
.delay(0.5).delayt(0.25).room(0.9).size(0.9).out()
|
||||
onbeat(1,1.5,2,3,4) :: sound('bd').gain(2.0).out()
|
||||
oncount([1,3,5.5,7,7.5,8],8) :: sound('hh').gain(irand(1.0,4.0)).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Using oncount to create rhythms with a custom meter",
|
||||
`
|
||||
bpm(200)
|
||||
oncount([1, 5, 9, 13],16) :: sound('808bd').n(4).shape(0.5).gain(1.0).out()
|
||||
oncount([5, 6, 13],16) :: sound('shaker').room(0.25).gain(0.9).out()
|
||||
oncount([2, 3, 3.5, 6, 7, 10, 15],16) :: sound('hh').n(8).gain(0.8).out()
|
||||
oncount([1, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16],16) :: sound('hh').out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
## Rhythm generators
|
||||
|
||||
We included a bunch of popular rhythm generators in Topos such as the euclidian rhythms algorithms or the one to generate rhythms based on a binary sequence. They all work using _iterators_ that you will gradually learn to use for iterating over lists. Note that they are levaraging <ic>mod(...n:number[])</ic> that you just learned about!
|
||||
|
||||
- <ic>euclid(iterator: number, pulses: number, length: number, rotate: number): boolean</ic>: generates <ic>true</ic> or <ic>false</ic> values from an euclidian rhythm sequence. This algorithm is very popular in the electronic music making world.
|
||||
|
||||
${makeExample(
|
||||
"Classic euclidian club music patterns",
|
||||
`
|
||||
beat(.5) && euclid($(1), 4, 8) && snd('kick').n(4).out()
|
||||
beat(.25) && euclid($(2), 5, 8) && snd('dr').n(21).out()
|
||||
beat(.25) && euclid($(3), 3, 8) && snd('shaker')
|
||||
.gain(r(0.7, 1)).cutoff(1000 + usine(1/8) * 3000)
|
||||
.n(11).out()
|
||||
beat(.25) && euclid($(3), 6, 8) && snd('shaker')
|
||||
.gain(r(0.7, 1)).cutoff(1000 + usine(1/4) * 4000)
|
||||
.speed(2).n(11).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"And now something a bit more complex",
|
||||
`
|
||||
bpm(145); // Setting a faster BPM
|
||||
beat(.5) && euclid($(1), 5, 8) :: sound('bd').out()
|
||||
beat(.5) && euclid($(2), [1,0].beat(8), 8)
|
||||
:: sound('ST03').n(5).room(1).size(1).o(1).out()
|
||||
beat(.5) && euclid($(6), [6,7].beat(8), 8) :: sound('hh').out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Adding more rhythmic density",
|
||||
`
|
||||
beat(.5) && euclid($(1), 5, 9) && snd('kick').shape(r(0.2,0.5)).out()
|
||||
beat(.5) && euclid($(2), 2, 3, 1) && snd('dr').end(0.5).n([8,9,13].beat(0.25))
|
||||
.gain(r(0.5,1)).speed(1).out()
|
||||
beat(.5) && euclid($(3), 6, 9, 1) && snd('dr').end(0.5).n(2).freq(200).speed(1)
|
||||
.gain(r(0.5,1)).out()
|
||||
beat(.25) && euclid($(4), 7, 9, 1) && snd('hh').out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
|
||||
Alternatively, you can <ic>oneuclid</ic> or <ic>rhythm</ic> without the _iterators_:
|
||||
|
||||
- <ic>oneuclid(pulses: number, length: number, rotate: number): boolean</ic>: generates <ic>true</ic> or <ic>false</ic> values from an euclidian rhythm sequence. This is another version of <ic>euclid</ic> that does not take an iterator.
|
||||
${makeExample(
|
||||
"Using oneuclid to create a rhythm without iterators",
|
||||
`
|
||||
// Change speed using bpm
|
||||
bpm(250)
|
||||
oneuclid(5, 9) :: snd('kick').out()
|
||||
oneuclid(7,16) :: snd('east').end(0.5).n(irand(3,5)).out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
|
||||
- <ic>rhythm(divisor: number, pulses: number, length: number, rotate: number): boolean</ic>: generates <ic>true</ic> or <ic>false</ic> values from an euclidian rhythm sequence. This is another version of <ic>euclid</ic> that does not take an iterator.
|
||||
${makeExample(
|
||||
"rhythm is a beginner friendly rhythmic function!",
|
||||
`
|
||||
let speed = [1, 0.5].beat(8); bpm(140);
|
||||
rhythm(speed, 5, 12) :: snd('linnhats').n(2).pan(noise()).out()
|
||||
rhythm(speed, 2, 12) :: snd('east').out()
|
||||
rhythm(speed, 3, 12) :: snd('linnhats').n(4).pan(noise()).out()
|
||||
rhythm(speed, 7, 12) :: snd('east').n(9).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
- <ic>bin(iterator: number, n: number): boolean</ic>: a binary rhythm generator. It transforms the given number into its binary representation (_e.g_ <ic>34</ic> becomes <ic>100010</ic>). It then returns a boolean value based on the iterator in order to generate a rhythm.
|
||||
- <ic>binrhythm(divisor: number, n: number): boolean: boolean</ic>: iterator-less version of the binary rhythm generator.
|
||||
|
||||
${makeExample(
|
||||
"Change the integers for a surprise rhythm!",
|
||||
`
|
||||
bpm(135);
|
||||
beat(.5) && bin($(1), 12) && snd('kick').n([4,9].beat(1.5)).out()
|
||||
beat(.5) && bin($(2), 34) && snd('snare').n([3,5].beat(1)).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"binrhythm for fast cool binary rhythms!",
|
||||
`
|
||||
let a = 0;
|
||||
a = beat(4) ? irand(1,20) : a;
|
||||
binrhythm(.5, 6) && snd(['kick', 'snare'].beat(0.5)).n(11).out()
|
||||
binrhythm([.5, .25].beat(1), 30) && snd('wt_granular').n(a)
|
||||
.cutoff(800).lpadsr(4, 0, 0.125, 0.5, 0.25)
|
||||
.adsr(0, r(.1, .4), 0, 0).freq([50, 60, 72].beat(4))
|
||||
.room(1).size(1).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Submarine jungle music",
|
||||
`
|
||||
bpm(145);
|
||||
beat(.5) && bin($(1), 911) && snd('ST69').n([2,3,4].beat())
|
||||
.delay(0.125).delayt(0.25).end(0.25).speed(1/3)
|
||||
.room(1).size(1).out()
|
||||
beat(.5) && sound('amencutup').n(irand(2,7)).shape(0.3).out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
|
||||
If you don't find it spicy enough, you can add some more probabilities to your rhythms by taking advantage of the probability functions. See the functions documentation page to learn more about them.
|
||||
|
||||
${makeExample(
|
||||
"Probablistic drums in one line!",
|
||||
`
|
||||
prob(60)::beat(.5) && euclid($(1), 5, 8) && snd('kick').out()
|
||||
prob(60)::beat(.5) && euclid($(2), 3, 8) && snd('mash')
|
||||
.n([1,2,3].beat(1))
|
||||
.pan(usine(1/4)).out()
|
||||
prob(80)::beat(.5) && sound(['hh', 'hat'].pick()).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
## Time Warping
|
||||
|
||||
Time generally flows from the past to the future. However, it's even cooler when you can manipulate it to your liking by jumping back and forth. Think about looping a specific part of your current pattern or song or jumping all of the sudden in the future. This is entirely possible thanks to two simple functions: <ic>warp(n: number)</ic> and <ic>beat_warp(n: number)</ic>. They are both very easy to use and very powerful. Let's see how they work.
|
||||
|
||||
- <ic>warp(n: number)</ic>: this function jumps to the _n_ tick of the clock. <ic>1</ic> is the first pulsation ever and the number keeps increasing indefinitely. You are most likely currently listening to tick n°<ic>12838123</ic>.
|
||||
|
||||
|
||||
${makeExample(
|
||||
"Time is now super elastic!",
|
||||
`
|
||||
// Obscure Shenanigans - Bubobubobubo
|
||||
beat([1/4,1/8,1/16].beat(8)):: sound('sine')
|
||||
.freq([100,50].beat(16) + 50 * ($(1)%10))
|
||||
.gain(0.5).room(0.9).size(0.9)
|
||||
.sustain(0.1).out()
|
||||
beat(1) :: sound('kick').out()
|
||||
beat(2) :: sound('dr').n(5).out()
|
||||
flip(3) :: beat([.25,.5].beat(.5)) :: sound('dr')
|
||||
.n([8,9].pick()).gain([.8,.5,.25,.1,.0].beat(.25)).out()
|
||||
// Jumping back and forth in time
|
||||
beat(.25) :: warp([12, 48, 24, 1, 120, 30].pick())
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
- <ic>beat_warp(beat: number)</ic>: this function jumps to the _n_ beat of the clock. The first beat is <ic>1</ic>.
|
||||
|
||||
${makeExample(
|
||||
"Jumping back and forth with beats",
|
||||
`
|
||||
// Resonance bliss - Bubobubobubo
|
||||
beat(.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();
|
||||
beat([.25,.125].beat(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();
|
||||
beat(.5) :: snd('arpy').note(
|
||||
[30, 33, 35].repeatAll(4).beat(1) - [12,0].beat(0.5)).out()
|
||||
// Comment me to stop warping!
|
||||
beat(1) :: beat_warp([2,4,5,10,11].pick())
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
## Larger time divisions
|
||||
|
||||
Now you know how to play some basic rhythmic music but you are a bit stuck in a one-bar long loop. Let's see how we can think about time flowing on longer periods. The functions you are going to learn now are _very fundamental_ and all the fun comes from mastering them. **Read and experiment a lot with the following examples**.
|
||||
|
||||
- <ic>flip(n: number, ratio: number = 50)</ic>: the <ic>flip</ic> method is a temporal switch. If the value <ic>2</ic> is given, the function will return <ic>true</ic> for two beats and <ic>false</ic> for two beats. There are multiple ways to use it effectively. You can pass an integer or a floating point number.
|
||||
- <ic>ratio: number = 50</ic>: this argument is ratio expressed in %. It determines how much of the period should be true or false. A ratio of <ic>75</ic> means that 75% of the period will be true. A ratio of <ic>25</ic> means that 25% of the period will be true.
|
||||
|
||||
${makeExample(
|
||||
"Two beats of silence, two beats of playing",
|
||||
`
|
||||
flip(4) :: beat(1) :: snd('kick').out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Clapping on the edge",
|
||||
`
|
||||
flip(2.5, 10) :: beat(.25) :: snd('cp').out()
|
||||
flip(2.5, 75) :: beat(.25) :: snd('click')
|
||||
.speed(2).end(0.2).out()
|
||||
flip(2.5) :: beat(.5) :: snd('bd').out()
|
||||
beat(.25) :: sound('hat').end(0.1).cutoff(1200).pan(usine(1/4)).out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Good old true and false",
|
||||
`
|
||||
if (flip(4, 75)) {
|
||||
beat(1) :: snd('kick').out()
|
||||
} else {
|
||||
beat(.5) :: snd('snare').out()
|
||||
}
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
<ic>flip</ic> is extremely powerful and is used internally for a lot of other Topos functions. You can also use it to think about **longer durations** spanning over multiple bars. Here is a silly composition that is using <ic>flip</ic> to generate a 4 bars long pattern.
|
||||
|
||||
${makeExample(
|
||||
"Clunky algorithmic rap music",
|
||||
`
|
||||
// Rap God VS Lil Wild -- Adel Faure
|
||||
if (flip(8)) {
|
||||
// Playing this part for two bars
|
||||
beat(1.5)::snd('kick').out()
|
||||
beat(2)::snd('snare').out()
|
||||
beat(.5)::snd('hh').out()
|
||||
} else {
|
||||
// Now adding some birds and tablas
|
||||
beat(1.5)::snd('kick').out()
|
||||
beat(2)::snd('snare').out()
|
||||
beat(.5)::snd('hh').out()
|
||||
beat(.5)::snd('tabla').speed([1,2].pick()).end(0.5).out()
|
||||
beat(2.34)::snd('birds').n(irand(1,10))
|
||||
.delay(0.5).delaytime(0.5).delayfb(0.25).out()
|
||||
beat(.5)::snd('diphone').end(0.5).n([1,2,3,4].pick()).out()
|
||||
}
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
You can use it everywhere to spice things up, including as a method parameter picker:
|
||||
|
||||
${makeExample(
|
||||
"flip is great for parameter variation",
|
||||
`
|
||||
beat(.5)::snd(flip(2) ? 'kick' : 'hat').out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
- <ic>flipbar(n: number = 1)</ic>: this method works just like <ic>flip</ic> but counts in bars instead of beats. It allows you to think about even larger time cycles. You can also pair it with regular <ic>flip</ic> for writing complex and long-spanning algorithmic beats.
|
||||
|
||||
${makeExample(
|
||||
"Thinking music over bars",
|
||||
`
|
||||
let roomy = (n) => n.room(1).size(1).cutoff(500 + usaw(1/8) * 5000);
|
||||
function a() {
|
||||
beat(1) && roomy(sound('kick')).out()
|
||||
beat(.5) && roomy(sound('hat')).out()
|
||||
}
|
||||
function b() {
|
||||
beat(1/4) && roomy(sound('shaker')).out()
|
||||
}
|
||||
flipbar(2) && a()
|
||||
flipbar(3) && b()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
${makeExample(
|
||||
"Alternating over four bars",
|
||||
`
|
||||
flipbar(2)
|
||||
? beat(.5) && snd(['kick', 'hh'].beat(1)).out()
|
||||
: beat(.5) && snd(['east', 'east:2'].beat(1)).out()
|
||||
`,
|
||||
false
|
||||
)};
|
||||
|
||||
|
||||
- <ic>onbar(bars: number | number[], n: number)</ic>: The second argument, <ic>n</ic>, is used to divide the time in a period of <ic>n</ic> consecutive bars. The first argument should be a bar number or a list of bar numbers to play on. For example, <ic>onbar([1, 4], 5)</ic> will return <ic>true</ic> on bar <ic>1</ic> and <ic>4</ic> but return <ic>false</ic> the rest of the time. You can easily divide time that way.
|
||||
|
||||
${makeExample(
|
||||
"Using onbar for filler drums",
|
||||
`
|
||||
bpm(150);
|
||||
// Only play on the third and fourth bar of the cycle.
|
||||
onbar([3,4], 4)::beat(.25)::snd('hh').out();
|
||||
// Using JavaScript regular control flow
|
||||
if (onbar([1,2], 4)) {
|
||||
beat(.5) :: sometimes() :: sound('east').out()
|
||||
rhythm(.5, 3, 7) :: snd('kick').out();
|
||||
rhythm(.5, 1, 7) :: snd('jvbass').out();
|
||||
rhythm(.5, 2, 7) :: snd('snare').n(5).out();
|
||||
} else {
|
||||
beat(.5) :: rarely() :: sound('east').n($(1)).out()
|
||||
rhythm(.5, 3, 7) :: snd('kick').n(4).out();
|
||||
rhythm(.5, 1, 7) :: snd('jvbass').n(2).out();
|
||||
rhythm(.5, 2, 7) :: snd('snare').n(3).out();
|
||||
}`,
|
||||
true
|
||||
)}
|
||||
|
||||
## What are pulses?
|
||||
|
||||
To make a beat, you need a certain number of time grains or **pulses**. The **pulse** is also known as the [PPQN](https://en.wikipedia.org/wiki/Pulses_per_quarter_note). By default, Topos is using a _pulses per quarter note_ of 48. You can change it by using the <ic>ppqn(number)</ic> function. It means that the lowest possible rhythmic value is 1/48 of a quarter note. That's plenty of time already.
|
||||
|
||||
**Note:** the <ic>ppqn(number)</ic> function can serve both for getting and setting the **PPQN** value.
|
||||
|
||||
## Time Primitives
|
||||
|
||||
Every script can access the current time by using the following functions:
|
||||
|
||||
- <ic>cbar(n: number)</ic>: returns the current bar since the origin of time.
|
||||
|
||||
- <ic>cbeat(n: number)</ic>: returns the current beat since the beginning of the bar.
|
||||
|
||||
- <ic>ebeat()</ic>: returns the current beat since the origin of time (counting from 1).
|
||||
|
||||
- <ic>cpulse()</ic>: returns the current bar since the origin of the beat.
|
||||
|
||||
- <ic>ppqn()</ic>: returns the current **PPQN** (see above).
|
||||
|
||||
- <ic>bpm()</ic>: returns the current **BPM** (see above).
|
||||
|
||||
- <ic>time()</ic>: returns the current wall clock time, the real time of the system.
|
||||
|
||||
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:
|
||||
|
||||
${makeExample(
|
||||
"Manual mode: using time primitives!",
|
||||
`
|
||||
// Manual time condition
|
||||
if((cbar() % 4) > 1) {
|
||||
beat(2) && sound('kick').out()
|
||||
rarely() && beat(.5) && sound('sd').out()
|
||||
beat([.5, .25].beat()) && sound('jvbass')
|
||||
.freq(100 * [2, 1].pick()).dec(2)
|
||||
.room(0.9).size(0.9).orbit(2).out()
|
||||
} else {
|
||||
beat(.5) && sound('hh').out()
|
||||
beat(2) && sound('cp').out()
|
||||
beat([.5, .5, .25].beat(.5)) && sound('jvbass')
|
||||
.freq(100 * [3, 1].pick()).dec(2)
|
||||
.room(0.9).size(0.9).orbit(2).out()
|
||||
}
|
||||
// This is always playing no matter what happens
|
||||
beat([.5, .5, 1, .25].beat(0.5)) :: sound('shaker').out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
`;
|
||||
};
|
||||
|
||||
4
src/documentation/times.svg
Normal file
4
src/documentation/times.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Do not edit this file with editors other than draw.io -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="501px" height="151px" viewBox="-0.5 -0.5 501 151" class="ge-export-svg-dark" content="<mxfile host="app.diagrams.net" modified="2023-11-06T09:00:40.296Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" etag="C_foan-BfT9OTEfKR49b" version="22.0.8" type="device"><diagram name="Page-1" id="SVK7qbBq6eghmxk_gXBK">7Zddb5swFIZ/DZeVAAfSXK5Jt2lN1Wq5aHs1efgULBlMjVOgv35mGIhjsqKoH7vIFebF59jnfYxsO2iZVt8EzpNrToA5vksqB60c3/dcd6YejVK3yjwMWiEWlOhOg7ChL9BFanVLCRRGR8k5kzQ3xYhnGUTS0LAQvDS7PXJmjprjGCxhE2Fmq3eUyESrXrgYPnwHGifd0GGgK05x11uXUiSY8HJHQpcOWgrOZdtKqyWwxr3OmDbu64Gv/cwEZHJKQDm/pz8u8mj+MydXv182kK/omc7yjNlWV6wnK+vOAsG3GYEmieugizKhEjY5jpqvpYKutESmTL15qqnTgZBQHZyn11ev1g3wFKSoVZdyMDhwtWnJrreBFrGGGvexQ9mqoSsfd+Hp4e7hHv1aXxU1ytcgrtObcIoLil7eNB8ZVF+ahaWKhYzo5ipiuChoZJphOnfAB98FYqzBV4yxfek0AQxL+myu3DGv9Ai3nKqZ+G7/s7YR+k8N98wu+FZEoGN2l9lemtni33kkFjFIK89fbn3Nx6P0TygtlJ5/JEsPvZLonWGiE8wJEKbS7Cf0STSDE80JEKbSRPPPpRmeaE6AcOyu+dE0FxbNNc0Ai+a8TVOw0B5xKByhaJ0TbY7jO1rnzg7l2fkI5n0X3+7EaG9NyzpiVN0c/lPLzt/PMvU6XGPaFTncBtHlHw==</diagram></mxfile>" style="background-color: rgb(18, 18, 18);"><defs><style type="text/css">svg.ge-export-svg-dark > * { filter: invert(100%) hue-rotate(180deg); }
svg.ge-export-svg-dark image { filter: invert(100%) hue-rotate(180deg) }</style></defs><g><rect x="0" y="0" width="500" height="150" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 10.5 65 L 10.5 55 L 470.5 55 L 470.5 44.5 L 489.5 60 L 470.5 75.5 L 470.5 65 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 10.5 125 L 10.5 115 L 110.5 115 L 110.5 104.5 L 129.5 120 L 110.5 135.5 L 110.5 125 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 130.5 125 L 130.5 115 L 230.5 115 L 230.5 104.5 L 249.5 120 L 230.5 135.5 L 230.5 125 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 250.5 125 L 250.5 115 L 350.5 115 L 350.5 104.5 L 369.5 120 L 350.5 135.5 L 350.5 125 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 370.5 125 L 370.5 115 L 470.5 115 L 470.5 104.5 L 489.5 120 L 470.5 135.5 L 470.5 125 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="10" y="20" width="480" height="20" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 478px; height: 1px; padding-top: 30px; margin-left: 11px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Linear time</div></div></div></foreignObject><text x="250" y="34" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Linear time</text></switch></g><rect x="10" y="80" width="480" height="20" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 478px; height: 1px; padding-top: 90px; margin-left: 11px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Cyclical time</div></div></div></foreignObject><text x="250" y="94" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Cyclical time</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://www.drawio.com/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Text is not SVG - cannot display</text></a></switch></svg>
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
@ -92,7 +92,11 @@ export class Editor {
|
||||
|
||||
this.initializeElements();
|
||||
this.initializeButtonGroups();
|
||||
this.initializeHydra();
|
||||
try {
|
||||
this.initializeHydra();
|
||||
} catch (error) {
|
||||
console.log("Couldn't start Hydra: ", error);
|
||||
}
|
||||
this.setCanvas(this.interface.feedback as HTMLCanvasElement);
|
||||
this.setCanvas(this.interface.scope as HTMLCanvasElement);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user