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

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

View File

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

View File

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

View File

@ -3,6 +3,10 @@ export {};
declare global {
interface Array<T> {
add(amount: number): number[];
sub(amount: number): number[];
mult(amount: number): number[];
division(amount: number): number[];
palindrome(): T[];
random(index: number): T;
rand(index: number): T;
@ -20,6 +24,8 @@ declare global {
rotate(steps: number): this;
unique(): this;
in(value: T): boolean;
square(): number[];
sqrt(): number[];
}
}
@ -27,6 +33,54 @@ export const makeArrayExtensions = (api: UserAPI) => {
Array.prototype.in = function <T>(this: T[], value: T): boolean {
return this.includes(value);
};
Array.prototype.square = function (): number[] {
/**
* @returns New array with squared values.
*/
return this.map((x: number) => x * x);
};
Array.prototype.sqrt = function (): number[] {
/**
* @returns New array with square roots of values. Throws if any element is negative.
*/
if (this.some(x => x < 0)) throw new Error('Cannot take square root of negative number');
return this.map((x: number) => Math.sqrt(x));
};
Array.prototype.add = function (amount: number): number[] {
/**
* @param amount - The value to add to each element in the array.
* @returns New array with added values.
*/
return this.map((x: number) => x + amount);
};
Array.prototype.sub = function (amount: number): number[] {
/**
* @param amount - The value to subtract from each element in the array.
* @returns New array with subtracted values.
*/
return this.map((x: number) => x - amount);
};
Array.prototype.mult = function (amount: number): number[] {
/**
* @param amount - The value to multiply with each element in the array.
* @returns New array with multiplied values.
*/
return this.map((x: number) => x * amount);
};
Array.prototype.division = function (amount: number): number[] {
/**
* @param amount - The value to divide each element in the array by.
* @returns New array with divided values. Throws if division by zero.
*/
if (amount === 0) throw new Error('Division by zero');
return this.map((x: number) => x / amount);
};
Array.prototype.pick = function () {
/**
@ -37,13 +91,13 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this[Math.floor(api.randomGen() * this.length)];
};
Array.prototype.beat = function () {
Array.prototype.beat = function (beat: number = 1) {
/**
* Returns the element corresponding to the current beat
*
* @returns The element corresponding to the current beat
*/
return this[api.ebeat() % this.length];
return this[(api.ebeat() / beat) % this.length];
};
Array.prototype.bar = function () {
@ -160,7 +214,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this;
};
Array.prototype.repeatAll = function <T>(this: T[], amount: number) {
Array.prototype.repeatAll = function <T>(this: T[], amount: number = 1) {
/**
* Repeats all elements in the array n times.
*
@ -181,7 +235,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this;
};
Array.prototype.repeatPair = function <T>(this: T[], amount: number) {
Array.prototype.repeatPair = function <T>(this: T[], amount: number = 1) {
/**
* Repeats all elements in the array n times, except for the
* elements at odd indexes.
@ -211,7 +265,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this;
};
Array.prototype.repeatOdd = function <T>(this: T[], amount: number) {
Array.prototype.repeatOdd = function <T>(this: T[], amount: number = 1) {
/**
* Repeats all elements in the array n times, except for the
* elements at even indexes.

View File

@ -22,14 +22,14 @@ const samples_to_markdown = (application: Editor) => {
// Adding new examples for each sample folder!
const codeId = `sampleExample${i}`;
application.api.codeExamples[codeId] = `mod(.5) :: sound("${keys[i]}").n(irand(1,100)).end(1).out()`;
application.api.codeExamples[codeId] = `sound("${keys[i]}").n(irand(1, 5)).end(1).out()`;
// @ts-ignore
const howMany = samples[keys[i]].data.samples.length;
markdownList += `
<button
class="hover:bg-neutral-500 inline px-4 py-2 bg-neutral-700 text-orange-300 text-2xl"
onclick="app.api._playDocExample(app.api.codeExamples['${codeId}'])"
class="hover:bg-neutral-500 inline px-4 py-2 bg-neutral-700 text-orange-300 text-xl"
onclick="app.api._playDocExampleOnce(app.api.codeExamples['${codeId}'])"
>
${keys[i]}
<b class="text-white">(${howMany})</b>
@ -82,11 +82,21 @@ Welcome to the Topos documentation. These pages are offering you an introduction
${makeExample(
"Welcome! Eval to get started", `
mod([1/4,1/8,1/16].div(8)):: sound('sine')
.freq([100,50].div(16) + 50 * ($(1)%10))
.gain(0.5).room(0.9).size(0.9)
.sustain(0.1).out()
mod(1) :: sound('kick').out()`,
bpm(110)
mod(0.125) && sound('sawtooth')
.note([60, 62, 63, 67, 70].div(.125) +
[-12,0,12].beat() + [0, 0, 5, 7].bar())
.sustain(0.1).fmi(0.25).fmh(2).room(0.9)
.gain(0.75).cutoff(500 + usine(8) * [500, 1000, 2000].bar())
.delay(0.5).delayt(0.25).delayfb(0.25)
.out();
mod(1) && snd('kick').out();
mod(2) && snd('snare').out();
mod(.5) && snd('hat').out();
`,
true
)}
@ -97,7 +107,11 @@ Topos is an _algorithmic_ sequencer. Topos uses small algorithms to represent mu
${makeExample(
"Small algorithms for direct musical expression",
`mod(1) :: sound(['kick', 'hat', 'snare', 'hat'].div(1)).out()`,
`
mod(1) :: sound(['kick', 'hat', 'snare', 'hat'].div(1)).out()
mod(.5) :: sound('jvbass').note(35 + [0,12].beat()).out()
mod([0.5, 0.25, 1, 2].div(1)) :: sound('east')
.room(.5).size(0.5).n(irand(1,5)).out()`,
false
)}
@ -112,7 +126,11 @@ ${makeExample(
${makeExample(
"Making the web less dreadful, one beep at at time",
`mod(.5) :: sound(['sid', 'crow', 'zap'].pick()).n($(1) % 10).out()`,
`
mod(.5) :: sound('sid').n($(2)).out()
mod(.25) :: sound('sid').note(
[34, 36, 41].div(.25) + [[0,-24].pick(),12].beat())
.room(0.9).size(0.9).n(4).out()`,
false
)}
@ -127,39 +145,32 @@ Press ${key_shortcut(
)}. You are now making music:
${makeExample(
"Drums and arpeggios",
"Obscure shenanigans",
`
bpm(80)
mod(0.25) && sound('sawtooth')
.note(seqbar(
[60, 67, 63].pick() - 12, [60, 67, 63].pick() - 12,
[60, 67, 63].pick() - 12 + 5, [60, 67, 63].pick() - 12 + 5,
[60, 67, 63].pick() - 12 + 7, [60, 67, 63].pick() - 12 + 7) + (sometimes() ? 24 : 12)
)
.sustain(0.1).fmi(8).fmh(4).room(0.9)
.gain(0.75).cutoff(500 + usine(8) * 10000)
.delay(0.5).delaytime(bpm() / 60 / 4 / 3)
.delayfeedback(0.25)
.out();
mod(1) && snd('kick').out();
mod(2) && snd('snare').out();
mod(.5) && snd('hat').out();
`,
mod([1/4,1/8,1/16].div(8)):: sound('sine')
.freq([100,50].div(16) + 50 * ($(1)%10))
.gain(0.5).room(0.9).size(0.9)
.sustain(0.1).out()
mod(1) :: sound('kick').out()
mod(2) :: sound('dr').n(5).out()
div(3) :: mod([.25,.5].div(.5)) :: sound('dr')
.n([8,9].pick()).gain([.8,.5,.25,.1,.0].div(.25)).out()`,
true
)}
${makeExample(
"Resonant madness",
`mod(.5)::snd('synth2')
.freq([50,50*1.25,50*1.5,50*1.75].div(8) / 2)
.cutoff(usine(.5) * 5000).resonance(15).end(0.8).room(0.9).size(0.9).n(7).out();
mod(.25)::snd('synth2')
.freq([50,50*1.25,50*1.5,50*1.75].div(.5))
.cutoff(usine(.5) * 5000).resonance(15)
.end(0.2).room(0.9).size(0.9).n(14).out()
mod(1)::snd('kick').out()
mod(2)::snd('snare').shape(0.5).out()
mod(.75)::snd('hat').shape(0.4).out()`,
`
mod(.25)::snd('arpy')
.note(30 + [0,3,7,10].beat())
.cutoff(usine(.5) * 5000).resonance(10).gain(0.3)
.end(0.8).room(0.9).size(0.9).n(0).out();
mod([.25,.125].div(2))::snd('arpy')
.note(30 + [0,3,7,10].beat())
.cutoff(usine(.5) * 5000).resonance(20).gain(0.3)
.end(0.8).room(0.9).size(0.9).n(3).out();
mod(.5) :: snd('arpy').note(
[30, 33, 35].repeatAll(4).div(1) - [12,0].div(0.5)).out()`,
false
)}
`;
@ -253,15 +264,17 @@ Let's study two very simple rhythmic functions, <icode>mod(n: ...number[])</icod
${makeExample(
"Using different mod values",
`// This code is alternating between different mod values
mod([1,1/2,1/4,1/8,1/16].div(4)) :: sound('kick').out()
`
// This code is alternating between different mod values
mod([1,1/2,1/4,1/8].div(2)) :: sound('bd').n(0).out()
`,
true
)}
${makeExample(
"Some sort of ringtone",
`let blip = (freq) => {return sound('sine').sustain(0.1).freq(freq)};
`
let blip = (freq) => {return sound('sine').sustain(0.1).freq(freq)};
mod(1) :: blip(200).out();
mod(1/3) :: blip(400).out();
div(3) :: mod(1/6) :: blip(800).out();
@ -269,12 +282,33 @@ mod([1,0.75].div(2)) :: blip([50, 100].div(2)).out();
`,
false
)}
- <icode>modp(...n: number[])</icode>: extreme version of the <icode>mod</icode> function. Instead of being normalised, this function is returning a modulo of real pulses! It can be used to break out of ratios and play with real clock pulses for unexpected results.
${makeExample(
"Intriguing rhythms",
`
modp(36) :: snd('east')
.n([2,4].div(1)).out()
modp([12, 36].div(4)) :: snd('east')
.n([2,4].add(5).div(1)).out()
`,
true
)}
${makeExample(
"modp is the OG rhythmic function in Topos",
`
modp([48, 24, 16].div(4)) :: sound('linnhats').out()
mod(1)::snd('bd').out()
`, false)};
- <icode>onbeat(...n: number[])</icode>: By default, the bar is set in <icode>4/4</icode> with four beats per bar. The <icode>onbeat</icode> function allows you to lock on to a specific beat to execute some code. It can accept multiple arguments. It's usage is very straightforward and not hard to understand. You can pass integers or floating point numbers.
${makeExample(
"Some simple yet detailed rhythms",
`onbeat(1,2,3,4)::snd('kick').out() // Bassdrum on each beat
`
onbeat(1,2,3,4)::snd('kick').out() // Bassdrum on each beat
onbeat(2,4)::snd('snare').out() // Snare on acccentuated beats
onbeat(1.5,2.5,3.5, 3.75)::snd('hat').out() // Cool high-hats
`,
@ -283,7 +317,8 @@ onbeat(1.5,2.5,3.5, 3.75)::snd('hat').out() // Cool high-hats
${makeExample(
"Let's do something more complex",
`onbeat(0.5, 1.5, 2, 3, 3.75)::snd('kick').n(2).out()
`
onbeat(0.5, 1.5, 2, 3, 3.75)::snd('kick').n(2).out()
onbeat(2, [1.5, 3].pick(), 4)::snd('snare').n(7).out()
mod([.25, 1/8].div(1.5))::snd('hat').n(2)
.gain(rand(0.4, 0.7))
@ -311,8 +346,10 @@ mod(.5) && euclid($(2), 2, 8) && snd('sd').out()
${makeExample(
"And now for more interesting rhythmic constructions",
`
mod(.5) && euclid($(1), 5, 9) && snd('kick').out()
mod(.5) && euclid($(2), 2, 3, 1) && snd('pluck').end(0.5).n(5).out()
bpm(145); // Setting a faster BPM
mod(.5) && euclid($(1), 5, 8) :: sound('bd').out()
mod(.5) && euclid($(2), [1,0].div(8), 8) :: sound('sd').out()
mod(.5) && euclid($(6), [6,7].div(8), 8) :: sound('hh').out()
`,
false
)}
@ -321,15 +358,29 @@ ${makeExample(
"Adding more rhythmic density",
`
mod(.5) && euclid($(1), 5, 9) && snd('kick').out()
mod(.5) && euclid($(2), 2, 3, 1) && snd('pluck').end(0.5).n(5).out()
mod(.5) && euclid($(3), 6, 9, 1) && snd('pluck').end(0.5).n(5).freq(200).out()
mod(.5) && euclid($(2), 2, 3, 1) && snd('east').end(0.5).n(5).out()
mod(.5) && euclid($(3), 6, 9, 1) && snd('east').end(0.5).n(5).freq(200).out()
mod(.25) && euclid($(4), 7, 9, 1) && snd('hh').out()
`,
false
)}
- <icode>rhythm(divisor: number, pulses: number, length: number, rotate: number): boolean</icode>: generates <icode>true</icode> or <icode>false</icode> values from an euclidian rhythm sequence. This is another version of <icode>euclid</icode> that does not take an iterator.
${makeExample(
"rhythm is a beginner friendly rhythmic function!",
`
let speed = [0.5, 0.25].div(8);
rhythm(speed, 5, 12) :: snd('east').n(2).out()
rhythm(speed, 2, 12) :: snd('east').out()
rhythm(speed, 3, 12) :: snd('east').n(4).out()
rhythm(speed, 7, 12) :: snd('east').n(9).out()
`,
true
)}
- <icode>bin(iterator: number, n: number): boolean</icode>: a binary rhythm generator. It transforms the given number into its binary representation (_e.g_ <icode>34</icode> becomes <icode>100010</icode>). It then returns a boolean value based on the iterator in order to generate a rhythm.
${makeExample(
@ -343,15 +394,17 @@ mod(.5) && bin($(2), 48) && snd('sd').out()
${makeExample(
"Calling 911",
`mod(.5) && bin($(1), 911) && snd('pluck').n(4).delay(0.5).delayt(0.25).out()
mod(1) && sound('kick').shape(0.5).out()
`
mod(.5) && bin($(1), 911) && snd('subroc3d').n($(2)).delay(0.5).delayt(0.25).end(0.5).out()
mod(.5) && sound('less').n(irand(1, 10)).out()
`,
false
)}
${makeExample(
"Playing around with simple numbers",
`mod(.5) && bin($(1), [123, 456, 789].div(4))
`
mod(.5) && bin($(1), [123, 456, 789].div(4))
&& snd('tabla').n($(2)).delay(0.5).delayt(0.25).out()
mod(1) && sound('kick').shape(0.5).out()
`,
@ -424,7 +477,8 @@ mod(.5)::snd(div(2) ? 'kick' : 'hat').out()
${makeExample(
"div is great for pretty much everything",
`div([1, .5].beat()) :: mod(.25) :: sound('shaker').out();
`
div([1, .5].beat()) :: mod(.25) :: sound('shaker').out();
div([4, .5].beat()) :: mod(.25) :: sound('shaker').speed(2).out();
div([1, 2].beat()) :: mod(1.75) :: sound('snare').out();
div(4) :: mod(.5) :: sound('tom').out()
@ -446,9 +500,16 @@ divbar(3)::mod(.5)::snd('hat').out()
`,
true
)}
${makeExample(
"Alternating over four bars",
`
divbar(2)
? mod(.5) && snd(['kick', 'hh'].div(1)).out()
: mod(.5) && snd(['east', 'snare'].div(1)).out()
`, false)};
- <icode>onbar(n: number, ...bar: number[])</icode>: The first argument, <icode>n</icode>, is used to divide the time in a period of <icode>n</icode> consecutive bars. The following arguments are bar numbers to play on. For example, <icode>onbar(5, 1, 4)</icode> will return <icode>true</icode> on bar <icode>1</icode> and <icode>4</icode> but return <icode>false</icode> the rest of the time. You can easily divide time that way.
- <icode>onbar(bars: number | number[], n: number)</icode>: The second argument, <icode>n</icode>, is used to divide the time in a period of <icode>n</icode> consecutive bars. The first argument should be a bar number or a list of bar numbers to play on. For example, <icode>onbar([1, 4], 5)</icode> will return <icode>true</icode> on bar <icode>1</icode> and <icode>4</icode> but return <icode>false</icode> the rest of the time. You can easily divide time that way.
${makeExample(
"Using onbar for filler drums",
@ -457,7 +518,7 @@ ${makeExample(
onbar(4, 4)::mod(.5)::snd('hh').out();
// Here comes a longer version using JavaScript normal control flow
if (onbar(4, 1, 3)) {
if (onbar([4, 1], 3)) {
mod(1)::snd('kick').out();
} else {
mod(.5)::snd('sd').out();
@ -465,7 +526,7 @@ if (onbar(4, 1, 3)) {
`,
true
)}
## What are pulses?
To make a beat, you need a certain number of time grains or **pulses**. The **pulse** is also known as the [PPQN](https://en.wikipedia.org/wiki/Pulses_per_quarter_note). By default, Topos is using a _pulses per quarter note_ of 48. You can change it by using the <icode>ppqn(number)</icode> function. It means that the lowest possible rhythmic value is 1/48 of a quarter note. That's plenty of time already.
@ -606,7 +667,8 @@ The basic function to play a sound is... <icode>sound(name: string)</icode> (you
${makeExample(
"Playing sounds is easy",
`mod(1) && sound('bd').out()
`
mod(1) && sound('bd').out()
mod(0.5) && sound('hh').out()
`,
true
@ -621,7 +683,8 @@ Let's make it slightly more complex:
${makeExample(
"Adding some effects",
`mod(1) && sound('bd').coarse(0.25).out();
`
mod(1) && sound('bd').coarse(0.25).room(0.5).orbit(2).out();
mod(0.5) && sound('hh').delay(0.25).delaytime(0.125).out();
`,
true
@ -640,12 +703,14 @@ Let's pause for a moment to explain what we just wrote. There are many things to
${makeExample(
'"Composing" a sound or making a sound chain',
`mod(1) :: sound('pad')
`
mod(1) :: sound('pad')
.begin(rand(0, 0.4))
.freq([50,52].beat())
.size(0.9)
.room(0.9)
.pan(sine()).release(2).out()`,
.velocity(0.25)
.pan(usine()).release(2).out()`,
true
)}
@ -672,7 +737,9 @@ When you type <icode>kick</icode> in the <icode>sound('kick').out()</icode> expr
The <icode>.n(number)</icode> method can be used to pick a sample from the currently selected sample folder. For instance, the following script will play a random sample from the _kick_ folder:
${makeExample(
"Picking a sample",
`mod(1) && sound('kick').n([1,2,3,4,5,6,7,8].pick()).out()`,
`
mod(1) && sound('kick').n([1,2,3,4,5,6,7,8].pick()).out()
`,
true
)}
@ -680,7 +747,8 @@ Don't worry about the number. If it gets too big, it will be automatically wrapp
${makeExample(
"Picking a sample... with your mouse!",
`// Move your mouse to change the sample being used!
`
// Move your mouse to change the sample being used!
mod(.25) && sound('numbers').n(Math.floor(mouseX())).out()`,
true
)}
@ -692,11 +760,12 @@ mod(.25) && sound('numbers').n(Math.floor(mouseX())).out()`,
As we said earlier, the <icode>sound('sample_name')</icode> function can be chained to _specify_ a sound more. For instance, you can add a filter and some effects to your high-hat:
${makeExample(
"Learning through repetition",
`mod(0.5) && sound('hh')
.sometimes(s=>s.speed([1,5,10].pick()))
.room(0.5)
.cutoff(usine(2) * 5000)
.out()`,
`
mod(0.5) && sound('hh')
.sometimes(s=>s.speed([1,5,10].pick()))
.room(0.5)
.cutoff(usine(2) * 5000)
.out()`,
true
)}
@ -748,11 +817,11 @@ Note that the **sustain** value is not a duration but an amplitude value (how lo
${makeExample(
"Simple synthesizer",
`
mod(4)::sound('sawtooth').note(50).decay(0.5).sustain(0.5).release(2).out();
mod(2)::sound('sawtooth').note(50+7).decay(0.5).sustain(0.6).release(2).out();
mod(1)::sound('sawtooth').note(50+12).decay(0.5).sustain(0.7).release(2).out();
mod(4)::sound('sawtooth').note(50).decay(0.5).sustain(0.5).release(2).gain(0.25).out();
mod(2)::sound('sawtooth').note(50+7).decay(0.5).sustain(0.6).release(2).gain(0.25).out();
mod(1)::sound('sawtooth').note(50+12).decay(0.5).sustain(0.7).release(2).gain(0.25).out();
mod(.25)::sound('sawtooth').note([50,57,62].pick() + [12, 24, 0].div(2))
.cutoff(5000).sustain(0.5).release(0.1).out()
.cutoff(5000).sustain(0.5).release(0.1).gain(0.25).out()
`,
true
)};
@ -804,9 +873,9 @@ ${makeExample(
"Filter sweep using a low frequency oscillator",
`
mod(.5) && snd('sawtooth')
.cutoff([2000,500].pick() + usine(.5) * 4000)
.resonance(0.9).freq([100,150].pick())
.out()
.cutoff([2000,500].pick() + usine(.5) * 4000)
.resonance(0.9).freq([100,150].pick())
.out()
`,
true
)};
@ -895,7 +964,8 @@ JavaScript is using [Arrays](https://developer.mozilla.org/en-US/docs/Web/JavaSc
${makeExample(
"Light drumming",
`// Every bar, use a different rhythm
`
// Every bar, use a different rhythm
mod([1, 0.75].div(4)) :: sound('cp').out()
mod([0.5, 1].div(4)) :: sound('kick').out()
mod(2)::snd('snare').shape(.5).out()
@ -904,7 +974,8 @@ mod(2)::snd('snare').shape(.5).out()
)}
${makeExample(
"Using div to create arpeggios",
`// Arpeggio using pulse divisions
`
// Arpeggio using pulse divisions
mod([.5, .25].div(2)) :: sound('sine')
.hcutoff(400)
.fmi([1,2].div(8))
@ -920,7 +991,8 @@ mod([.5, .25].div(2)) :: sound('sine')
)}
${makeExample(
"Cool ambiance",
`mod(.5) :: snd(['kick', 'hat'].div(4)).out()
`
mod(.5) :: snd(['kick', 'hat'].div(4)).out()
mod([2,4].div(2)) :: snd('shaker').delay(.5).delayfb(.75).delayt(0.125).out()
div(2)::mod(1)::snd('clap').out()
div(4)::mod(2)::snd('pad').n(2).shape(.5).orbit(2).room(0.9).size(0.9).release(0.5).out()
@ -936,7 +1008,8 @@ div(4)::mod(2)::snd('pad').n(2).shape(.5).orbit(2).room(0.9).size(0.9).release(0
${makeExample(
"A simple drumbeat in no time!",
`mod(1)::sound(['kick', 'hat', 'snare', 'hat'].beat()).out()
`
mod(1)::sound(['kick', 'hat', 'snare', 'hat'].beat()).out()
mod(1.5)::sound(['jvbass', 'clap'].beat()).out()
`,
true
@ -957,7 +1030,8 @@ mod([1, 0.5].beat()) :: sound(['bass3'].bar())
${makeExample(
"Palindrome filter sweep",
`mod([1,.5,.25].beat()) :: snd('sine')
`
mod([1,.5,.25].beat()) :: snd('sine')
.freq([100,200,300].div(0.25))
.fmi([1,2,3].palindrome().div(0.5))
.fmh([4, 8].palindrome().beat())
@ -976,7 +1050,8 @@ ${makeExample(
${makeExample(
"Sipping some gasoline at the robot bar",
`mod(1)::snd('kick').shape(0.5).out()
`
mod(1)::snd('kick').shape(0.5).out()
mod([.5, 1].random() / 2) :: snd(
['amencutup', 'synth2'].random())
.n(irand(4,10))
@ -991,7 +1066,8 @@ mod([.5, 1].random() / 2) :: snd(
${makeExample(
"Amen break suffering from data loss",
`// Tweak the value to degrade this amen break even more!
`
// Tweak the value to degrade this amen break even more!
mod(.25)::snd('amencutup').n([1,2,3,4,5,6,7,8,9].degrade(20).loop($(1))).out()
`,
true
@ -1003,7 +1079,8 @@ mod(.25)::snd('amencutup').n([1,2,3,4,5,6,7,8,9].degrade(20).loop($(1))).out()
${makeExample(
"Repeating samples a given number of times",
`// Please take this repeat number down a bit!
`
// Please take this repeat number down a bit!
mod(.25)::sound('amencutup').n([1,2,3,4,5,6,7,8].repeatAll(4).beat()).out()
`,
true
@ -1013,7 +1090,9 @@ mod(.25)::sound('amencutup').n([1,2,3,4,5,6,7,8].repeatAll(4).beat()).out()
${makeExample(
"Don't you know how to count up to 5?",
`mod(1) :: sound('numbers').n([1,2,3,4,5].loop($(3, 10, 2))).out()`,
`
mod(1) :: sound('numbers').n([1,2,3,4,5].loop($(3, 10, 2))).out()
`,
true
)}
@ -1021,7 +1100,8 @@ ${makeExample(
${makeExample(
"Shuffling a list for extra randomness",
`mod(1) :: sound('numbers').n([1,2,3,4,5].shuffle().loop($(1)).out()
`
mod(1) :: sound('numbers').n([1,2,3,4,5].shuffle().loop($(1)).out()
`,
true
)}
@ -1030,10 +1110,11 @@ ${makeExample(
${makeExample(
"To make things more complex... here you go",
`mod(.5) :: snd('sine')
`
mod(.5) :: snd('sine')
.freq([100, 150, 200, 250, ,300, 400]
.rotate([1,2,3].bar()) // The list of frequencies is rotating
.beat()) // while being indexed over!
.rotate([1,2,3].bar()) // The list of frequencies is rotating
.beat()) // while being indexed over!
.sustain(0.1)
.out()
`,
@ -1044,12 +1125,23 @@ ${makeExample(
${makeExample(
"Demonstrative filtering. Final list is [100, 200]",
`// Remove unique and 100 will repeat four times!
`
// Remove unique and 100 will repeat four times!
mod(1)::snd('sine').sustain(0.1).freq([100,100,100,100,200].unique().beat()).out()
`,
true
)}
- <icode>add()</icode>: add a given amount to every list element.
- <icode>sub()</icode>: add a given amount to every list element.
- <icode>mult()</icode>: add a given amount to every list element.
- <icode>division()</icode>: add a given amount to every list element. The method is named <icode>division</icode> because obviously <icode>div</icode> is already taken.
${makeExample(
"Simple addition",
`[1, 2 ,3].add(2).beat()`,
true
)}
## Simple patterns
@ -1248,7 +1340,8 @@ The <icode>sound</icode> function can take the name of a synthesizer as first ar
${makeExample(
"Simple synthesizer voice with filter",
`mod(.5) && snd('sawtooth')
`
mod(.5) && snd('sawtooth')
.cutoff([2000,500].pick() + usine(.5) * 4000)
.resonance(0.9).freq([100,150].pick())
.out()
@ -1258,8 +1351,9 @@ ${makeExample(
${makeExample(
"Listening to the different waveforms from the sweetest to the harshest",
`mod(.5) && snd(['sine', 'triangle', 'sawtooth', 'square'].beat()).freq(100).out()
.freq(50)
`
mod(.5) && snd(['sine', 'triangle', 'sawtooth', 'square'].beat()).freq(100).out()
.freq(50)
.out()
`,
false
@ -1268,7 +1362,8 @@ ${makeExample(
${makeExample(
"Blessed by the square wave",
`mod(4) :: [100,101].forEach((freq) => sound('square').freq(freq).sustain(0.1).out())
`
mod(4) :: [100,101].forEach((freq) => sound('square').freq(freq).sustain(0.1).out())
mod(.5) :: [100,101].forEach((freq) => sound('square').freq(freq*2).sustain(0.01).out())
mod([.5, .75, 2].beat()) :: [100,101].forEach((freq) => sound('square')
.freq(freq*4 + usquare(2) * 200).sustain(0.125).out())
@ -1279,11 +1374,14 @@ mod(.25) :: sound('square').freq(100*[1,2,4,8].beat()).sustain(0.1).out()`,
${makeExample(
"Ghost carillon",
`mod(1/8)::sound('sine')
`
mod(1/8)::sound('sine')
.velocity(rand(0.0, 1.0))
.delay(0.75).delayt(.5)
.sustain(0.4)
.cutoff(2000)
.freq(mouseX())
.gain(0.25)
.out()`,
false
)}
@ -1298,7 +1396,8 @@ The same basic waveforms can take additional methods to switch to a basic two op
${makeExample(
"80s nostalgia",
`mod(.25) && snd('sine')
`
mod(.25) && snd('sine')
.fmi([1,2,4,8].pick())
.fmh([1,2,4,8].div(8))
.freq([100,150].pick())
@ -1310,7 +1409,8 @@ ${makeExample(
${makeExample(
"Giving some love to weird ratios",
`mod([.5, .25].bar()) :: sound('sine').fm('2.2183:3.18293').sustain(0.05).out()
`
mod([.5, .25].bar()) :: sound('sine').fm('2.2183:3.18293').sustain(0.05).out()
mod([4].bar()) :: sound('sine').fm('5.2183:4.5').sustain(0.05).out()
mod(.5) :: sound('sine')
.fmh([1, 1.75].beat())
@ -1321,9 +1421,10 @@ mod(.5) :: sound('sine')
${makeExample(
"Some peace and serenity",
`mod(0.25) :: sound('sine')
.note([60, 67, 70, 72, 77].beat())
.attack(0.2).release(0.5).gain(0.5)
`
mod(0.25) :: sound('sine')
.note([60, 67, 70, 72, 77].beat() - [0,12].bar())
.attack(0.2).release(0.5).gain(0.25)
.room(0.9).size(0.8).sustain(0.5)
.fmi(Math.floor(usine(.25) * 10))
.cutoff(1500).delay(0.5).delayt(0.125)
@ -1383,16 +1484,23 @@ There are some techniques that Topos players are using to keep their JavaScript
${makeExample(
"Shortening your if conditions",
`// The && symbol (overriden by :: in Topos) is very often used for conditions!
mod(.75) :: snd('zap').out()
`
// The && symbol (overriden by :: in Topos) is very often used for conditions!
mod(.75) :: snd('linnhats').n([1,4,5].beat()).out()
mod(1) :: snd('bd').out()
//if (true) && log('very true')
// These two lines are the same:
// mod(1) && snd('bd').out()
//// mod(1) :: snd('bd').out()
`,
true
)}
${makeExample(
"More complex conditions using ?",
`// The ? symbol can be used to write a if/true/false condition
`
// The ? symbol can be used to write a if/true/false condition
mod(4) ? snd('kick').out() : mod(2)::snd('snare').out()
// (true) ? log('very true') : log('very false')
`,
@ -1402,7 +1510,8 @@ mod(4) ? snd('kick').out() : mod(2)::snd('snare').out()
${makeExample(
"Using not and other short symbols",
`// The ! symbol can be used to reverse a condition
`
// The ! symbol can be used to reverse a condition
mod(4) ? snd('kick').out() : mod(2)::snd('snare').out()
!mod(2) :: mod(0.5)::snd('clap').out()
`,
@ -1477,36 +1586,38 @@ Low Frequency Oscillators (_LFOs_) are an important piece in any digital audio w
- <icode>sine(freq: number = 1, offset: number= 0): number</icode>: returns a sinusoïdal oscillation between <icode>-1</icode> and <icode>1</icode>.
- <icode>usine(freq: number = 1, offset: number= 0): number</icode>: returns a sinusoïdal oscillation between <icode>0</icode> and <icode>1</icode>. The <icode>u</icode> stands for _unipolar_.
\`\`\`javascript
mod(.25) && snd('cp').speed(1 + usine(0.25) * 2).out()
\`\`\`
${makeExample(
"Modulating the speed of a sample player using a sine LFO", 
`mod(.25) && snd('cp').speed(1 + usine(0.25) * 2).out()`, true)};
- <icode>triangle(freq: number = 1, offset: number= 0): number</icode>: returns a triangle oscillation between <icode>-1</icode> and <icode>1</icode>.
- <icode>utriangle(freq: number = 1, offset: number= 0): number</icode>: returns a triangle oscillation between <icode>0</icode> and <icode>1</icode>. The <icode>u</icode> stands for _unipolar_.
\`\`\`javascript
mod(.25) && snd('cp').speed(1 + utriangle(0.25) * 2).out()
\`\`\`
${makeExample(
"Modulating the speed of a sample player using a triangle LFO", 
`mod(.25) && snd('cp').speed(1 + utriangle(0.25) * 2).out()`, true)}
- <icode>saw(freq: number = 1, offset: number= 0): number</icode>: returns a sawtooth-like oscillation between <icode>-1</icode> and <icode>1</icode>.
- <icode>usaw(freq: number = 1, offset: number= 0): number</icode>: returns a sawtooth-like oscillation between <icode>0</icode> and <icode>1</icode>. The <icode>u</icode> stands for _unipolar_.
\`\`\`javascript
mod(.25) && snd('cp').speed(1 + usaw(0.25) * 2).out()
\`\`\`
${makeExample(
"Modulating the speed of a sample player using a saw LFO", 
`mod(.25) && snd('cp').speed(1 + usaw(0.25) * 2).out()`, true)}
- <icode>square(freq: number = 1, offset: number= 0, duty: number = .5): number</icode>: returns a square wave oscillation between <icode>-1</icode> and <icode>1</icode>. You can also control the duty cycle using the <icode>duty</icode> parameter.
- <icode>usquare(freq: number = 1, offset: number= 0, duty: number = .5): number</icode>: returns a square wave oscillation between <icode>0</icode> and <icode>1</icode>. The <icode>u</icode> stands for _unipolar_. You can also control the duty cycle using the <icode>duty</icode> parameter.
\`\`\`javascript
mod(.25) && snd('cp').speed(1 + usquare(0.25, 0, 0.25) * 2).out()
\`\`\`
${makeExample(
"Modulating the speed of a sample player using a square LFO", 
`mod(.25) && snd('cp').speed(1 + usquare(0.25, 0, 0.25) * 2).out()`,true)};
- <icode>noise()</icode>: returns a random value between -1 and 1.
\`\`\`javascript
mod(.25) && snd('cp').speed(1 + noise() * 2).out()
\`\`\`
${makeExample(
"Modulating the speed of a sample player using noise", 
`mod(.25) && snd('cp').speed(1 + noise() * 2).out()`, true)};
## Probabilities

View File

@ -95,3 +95,17 @@ export const evaluate = async (
console.log(error);
}
};
export const evaluateOnce = async (
application: Editor,
code: string
): Promise<void> => {
/**
* Evaluates the code once without any caching or error-handling mechanisms besides the tryCatchWrapper.
*
* @param application - The application object that contains the Editor API.
* @param code - The code to be evaluated.
* @returns A promise that resolves when the code has been evaluated.
*/
await tryCatchWrapper(application, code);
};

55
src/examples/excerpts.ts Normal file
View File

@ -0,0 +1,55 @@
export const examples = [
`
// Ancient rhythms - Bubobubobubo
mod(1)::snd('kick').out();
mod(2)::snd('sd').room(0.9).size(0.9).out();
mod(0.25)::snd('hh').out();
mod(2)::snd('square')
.cutoff(500).note(50-12).resonance(20).sustain(0.2).out()
mod(1/4)::snd(divseq(1, 'sawtooth', 'triangle', 'pulse'))
.note(divseq(4, 50, 53, 55, 50, 50, 52, 58, 50+12, 50+15) + divseq(0.5, 0, 12, 24))
.cutoff(usine(.5)*10000).resonance(divseq(2, 10,20))
.fmi($(1) % 10).fmh($(2) % 5)
.room(0.8).size(0.9)
.delay(0.5).delaytime(0.25)
.delayfb(0.6)
.sustain(0.01 + usine(.25) / 10).out()
mod(4)::snd('amencutup').n($(19)).cut(1).orbit(2).pan(rand(0.0,1.0)).out()
log(bar(), beat(), pulse())`,
`
// Crazy arpeggios - Bubobubobubo
bpm(110)
mod(0.125) && sound('sawtooth')
.note([60, 62, 63, 67, 70].div(.125) +
[-12,0,12].beat() + [0, 0, 5, 7].bar())
.sustain(0.1).fmi(0.25).fmh(2).room(0.9)
.gain(0.75).cutoff(500 + usine(8) * [500, 1000, 2000].bar())
.delay(0.5).delayt(0.25).delayfb(0.25)
.out();
mod(1) && snd('kick').out();
mod(2) && snd('snare').out();
mod(.5) && snd('hat').out();
`, `
// Obscure Shenanigans - Bubobubobubo
mod([1/4,1/8,1/16].div(8)):: sound('sine')
.freq([100,50].div(16) + 50 * ($(1)%10))
.gain(0.5).room(0.9).size(0.9)
.sustain(0.1).out()
mod(1) :: sound('kick').out()
mod(2) :: sound('dr').n(5).out()
div(3) :: mod([.25,.5].div(.5)) :: sound('dr')
.n([8,9].pick()).gain([.8,.5,.25,.1,.0].div(.25)).out()
`, `
// Resonance bliss - Bubobubobubo
mod(.25)::snd('arpy')
.note(30 + [0,3,7,10].beat())
.cutoff(usine(.5) * 5000).resonance(10).gain(0.3)
.end(0.8).room(0.9).size(0.9).n(0).out();
mod([.25,.125].div(2))::snd('arpy')
.note(30 + [0,3,7,10].beat())
.cutoff(usine(.5) * 5000).resonance(20).gain(0.3)
.end(0.8).room(0.9).size(0.9).n(3).out();
mod(.5) :: snd('arpy').note(
[30, 33, 35].repeatAll(4).div(1) - [12,0].div(0.5)).out()
`
]

View File

@ -3,6 +3,7 @@ import {
colors,
animals,
} from "unique-names-generator";
import { examples } from "./examples/excerpts";
import { EditorState, Compartment } from "@codemirror/state";
import { ViewUpdate, lineNumbers, keymap } from "@codemirror/view";
import { javascript } from "@codemirror/lang-javascript";
@ -90,6 +91,9 @@ export class Editor {
public _mouseX: number = 0;
public _mouseY: number = 0;
// Topos Logo
topos_logo: HTMLElement = document.getElementById('topos-logo') as HTMLElement;
// Transport elements
play_buttons: HTMLButtonElement[] = [
document.getElementById("play-button-1") as HTMLButtonElement,
@ -130,6 +134,10 @@ export class Editor {
close_settings_button: HTMLButtonElement = document.getElementById(
"close-settings-button"
) as HTMLButtonElement;
close_universes_button: HTMLButtonElement = document.getElementById(
"close-universes-button"
) as HTMLButtonElement;
universe_viewer: HTMLDivElement = document.getElementById(
"universe-viewer"
) as HTMLDivElement;
@ -184,9 +192,12 @@ export class Editor {
// Loading the universe from local storage
// ================================================================================
this.selected_universe = this.settings.selected_universe;
this.universe_viewer.innerHTML = `Topos: ${this.selected_universe}`;
this.universes = { ...template_universes, ...this.settings.universes };
this.selected_universe = "Welcome";
this.universe_viewer.innerHTML = `Topos: ${this.selected_universe}`;
let random_example = examples[Math.floor(Math.random() * examples.length)];
this.universes[this.selected_universe].global.committed = random_example;
this.universes[this.selected_universe].global.candidate = random_example;
// ================================================================================
// Audio context and clock
@ -237,9 +248,6 @@ export class Editor {
// ================================================================================
// Building the documentation
// loadSamples().then(() => {
// this.docs = documentation_factory(this);
// });
let pre_loading = async () => { await loadSamples(); };
pre_loading();
this.docs = documentation_factory(this);
@ -307,18 +315,7 @@ export class Editor {
// This is the modal to switch between universes
if (event.ctrlKey && event.key === "b") {
this.hideDocumentation();
let existing_universes = document.getElementById("existing-universes");
let known_universes = Object.keys(this.universes);
let final_html = "<ul class='lg:h-80 lg:w-80 lg:pb-2 lg:pt-2 overflow-y-scroll text-white lg:mb-4 border rounded-lg bg-gray-800'>";
known_universes.forEach((name) => {
final_html += `
<li onclick="_loadUniverseFromInterface('${name}')" class="hover:fill-black hover:bg-white py-2 hover:text-black flex justify-between px-4">
<p >${name}</p>
<button onclick=_deleteUniverseFromInterface('${name}')>🗑</button>
</li>`;
});
final_html = final_html + "</ul>";
existing_universes!.innerHTML = final_html;
this.updateKnownUniversesView();
this.openBuffersModal();
}
@ -408,6 +405,12 @@ export class Editor {
});
}
this.topos_logo.addEventListener("click", () => {
this.hideDocumentation();
this.updateKnownUniversesView();
this.openBuffersModal();
})
this.play_buttons.forEach((button) => {
button.addEventListener("click", () => {
if (this.isPlaying) {
@ -495,6 +498,10 @@ export class Editor {
editor?.classList.remove("invisible");
});
this.close_universes_button.addEventListener("click", () => {
this.openBuffersModal();
});
this.font_size_slider.addEventListener("input", () => {
const new_value = this.font_size_slider.value;
this.settings.font_size = parseInt(new_value);
@ -665,6 +672,21 @@ export class Editor {
return JSON.parse(hash);
};
updateKnownUniversesView = () => {
let existing_universes = document.getElementById("existing-universes");
let known_universes = Object.keys(this.universes);
let final_html = "<ul class='lg:h-80 lg:w-80 lg:pb-2 lg:pt-2 overflow-y-scroll text-white lg:mb-4 border rounded-lg bg-gray-800'>";
known_universes.forEach((name) => {
final_html += `
<li onclick="_loadUniverseFromInterface('${name}')" class="hover:fill-black hover:bg-white py-2 hover:text-black flex justify-between px-4">
<p >${name}</p>
<button onclick=_deleteUniverseFromInterface('${name}')>🗑</button>
</li>`;
});
final_html = final_html + "</ul>";
existing_universes!.innerHTML = final_html;
}
share() {
const hashed_table = btoa(
JSON.stringify({
@ -744,6 +766,7 @@ export class Editor {
button.children[0].classList.remove("text-white");
button.children[0].classList.add("text-orange-300");
button.classList.add("text-orange-300");
button.classList.add("fill-orange-300");
};
switch (mode) {
@ -968,12 +991,8 @@ export class Editor {
}
}
// Creating the application
const app = new Editor();
// When the user leaves the page, all the universes should be saved in the localStorage
window.addEventListener("beforeunload", () => {
// @ts-ignore
event.preventDefault();
@ -984,11 +1003,3 @@ window.addEventListener("beforeunload", () => {
app.clock.stop();
return null;
});
// function reportMouseCoordinates(event: MouseEvent) {
// app._mouseX = event.clientX;
// app._mouseY = event.clientY;
// }
// onmousemove = function(e){console.log("mouse location:", e.clientX, e.clientY)}