Merge branch 'main' of github.com:Bubobubobubobubo/Topos
This commit is contained in:
36
src/API.ts
36
src/API.ts
@ -1,6 +1,6 @@
|
||||
import { seededRandom } from "zifferjs";
|
||||
import { MidiConnection } from "./IO/MidiConnection";
|
||||
import { tryEvaluate } from "./Evaluator";
|
||||
import { tryEvaluate, evaluateOnce } from "./Evaluator";
|
||||
import { DrunkWalk } from "./Utils/Drunk";
|
||||
import { scale } from "./Scales";
|
||||
import { Editor } from "./main";
|
||||
@ -66,7 +66,7 @@ export class UserAPI {
|
||||
this.app.universes,
|
||||
this.app.settings
|
||||
);
|
||||
this.app.openBuffersModal();
|
||||
this.app.updateKnownUniversesView();
|
||||
}
|
||||
|
||||
_playDocExample = (code?: string) => {
|
||||
@ -80,6 +80,13 @@ export class UserAPI {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
_playDocExampleOnce = (code?: string) => {
|
||||
this.play();
|
||||
console.log("Executing documentation example: " + this.app.selectedExample);
|
||||
evaluateOnce(this.app, code as string);
|
||||
};
|
||||
|
||||
_all_samples = (): object => {
|
||||
return soundMap.get();
|
||||
};
|
||||
@ -884,7 +891,7 @@ export class UserAPI {
|
||||
const results: boolean[] = n.map((value) => this.epulse() % value === 0);
|
||||
return results.some((value) => value === true);
|
||||
};
|
||||
pmod = this.modpulse;
|
||||
modp = this.modpulse;
|
||||
|
||||
public modbar = (...n: number[]): boolean => {
|
||||
const results: boolean[] = n.map(
|
||||
@ -908,12 +915,12 @@ export class UserAPI {
|
||||
return current_chunk % 2 === 0;
|
||||
};
|
||||
|
||||
onbar = (n: number, ...bar: number[]): boolean => {
|
||||
// n is acting as a modulo on the bar number
|
||||
const bar_list = [...Array(n).keys()].map((i) => i + 1);
|
||||
console.log(bar.some((b) => bar_list.includes(b % n)));
|
||||
return bar.some((b) => bar_list.includes(b % n));
|
||||
};
|
||||
public onbar = (bars: number[] | number, n: number = this.app.clock.time_signature[0]): boolean => {
|
||||
let current_bar = (this.bar() % n) + 1;
|
||||
return (typeof bars === "number")
|
||||
? bars === current_bar
|
||||
: bars.some((b) => b == current_bar)
|
||||
};
|
||||
|
||||
onbeat = (...beat: number[]): boolean => {
|
||||
/**
|
||||
@ -991,7 +998,16 @@ export class UserAPI {
|
||||
*/
|
||||
return this._euclidean_cycle(pulses, length, rotate)[iterator % length];
|
||||
};
|
||||
ec = this.euclid;
|
||||
ec: Function = this.euclid;
|
||||
|
||||
public rhythm = (
|
||||
div: number,
|
||||
pulses: number,
|
||||
length: number,
|
||||
rotate: number = 0
|
||||
): boolean => {
|
||||
return this.mod(div) && this._euclidean_cycle(pulses, length, rotate).div(div);
|
||||
}
|
||||
|
||||
_euclidean_cycle(
|
||||
pulses: number,
|
||||
|
||||
@ -69,7 +69,7 @@ export const template_universe = {
|
||||
};
|
||||
|
||||
export const template_universes = {
|
||||
Default: {
|
||||
Welcome: {
|
||||
global: { candidate: "", committed: "", evaluations: 0 },
|
||||
locals: {
|
||||
1: { candidate: "", committed: "", evaluations: 0 },
|
||||
@ -83,7 +83,7 @@ export const template_universes = {
|
||||
9: { candidate: "", committed: "", evaluations: 0 },
|
||||
},
|
||||
init: { candidate: "", committed: "", evaluations: 0 },
|
||||
notes: { candidate: "// NOTES" },
|
||||
notes: { candidate: "" },
|
||||
},
|
||||
Help: tutorial_universe,
|
||||
};
|
||||
|
||||
@ -3,6 +3,10 @@ export {};
|
||||
|
||||
declare global {
|
||||
interface Array<T> {
|
||||
add(amount: number): number[];
|
||||
sub(amount: number): number[];
|
||||
mult(amount: number): number[];
|
||||
division(amount: number): number[];
|
||||
palindrome(): T[];
|
||||
random(index: number): T;
|
||||
rand(index: number): T;
|
||||
@ -20,6 +24,8 @@ declare global {
|
||||
rotate(steps: number): this;
|
||||
unique(): this;
|
||||
in(value: T): boolean;
|
||||
square(): number[];
|
||||
sqrt(): number[];
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,6 +33,54 @@ export const makeArrayExtensions = (api: UserAPI) => {
|
||||
Array.prototype.in = function <T>(this: T[], value: T): boolean {
|
||||
return this.includes(value);
|
||||
};
|
||||
|
||||
Array.prototype.square = function (): number[] {
|
||||
/**
|
||||
* @returns New array with squared values.
|
||||
*/
|
||||
return this.map((x: number) => x * x);
|
||||
};
|
||||
|
||||
Array.prototype.sqrt = function (): number[] {
|
||||
/**
|
||||
* @returns New array with square roots of values. Throws if any element is negative.
|
||||
*/
|
||||
if (this.some(x => x < 0)) throw new Error('Cannot take square root of negative number');
|
||||
return this.map((x: number) => Math.sqrt(x));
|
||||
};
|
||||
|
||||
Array.prototype.add = function (amount: number): number[] {
|
||||
/**
|
||||
* @param amount - The value to add to each element in the array.
|
||||
* @returns New array with added values.
|
||||
*/
|
||||
return this.map((x: number) => x + amount);
|
||||
};
|
||||
|
||||
Array.prototype.sub = function (amount: number): number[] {
|
||||
/**
|
||||
* @param amount - The value to subtract from each element in the array.
|
||||
* @returns New array with subtracted values.
|
||||
*/
|
||||
return this.map((x: number) => x - amount);
|
||||
};
|
||||
|
||||
Array.prototype.mult = function (amount: number): number[] {
|
||||
/**
|
||||
* @param amount - The value to multiply with each element in the array.
|
||||
* @returns New array with multiplied values.
|
||||
*/
|
||||
return this.map((x: number) => x * amount);
|
||||
};
|
||||
|
||||
Array.prototype.division = function (amount: number): number[] {
|
||||
/**
|
||||
* @param amount - The value to divide each element in the array by.
|
||||
* @returns New array with divided values. Throws if division by zero.
|
||||
*/
|
||||
if (amount === 0) throw new Error('Division by zero');
|
||||
return this.map((x: number) => x / amount);
|
||||
};
|
||||
|
||||
Array.prototype.pick = function () {
|
||||
/**
|
||||
@ -37,13 +91,13 @@ export const makeArrayExtensions = (api: UserAPI) => {
|
||||
return this[Math.floor(api.randomGen() * this.length)];
|
||||
};
|
||||
|
||||
Array.prototype.beat = function () {
|
||||
Array.prototype.beat = function (beat: number = 1) {
|
||||
/**
|
||||
* Returns the element corresponding to the current beat
|
||||
*
|
||||
* @returns The element corresponding to the current beat
|
||||
*/
|
||||
return this[api.ebeat() % this.length];
|
||||
return this[(api.ebeat() / beat) % this.length];
|
||||
};
|
||||
|
||||
Array.prototype.bar = function () {
|
||||
@ -160,7 +214,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
|
||||
return this;
|
||||
};
|
||||
|
||||
Array.prototype.repeatAll = function <T>(this: T[], amount: number) {
|
||||
Array.prototype.repeatAll = function <T>(this: T[], amount: number = 1) {
|
||||
/**
|
||||
* Repeats all elements in the array n times.
|
||||
*
|
||||
@ -181,7 +235,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
|
||||
return this;
|
||||
};
|
||||
|
||||
Array.prototype.repeatPair = function <T>(this: T[], amount: number) {
|
||||
Array.prototype.repeatPair = function <T>(this: T[], amount: number = 1) {
|
||||
/**
|
||||
* Repeats all elements in the array n times, except for the
|
||||
* elements at odd indexes.
|
||||
@ -211,7 +265,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
|
||||
return this;
|
||||
};
|
||||
|
||||
Array.prototype.repeatOdd = function <T>(this: T[], amount: number) {
|
||||
Array.prototype.repeatOdd = function <T>(this: T[], amount: number = 1) {
|
||||
/**
|
||||
* Repeats all elements in the array n times, except for the
|
||||
* elements at even indexes.
|
||||
|
||||
@ -22,14 +22,14 @@ const samples_to_markdown = (application: Editor) => {
|
||||
|
||||
// Adding new examples for each sample folder!
|
||||
const codeId = `sampleExample${i}`;
|
||||
application.api.codeExamples[codeId] = `mod(.5) :: sound("${keys[i]}").n(irand(1,100)).end(1).out()`;
|
||||
application.api.codeExamples[codeId] = `sound("${keys[i]}").n(irand(1, 5)).end(1).out()`;
|
||||
// @ts-ignore
|
||||
const howMany = samples[keys[i]].data.samples.length;
|
||||
|
||||
markdownList += `
|
||||
<button
|
||||
class="hover:bg-neutral-500 inline px-4 py-2 bg-neutral-700 text-orange-300 text-2xl"
|
||||
onclick="app.api._playDocExample(app.api.codeExamples['${codeId}'])"
|
||||
class="hover:bg-neutral-500 inline px-4 py-2 bg-neutral-700 text-orange-300 text-xl"
|
||||
onclick="app.api._playDocExampleOnce(app.api.codeExamples['${codeId}'])"
|
||||
>
|
||||
${keys[i]}
|
||||
<b class="text-white">(${howMany})</b>
|
||||
@ -82,11 +82,21 @@ Welcome to the Topos documentation. These pages are offering you an introduction
|
||||
|
||||
${makeExample(
|
||||
"Welcome! Eval to get started", `
|
||||
mod([1/4,1/8,1/16].div(8)):: sound('sine')
|
||||
.freq([100,50].div(16) + 50 * ($(1)%10))
|
||||
.gain(0.5).room(0.9).size(0.9)
|
||||
.sustain(0.1).out()
|
||||
mod(1) :: sound('kick').out()`,
|
||||
|
||||
|
||||
bpm(110)
|
||||
mod(0.125) && sound('sawtooth')
|
||||
.note([60, 62, 63, 67, 70].div(.125) +
|
||||
[-12,0,12].beat() + [0, 0, 5, 7].bar())
|
||||
.sustain(0.1).fmi(0.25).fmh(2).room(0.9)
|
||||
.gain(0.75).cutoff(500 + usine(8) * [500, 1000, 2000].bar())
|
||||
.delay(0.5).delayt(0.25).delayfb(0.25)
|
||||
.out();
|
||||
mod(1) && snd('kick').out();
|
||||
mod(2) && snd('snare').out();
|
||||
mod(.5) && snd('hat').out();
|
||||
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
@ -97,7 +107,11 @@ Topos is an _algorithmic_ sequencer. Topos uses small algorithms to represent mu
|
||||
|
||||
${makeExample(
|
||||
"Small algorithms for direct musical expression",
|
||||
`mod(1) :: sound(['kick', 'hat', 'snare', 'hat'].div(1)).out()`,
|
||||
`
|
||||
mod(1) :: sound(['kick', 'hat', 'snare', 'hat'].div(1)).out()
|
||||
mod(.5) :: sound('jvbass').note(35 + [0,12].beat()).out()
|
||||
mod([0.5, 0.25, 1, 2].div(1)) :: sound('east')
|
||||
.room(.5).size(0.5).n(irand(1,5)).out()`,
|
||||
false
|
||||
)}
|
||||
|
||||
@ -112,7 +126,11 @@ ${makeExample(
|
||||
|
||||
${makeExample(
|
||||
"Making the web less dreadful, one beep at at time",
|
||||
`mod(.5) :: sound(['sid', 'crow', 'zap'].pick()).n($(1) % 10).out()`,
|
||||
`
|
||||
mod(.5) :: sound('sid').n($(2)).out()
|
||||
mod(.25) :: sound('sid').note(
|
||||
[34, 36, 41].div(.25) + [[0,-24].pick(),12].beat())
|
||||
.room(0.9).size(0.9).n(4).out()`,
|
||||
false
|
||||
)}
|
||||
|
||||
@ -127,39 +145,32 @@ Press ${key_shortcut(
|
||||
)}. You are now making music:
|
||||
|
||||
${makeExample(
|
||||
"Drums and arpeggios",
|
||||
"Obscure shenanigans",
|
||||
`
|
||||
bpm(80)
|
||||
mod(0.25) && sound('sawtooth')
|
||||
.note(seqbar(
|
||||
[60, 67, 63].pick() - 12, [60, 67, 63].pick() - 12,
|
||||
[60, 67, 63].pick() - 12 + 5, [60, 67, 63].pick() - 12 + 5,
|
||||
[60, 67, 63].pick() - 12 + 7, [60, 67, 63].pick() - 12 + 7) + (sometimes() ? 24 : 12)
|
||||
)
|
||||
.sustain(0.1).fmi(8).fmh(4).room(0.9)
|
||||
.gain(0.75).cutoff(500 + usine(8) * 10000)
|
||||
.delay(0.5).delaytime(bpm() / 60 / 4 / 3)
|
||||
.delayfeedback(0.25)
|
||||
.out();
|
||||
mod(1) && snd('kick').out();
|
||||
mod(2) && snd('snare').out();
|
||||
mod(.5) && snd('hat').out();
|
||||
`,
|
||||
mod([1/4,1/8,1/16].div(8)):: sound('sine')
|
||||
.freq([100,50].div(16) + 50 * ($(1)%10))
|
||||
.gain(0.5).room(0.9).size(0.9)
|
||||
.sustain(0.1).out()
|
||||
mod(1) :: sound('kick').out()
|
||||
mod(2) :: sound('dr').n(5).out()
|
||||
div(3) :: mod([.25,.5].div(.5)) :: sound('dr')
|
||||
.n([8,9].pick()).gain([.8,.5,.25,.1,.0].div(.25)).out()`,
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Resonant madness",
|
||||
`mod(.5)::snd('synth2')
|
||||
.freq([50,50*1.25,50*1.5,50*1.75].div(8) / 2)
|
||||
.cutoff(usine(.5) * 5000).resonance(15).end(0.8).room(0.9).size(0.9).n(7).out();
|
||||
mod(.25)::snd('synth2')
|
||||
.freq([50,50*1.25,50*1.5,50*1.75].div(.5))
|
||||
.cutoff(usine(.5) * 5000).resonance(15)
|
||||
.end(0.2).room(0.9).size(0.9).n(14).out()
|
||||
mod(1)::snd('kick').out()
|
||||
mod(2)::snd('snare').shape(0.5).out()
|
||||
mod(.75)::snd('hat').shape(0.4).out()`,
|
||||
`
|
||||
mod(.25)::snd('arpy')
|
||||
.note(30 + [0,3,7,10].beat())
|
||||
.cutoff(usine(.5) * 5000).resonance(10).gain(0.3)
|
||||
.end(0.8).room(0.9).size(0.9).n(0).out();
|
||||
mod([.25,.125].div(2))::snd('arpy')
|
||||
.note(30 + [0,3,7,10].beat())
|
||||
.cutoff(usine(.5) * 5000).resonance(20).gain(0.3)
|
||||
.end(0.8).room(0.9).size(0.9).n(3).out();
|
||||
mod(.5) :: snd('arpy').note(
|
||||
[30, 33, 35].repeatAll(4).div(1) - [12,0].div(0.5)).out()`,
|
||||
false
|
||||
)}
|
||||
`;
|
||||
@ -253,15 +264,17 @@ Let's study two very simple rhythmic functions, <icode>mod(n: ...number[])</icod
|
||||
|
||||
${makeExample(
|
||||
"Using different mod values",
|
||||
`// This code is alternating between different mod values
|
||||
mod([1,1/2,1/4,1/8,1/16].div(4)) :: sound('kick').out()
|
||||
`
|
||||
// This code is alternating between different mod values
|
||||
mod([1,1/2,1/4,1/8].div(2)) :: sound('bd').n(0).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Some sort of ringtone",
|
||||
`let blip = (freq) => {return sound('sine').sustain(0.1).freq(freq)};
|
||||
`
|
||||
let blip = (freq) => {return sound('sine').sustain(0.1).freq(freq)};
|
||||
mod(1) :: blip(200).out();
|
||||
mod(1/3) :: blip(400).out();
|
||||
div(3) :: mod(1/6) :: blip(800).out();
|
||||
@ -269,12 +282,33 @@ mod([1,0.75].div(2)) :: blip([50, 100].div(2)).out();
|
||||
`,
|
||||
false
|
||||
)}
|
||||
|
||||
|
||||
|
||||
- <icode>modp(...n: number[])</icode>: extreme version of the <icode>mod</icode> function. Instead of being normalised, this function is returning a modulo of real pulses! It can be used to break out of ratios and play with real clock pulses for unexpected results.
|
||||
|
||||
${makeExample(
|
||||
"Intriguing rhythms",
|
||||
`
|
||||
modp(36) :: snd('east')
|
||||
.n([2,4].div(1)).out()
|
||||
modp([12, 36].div(4)) :: snd('east')
|
||||
.n([2,4].add(5).div(1)).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
${makeExample(
|
||||
"modp is the OG rhythmic function in Topos",
|
||||
`
|
||||
modp([48, 24, 16].div(4)) :: sound('linnhats').out()
|
||||
mod(1)::snd('bd').out()
|
||||
`, false)};
|
||||
|
||||
- <icode>onbeat(...n: number[])</icode>: By default, the bar is set in <icode>4/4</icode> with four beats per bar. The <icode>onbeat</icode> function allows you to lock on to a specific beat to execute some code. It can accept multiple arguments. It's usage is very straightforward and not hard to understand. You can pass integers or floating point numbers.
|
||||
|
||||
${makeExample(
|
||||
"Some simple yet detailed rhythms",
|
||||
`onbeat(1,2,3,4)::snd('kick').out() // Bassdrum on each beat
|
||||
`
|
||||
onbeat(1,2,3,4)::snd('kick').out() // Bassdrum on each beat
|
||||
onbeat(2,4)::snd('snare').out() // Snare on acccentuated beats
|
||||
onbeat(1.5,2.5,3.5, 3.75)::snd('hat').out() // Cool high-hats
|
||||
`,
|
||||
@ -283,7 +317,8 @@ onbeat(1.5,2.5,3.5, 3.75)::snd('hat').out() // Cool high-hats
|
||||
|
||||
${makeExample(
|
||||
"Let's do something more complex",
|
||||
`onbeat(0.5, 1.5, 2, 3, 3.75)::snd('kick').n(2).out()
|
||||
`
|
||||
onbeat(0.5, 1.5, 2, 3, 3.75)::snd('kick').n(2).out()
|
||||
onbeat(2, [1.5, 3].pick(), 4)::snd('snare').n(7).out()
|
||||
mod([.25, 1/8].div(1.5))::snd('hat').n(2)
|
||||
.gain(rand(0.4, 0.7))
|
||||
@ -311,8 +346,10 @@ mod(.5) && euclid($(2), 2, 8) && snd('sd').out()
|
||||
${makeExample(
|
||||
"And now for more interesting rhythmic constructions",
|
||||
`
|
||||
mod(.5) && euclid($(1), 5, 9) && snd('kick').out()
|
||||
mod(.5) && euclid($(2), 2, 3, 1) && snd('pluck').end(0.5).n(5).out()
|
||||
bpm(145); // Setting a faster BPM
|
||||
mod(.5) && euclid($(1), 5, 8) :: sound('bd').out()
|
||||
mod(.5) && euclid($(2), [1,0].div(8), 8) :: sound('sd').out()
|
||||
mod(.5) && euclid($(6), [6,7].div(8), 8) :: sound('hh').out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
@ -321,15 +358,29 @@ ${makeExample(
|
||||
"Adding more rhythmic density",
|
||||
`
|
||||
mod(.5) && euclid($(1), 5, 9) && snd('kick').out()
|
||||
mod(.5) && euclid($(2), 2, 3, 1) && snd('pluck').end(0.5).n(5).out()
|
||||
mod(.5) && euclid($(3), 6, 9, 1) && snd('pluck').end(0.5).n(5).freq(200).out()
|
||||
mod(.5) && euclid($(2), 2, 3, 1) && snd('east').end(0.5).n(5).out()
|
||||
mod(.5) && euclid($(3), 6, 9, 1) && snd('east').end(0.5).n(5).freq(200).out()
|
||||
mod(.25) && euclid($(4), 7, 9, 1) && snd('hh').out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
|
||||
|
||||
|
||||
- <icode>rhythm(divisor: number, pulses: number, length: number, rotate: number): boolean</icode>: generates <icode>true</icode> or <icode>false</icode> values from an euclidian rhythm sequence. This is another version of <icode>euclid</icode> that does not take an iterator.
|
||||
${makeExample(
|
||||
"rhythm is a beginner friendly rhythmic function!",
|
||||
`
|
||||
let speed = [0.5, 0.25].div(8);
|
||||
rhythm(speed, 5, 12) :: snd('east').n(2).out()
|
||||
rhythm(speed, 2, 12) :: snd('east').out()
|
||||
rhythm(speed, 3, 12) :: snd('east').n(4).out()
|
||||
rhythm(speed, 7, 12) :: snd('east').n(9).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
|
||||
|
||||
- <icode>bin(iterator: number, n: number): boolean</icode>: a binary rhythm generator. It transforms the given number into its binary representation (_e.g_ <icode>34</icode> becomes <icode>100010</icode>). It then returns a boolean value based on the iterator in order to generate a rhythm.
|
||||
|
||||
${makeExample(
|
||||
@ -343,15 +394,17 @@ mod(.5) && bin($(2), 48) && snd('sd').out()
|
||||
|
||||
${makeExample(
|
||||
"Calling 911",
|
||||
`mod(.5) && bin($(1), 911) && snd('pluck').n(4).delay(0.5).delayt(0.25).out()
|
||||
mod(1) && sound('kick').shape(0.5).out()
|
||||
`
|
||||
mod(.5) && bin($(1), 911) && snd('subroc3d').n($(2)).delay(0.5).delayt(0.25).end(0.5).out()
|
||||
mod(.5) && sound('less').n(irand(1, 10)).out()
|
||||
`,
|
||||
false
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"Playing around with simple numbers",
|
||||
`mod(.5) && bin($(1), [123, 456, 789].div(4))
|
||||
`
|
||||
mod(.5) && bin($(1), [123, 456, 789].div(4))
|
||||
&& snd('tabla').n($(2)).delay(0.5).delayt(0.25).out()
|
||||
mod(1) && sound('kick').shape(0.5).out()
|
||||
`,
|
||||
@ -424,7 +477,8 @@ mod(.5)::snd(div(2) ? 'kick' : 'hat').out()
|
||||
|
||||
${makeExample(
|
||||
"div is great for pretty much everything",
|
||||
`div([1, .5].beat()) :: mod(.25) :: sound('shaker').out();
|
||||
`
|
||||
div([1, .5].beat()) :: mod(.25) :: sound('shaker').out();
|
||||
div([4, .5].beat()) :: mod(.25) :: sound('shaker').speed(2).out();
|
||||
div([1, 2].beat()) :: mod(1.75) :: sound('snare').out();
|
||||
div(4) :: mod(.5) :: sound('tom').out()
|
||||
@ -446,9 +500,16 @@ divbar(3)::mod(.5)::snd('hat').out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
${makeExample(
|
||||
"Alternating over four bars",
|
||||
`
|
||||
divbar(2)
|
||||
? mod(.5) && snd(['kick', 'hh'].div(1)).out()
|
||||
: mod(.5) && snd(['east', 'snare'].div(1)).out()
|
||||
`, false)};
|
||||
|
||||
|
||||
- <icode>onbar(n: number, ...bar: number[])</icode>: The first argument, <icode>n</icode>, is used to divide the time in a period of <icode>n</icode> consecutive bars. The following arguments are bar numbers to play on. For example, <icode>onbar(5, 1, 4)</icode> will return <icode>true</icode> on bar <icode>1</icode> and <icode>4</icode> but return <icode>false</icode> the rest of the time. You can easily divide time that way.
|
||||
|
||||
- <icode>onbar(bars: number | number[], n: number)</icode>: The second argument, <icode>n</icode>, is used to divide the time in a period of <icode>n</icode> consecutive bars. The first argument should be a bar number or a list of bar numbers to play on. For example, <icode>onbar([1, 4], 5)</icode> will return <icode>true</icode> on bar <icode>1</icode> and <icode>4</icode> but return <icode>false</icode> the rest of the time. You can easily divide time that way.
|
||||
|
||||
${makeExample(
|
||||
"Using onbar for filler drums",
|
||||
@ -457,7 +518,7 @@ ${makeExample(
|
||||
onbar(4, 4)::mod(.5)::snd('hh').out();
|
||||
|
||||
// Here comes a longer version using JavaScript normal control flow
|
||||
if (onbar(4, 1, 3)) {
|
||||
if (onbar([4, 1], 3)) {
|
||||
mod(1)::snd('kick').out();
|
||||
} else {
|
||||
mod(.5)::snd('sd').out();
|
||||
@ -465,7 +526,7 @@ if (onbar(4, 1, 3)) {
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
|
||||
## What are pulses?
|
||||
|
||||
To make a beat, you need a certain number of time grains or **pulses**. The **pulse** is also known as the [PPQN](https://en.wikipedia.org/wiki/Pulses_per_quarter_note). By default, Topos is using a _pulses per quarter note_ of 48. You can change it by using the <icode>ppqn(number)</icode> function. It means that the lowest possible rhythmic value is 1/48 of a quarter note. That's plenty of time already.
|
||||
@ -606,7 +667,8 @@ The basic function to play a sound is... <icode>sound(name: string)</icode> (you
|
||||
|
||||
${makeExample(
|
||||
"Playing sounds is easy",
|
||||
`mod(1) && sound('bd').out()
|
||||
`
|
||||
mod(1) && sound('bd').out()
|
||||
mod(0.5) && sound('hh').out()
|
||||
`,
|
||||
true
|
||||
@ -621,7 +683,8 @@ Let's make it slightly more complex:
|
||||
|
||||
${makeExample(
|
||||
"Adding some effects",
|
||||
`mod(1) && sound('bd').coarse(0.25).out();
|
||||
`
|
||||
mod(1) && sound('bd').coarse(0.25).room(0.5).orbit(2).out();
|
||||
mod(0.5) && sound('hh').delay(0.25).delaytime(0.125).out();
|
||||
`,
|
||||
true
|
||||
@ -640,12 +703,14 @@ Let's pause for a moment to explain what we just wrote. There are many things to
|
||||
|
||||
${makeExample(
|
||||
'"Composing" a sound or making a sound chain',
|
||||
`mod(1) :: sound('pad')
|
||||
`
|
||||
mod(1) :: sound('pad')
|
||||
.begin(rand(0, 0.4))
|
||||
.freq([50,52].beat())
|
||||
.size(0.9)
|
||||
.room(0.9)
|
||||
.pan(sine()).release(2).out()`,
|
||||
.velocity(0.25)
|
||||
.pan(usine()).release(2).out()`,
|
||||
true
|
||||
)}
|
||||
|
||||
@ -672,7 +737,9 @@ When you type <icode>kick</icode> in the <icode>sound('kick').out()</icode> expr
|
||||
The <icode>.n(number)</icode> method can be used to pick a sample from the currently selected sample folder. For instance, the following script will play a random sample from the _kick_ folder:
|
||||
${makeExample(
|
||||
"Picking a sample",
|
||||
`mod(1) && sound('kick').n([1,2,3,4,5,6,7,8].pick()).out()`,
|
||||
`
|
||||
mod(1) && sound('kick').n([1,2,3,4,5,6,7,8].pick()).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
@ -680,7 +747,8 @@ Don't worry about the number. If it gets too big, it will be automatically wrapp
|
||||
|
||||
${makeExample(
|
||||
"Picking a sample... with your mouse!",
|
||||
`// Move your mouse to change the sample being used!
|
||||
`
|
||||
// Move your mouse to change the sample being used!
|
||||
mod(.25) && sound('numbers').n(Math.floor(mouseX())).out()`,
|
||||
true
|
||||
)}
|
||||
@ -692,11 +760,12 @@ mod(.25) && sound('numbers').n(Math.floor(mouseX())).out()`,
|
||||
As we said earlier, the <icode>sound('sample_name')</icode> function can be chained to _specify_ a sound more. For instance, you can add a filter and some effects to your high-hat:
|
||||
${makeExample(
|
||||
"Learning through repetition",
|
||||
`mod(0.5) && sound('hh')
|
||||
.sometimes(s=>s.speed([1,5,10].pick()))
|
||||
.room(0.5)
|
||||
.cutoff(usine(2) * 5000)
|
||||
.out()`,
|
||||
`
|
||||
mod(0.5) && sound('hh')
|
||||
.sometimes(s=>s.speed([1,5,10].pick()))
|
||||
.room(0.5)
|
||||
.cutoff(usine(2) * 5000)
|
||||
.out()`,
|
||||
true
|
||||
)}
|
||||
|
||||
@ -748,11 +817,11 @@ Note that the **sustain** value is not a duration but an amplitude value (how lo
|
||||
${makeExample(
|
||||
"Simple synthesizer",
|
||||
`
|
||||
mod(4)::sound('sawtooth').note(50).decay(0.5).sustain(0.5).release(2).out();
|
||||
mod(2)::sound('sawtooth').note(50+7).decay(0.5).sustain(0.6).release(2).out();
|
||||
mod(1)::sound('sawtooth').note(50+12).decay(0.5).sustain(0.7).release(2).out();
|
||||
mod(4)::sound('sawtooth').note(50).decay(0.5).sustain(0.5).release(2).gain(0.25).out();
|
||||
mod(2)::sound('sawtooth').note(50+7).decay(0.5).sustain(0.6).release(2).gain(0.25).out();
|
||||
mod(1)::sound('sawtooth').note(50+12).decay(0.5).sustain(0.7).release(2).gain(0.25).out();
|
||||
mod(.25)::sound('sawtooth').note([50,57,62].pick() + [12, 24, 0].div(2))
|
||||
.cutoff(5000).sustain(0.5).release(0.1).out()
|
||||
.cutoff(5000).sustain(0.5).release(0.1).gain(0.25).out()
|
||||
`,
|
||||
true
|
||||
)};
|
||||
@ -804,9 +873,9 @@ ${makeExample(
|
||||
"Filter sweep using a low frequency oscillator",
|
||||
`
|
||||
mod(.5) && snd('sawtooth')
|
||||
.cutoff([2000,500].pick() + usine(.5) * 4000)
|
||||
.resonance(0.9).freq([100,150].pick())
|
||||
.out()
|
||||
.cutoff([2000,500].pick() + usine(.5) * 4000)
|
||||
.resonance(0.9).freq([100,150].pick())
|
||||
.out()
|
||||
`,
|
||||
true
|
||||
)};
|
||||
@ -895,7 +964,8 @@ JavaScript is using [Arrays](https://developer.mozilla.org/en-US/docs/Web/JavaSc
|
||||
|
||||
${makeExample(
|
||||
"Light drumming",
|
||||
`// Every bar, use a different rhythm
|
||||
`
|
||||
// Every bar, use a different rhythm
|
||||
mod([1, 0.75].div(4)) :: sound('cp').out()
|
||||
mod([0.5, 1].div(4)) :: sound('kick').out()
|
||||
mod(2)::snd('snare').shape(.5).out()
|
||||
@ -904,7 +974,8 @@ mod(2)::snd('snare').shape(.5).out()
|
||||
)}
|
||||
${makeExample(
|
||||
"Using div to create arpeggios",
|
||||
`// Arpeggio using pulse divisions
|
||||
`
|
||||
// Arpeggio using pulse divisions
|
||||
mod([.5, .25].div(2)) :: sound('sine')
|
||||
.hcutoff(400)
|
||||
.fmi([1,2].div(8))
|
||||
@ -920,7 +991,8 @@ mod([.5, .25].div(2)) :: sound('sine')
|
||||
)}
|
||||
${makeExample(
|
||||
"Cool ambiance",
|
||||
`mod(.5) :: snd(['kick', 'hat'].div(4)).out()
|
||||
`
|
||||
mod(.5) :: snd(['kick', 'hat'].div(4)).out()
|
||||
mod([2,4].div(2)) :: snd('shaker').delay(.5).delayfb(.75).delayt(0.125).out()
|
||||
div(2)::mod(1)::snd('clap').out()
|
||||
div(4)::mod(2)::snd('pad').n(2).shape(.5).orbit(2).room(0.9).size(0.9).release(0.5).out()
|
||||
@ -936,7 +1008,8 @@ div(4)::mod(2)::snd('pad').n(2).shape(.5).orbit(2).room(0.9).size(0.9).release(0
|
||||
|
||||
${makeExample(
|
||||
"A simple drumbeat in no time!",
|
||||
`mod(1)::sound(['kick', 'hat', 'snare', 'hat'].beat()).out()
|
||||
`
|
||||
mod(1)::sound(['kick', 'hat', 'snare', 'hat'].beat()).out()
|
||||
mod(1.5)::sound(['jvbass', 'clap'].beat()).out()
|
||||
`,
|
||||
true
|
||||
@ -957,7 +1030,8 @@ mod([1, 0.5].beat()) :: sound(['bass3'].bar())
|
||||
|
||||
${makeExample(
|
||||
"Palindrome filter sweep",
|
||||
`mod([1,.5,.25].beat()) :: snd('sine')
|
||||
`
|
||||
mod([1,.5,.25].beat()) :: snd('sine')
|
||||
.freq([100,200,300].div(0.25))
|
||||
.fmi([1,2,3].palindrome().div(0.5))
|
||||
.fmh([4, 8].palindrome().beat())
|
||||
@ -976,7 +1050,8 @@ ${makeExample(
|
||||
|
||||
${makeExample(
|
||||
"Sipping some gasoline at the robot bar",
|
||||
`mod(1)::snd('kick').shape(0.5).out()
|
||||
`
|
||||
mod(1)::snd('kick').shape(0.5).out()
|
||||
mod([.5, 1].random() / 2) :: snd(
|
||||
['amencutup', 'synth2'].random())
|
||||
.n(irand(4,10))
|
||||
@ -991,7 +1066,8 @@ mod([.5, 1].random() / 2) :: snd(
|
||||
|
||||
${makeExample(
|
||||
"Amen break suffering from data loss",
|
||||
`// Tweak the value to degrade this amen break even more!
|
||||
`
|
||||
// Tweak the value to degrade this amen break even more!
|
||||
mod(.25)::snd('amencutup').n([1,2,3,4,5,6,7,8,9].degrade(20).loop($(1))).out()
|
||||
`,
|
||||
true
|
||||
@ -1003,7 +1079,8 @@ mod(.25)::snd('amencutup').n([1,2,3,4,5,6,7,8,9].degrade(20).loop($(1))).out()
|
||||
|
||||
${makeExample(
|
||||
"Repeating samples a given number of times",
|
||||
`// Please take this repeat number down a bit!
|
||||
`
|
||||
// Please take this repeat number down a bit!
|
||||
mod(.25)::sound('amencutup').n([1,2,3,4,5,6,7,8].repeatAll(4).beat()).out()
|
||||
`,
|
||||
true
|
||||
@ -1013,7 +1090,9 @@ mod(.25)::sound('amencutup').n([1,2,3,4,5,6,7,8].repeatAll(4).beat()).out()
|
||||
|
||||
${makeExample(
|
||||
"Don't you know how to count up to 5?",
|
||||
`mod(1) :: sound('numbers').n([1,2,3,4,5].loop($(3, 10, 2))).out()`,
|
||||
`
|
||||
mod(1) :: sound('numbers').n([1,2,3,4,5].loop($(3, 10, 2))).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
@ -1021,7 +1100,8 @@ ${makeExample(
|
||||
|
||||
${makeExample(
|
||||
"Shuffling a list for extra randomness",
|
||||
`mod(1) :: sound('numbers').n([1,2,3,4,5].shuffle().loop($(1)).out()
|
||||
`
|
||||
mod(1) :: sound('numbers').n([1,2,3,4,5].shuffle().loop($(1)).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
@ -1030,10 +1110,11 @@ ${makeExample(
|
||||
|
||||
${makeExample(
|
||||
"To make things more complex... here you go",
|
||||
`mod(.5) :: snd('sine')
|
||||
`
|
||||
mod(.5) :: snd('sine')
|
||||
.freq([100, 150, 200, 250, ,300, 400]
|
||||
.rotate([1,2,3].bar()) // The list of frequencies is rotating
|
||||
.beat()) // while being indexed over!
|
||||
.rotate([1,2,3].bar()) // The list of frequencies is rotating
|
||||
.beat()) // while being indexed over!
|
||||
.sustain(0.1)
|
||||
.out()
|
||||
`,
|
||||
@ -1044,12 +1125,23 @@ ${makeExample(
|
||||
|
||||
${makeExample(
|
||||
"Demonstrative filtering. Final list is [100, 200]",
|
||||
`// Remove unique and 100 will repeat four times!
|
||||
`
|
||||
// Remove unique and 100 will repeat four times!
|
||||
mod(1)::snd('sine').sustain(0.1).freq([100,100,100,100,200].unique().beat()).out()
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
- <icode>add()</icode>: add a given amount to every list element.
|
||||
- <icode>sub()</icode>: add a given amount to every list element.
|
||||
- <icode>mult()</icode>: add a given amount to every list element.
|
||||
- <icode>division()</icode>: add a given amount to every list element. The method is named <icode>division</icode> because obviously <icode>div</icode> is already taken.
|
||||
|
||||
${makeExample(
|
||||
"Simple addition",
|
||||
`[1, 2 ,3].add(2).beat()`,
|
||||
true
|
||||
)}
|
||||
|
||||
## Simple patterns
|
||||
|
||||
@ -1248,7 +1340,8 @@ The <icode>sound</icode> function can take the name of a synthesizer as first ar
|
||||
|
||||
${makeExample(
|
||||
"Simple synthesizer voice with filter",
|
||||
`mod(.5) && snd('sawtooth')
|
||||
`
|
||||
mod(.5) && snd('sawtooth')
|
||||
.cutoff([2000,500].pick() + usine(.5) * 4000)
|
||||
.resonance(0.9).freq([100,150].pick())
|
||||
.out()
|
||||
@ -1258,8 +1351,9 @@ ${makeExample(
|
||||
|
||||
${makeExample(
|
||||
"Listening to the different waveforms from the sweetest to the harshest",
|
||||
`mod(.5) && snd(['sine', 'triangle', 'sawtooth', 'square'].beat()).freq(100).out()
|
||||
.freq(50)
|
||||
`
|
||||
mod(.5) && snd(['sine', 'triangle', 'sawtooth', 'square'].beat()).freq(100).out()
|
||||
.freq(50)
|
||||
.out()
|
||||
`,
|
||||
false
|
||||
@ -1268,7 +1362,8 @@ ${makeExample(
|
||||
|
||||
${makeExample(
|
||||
"Blessed by the square wave",
|
||||
`mod(4) :: [100,101].forEach((freq) => sound('square').freq(freq).sustain(0.1).out())
|
||||
`
|
||||
mod(4) :: [100,101].forEach((freq) => sound('square').freq(freq).sustain(0.1).out())
|
||||
mod(.5) :: [100,101].forEach((freq) => sound('square').freq(freq*2).sustain(0.01).out())
|
||||
mod([.5, .75, 2].beat()) :: [100,101].forEach((freq) => sound('square')
|
||||
.freq(freq*4 + usquare(2) * 200).sustain(0.125).out())
|
||||
@ -1279,11 +1374,14 @@ mod(.25) :: sound('square').freq(100*[1,2,4,8].beat()).sustain(0.1).out()`,
|
||||
|
||||
${makeExample(
|
||||
"Ghost carillon",
|
||||
`mod(1/8)::sound('sine')
|
||||
`
|
||||
mod(1/8)::sound('sine')
|
||||
.velocity(rand(0.0, 1.0))
|
||||
.delay(0.75).delayt(.5)
|
||||
.sustain(0.4)
|
||||
.cutoff(2000)
|
||||
.freq(mouseX())
|
||||
.gain(0.25)
|
||||
.out()`,
|
||||
false
|
||||
)}
|
||||
@ -1298,7 +1396,8 @@ The same basic waveforms can take additional methods to switch to a basic two op
|
||||
|
||||
${makeExample(
|
||||
"80s nostalgia",
|
||||
`mod(.25) && snd('sine')
|
||||
`
|
||||
mod(.25) && snd('sine')
|
||||
.fmi([1,2,4,8].pick())
|
||||
.fmh([1,2,4,8].div(8))
|
||||
.freq([100,150].pick())
|
||||
@ -1310,7 +1409,8 @@ ${makeExample(
|
||||
|
||||
${makeExample(
|
||||
"Giving some love to weird ratios",
|
||||
`mod([.5, .25].bar()) :: sound('sine').fm('2.2183:3.18293').sustain(0.05).out()
|
||||
`
|
||||
mod([.5, .25].bar()) :: sound('sine').fm('2.2183:3.18293').sustain(0.05).out()
|
||||
mod([4].bar()) :: sound('sine').fm('5.2183:4.5').sustain(0.05).out()
|
||||
mod(.5) :: sound('sine')
|
||||
.fmh([1, 1.75].beat())
|
||||
@ -1321,9 +1421,10 @@ mod(.5) :: sound('sine')
|
||||
|
||||
${makeExample(
|
||||
"Some peace and serenity",
|
||||
`mod(0.25) :: sound('sine')
|
||||
.note([60, 67, 70, 72, 77].beat())
|
||||
.attack(0.2).release(0.5).gain(0.5)
|
||||
`
|
||||
mod(0.25) :: sound('sine')
|
||||
.note([60, 67, 70, 72, 77].beat() - [0,12].bar())
|
||||
.attack(0.2).release(0.5).gain(0.25)
|
||||
.room(0.9).size(0.8).sustain(0.5)
|
||||
.fmi(Math.floor(usine(.25) * 10))
|
||||
.cutoff(1500).delay(0.5).delayt(0.125)
|
||||
@ -1383,16 +1484,23 @@ There are some techniques that Topos players are using to keep their JavaScript
|
||||
|
||||
${makeExample(
|
||||
"Shortening your if conditions",
|
||||
`// The && symbol (overriden by :: in Topos) is very often used for conditions!
|
||||
mod(.75) :: snd('zap').out()
|
||||
`
|
||||
// The && symbol (overriden by :: in Topos) is very often used for conditions!
|
||||
mod(.75) :: snd('linnhats').n([1,4,5].beat()).out()
|
||||
mod(1) :: snd('bd').out()
|
||||
//if (true) && log('very true')
|
||||
// These two lines are the same:
|
||||
// mod(1) && snd('bd').out()
|
||||
//// mod(1) :: snd('bd').out()
|
||||
|
||||
`,
|
||||
true
|
||||
)}
|
||||
|
||||
${makeExample(
|
||||
"More complex conditions using ?",
|
||||
`// The ? symbol can be used to write a if/true/false condition
|
||||
`
|
||||
// The ? symbol can be used to write a if/true/false condition
|
||||
mod(4) ? snd('kick').out() : mod(2)::snd('snare').out()
|
||||
// (true) ? log('very true') : log('very false')
|
||||
`,
|
||||
@ -1402,7 +1510,8 @@ mod(4) ? snd('kick').out() : mod(2)::snd('snare').out()
|
||||
|
||||
${makeExample(
|
||||
"Using not and other short symbols",
|
||||
`// The ! symbol can be used to reverse a condition
|
||||
`
|
||||
// The ! symbol can be used to reverse a condition
|
||||
mod(4) ? snd('kick').out() : mod(2)::snd('snare').out()
|
||||
!mod(2) :: mod(0.5)::snd('clap').out()
|
||||
`,
|
||||
@ -1477,36 +1586,38 @@ Low Frequency Oscillators (_LFOs_) are an important piece in any digital audio w
|
||||
- <icode>sine(freq: number = 1, offset: number= 0): number</icode>: returns a sinusoïdal oscillation between <icode>-1</icode> and <icode>1</icode>.
|
||||
- <icode>usine(freq: number = 1, offset: number= 0): number</icode>: returns a sinusoïdal oscillation between <icode>0</icode> and <icode>1</icode>. The <icode>u</icode> stands for _unipolar_.
|
||||
|
||||
\`\`\`javascript
|
||||
mod(.25) && snd('cp').speed(1 + usine(0.25) * 2).out()
|
||||
\`\`\`
|
||||
|
||||
${makeExample(
|
||||
"Modulating the speed of a sample player using a sine LFO",
|
||||
`mod(.25) && snd('cp').speed(1 + usine(0.25) * 2).out()`, true)};
|
||||
|
||||
- <icode>triangle(freq: number = 1, offset: number= 0): number</icode>: returns a triangle oscillation between <icode>-1</icode> and <icode>1</icode>.
|
||||
- <icode>utriangle(freq: number = 1, offset: number= 0): number</icode>: returns a triangle oscillation between <icode>0</icode> and <icode>1</icode>. The <icode>u</icode> stands for _unipolar_.
|
||||
|
||||
\`\`\`javascript
|
||||
mod(.25) && snd('cp').speed(1 + utriangle(0.25) * 2).out()
|
||||
\`\`\`
|
||||
|
||||
${makeExample(
|
||||
"Modulating the speed of a sample player using a triangle LFO",
|
||||
`mod(.25) && snd('cp').speed(1 + utriangle(0.25) * 2).out()`, true)}
|
||||
|
||||
- <icode>saw(freq: number = 1, offset: number= 0): number</icode>: returns a sawtooth-like oscillation between <icode>-1</icode> and <icode>1</icode>.
|
||||
- <icode>usaw(freq: number = 1, offset: number= 0): number</icode>: returns a sawtooth-like oscillation between <icode>0</icode> and <icode>1</icode>. The <icode>u</icode> stands for _unipolar_.
|
||||
|
||||
\`\`\`javascript
|
||||
mod(.25) && snd('cp').speed(1 + usaw(0.25) * 2).out()
|
||||
\`\`\`
|
||||
|
||||
${makeExample(
|
||||
"Modulating the speed of a sample player using a saw LFO",
|
||||
`mod(.25) && snd('cp').speed(1 + usaw(0.25) * 2).out()`, true)}
|
||||
|
||||
- <icode>square(freq: number = 1, offset: number= 0, duty: number = .5): number</icode>: returns a square wave oscillation between <icode>-1</icode> and <icode>1</icode>. You can also control the duty cycle using the <icode>duty</icode> parameter.
|
||||
- <icode>usquare(freq: number = 1, offset: number= 0, duty: number = .5): number</icode>: returns a square wave oscillation between <icode>0</icode> and <icode>1</icode>. The <icode>u</icode> stands for _unipolar_. You can also control the duty cycle using the <icode>duty</icode> parameter.
|
||||
|
||||
\`\`\`javascript
|
||||
mod(.25) && snd('cp').speed(1 + usquare(0.25, 0, 0.25) * 2).out()
|
||||
\`\`\`
|
||||
${makeExample(
|
||||
"Modulating the speed of a sample player using a square LFO",
|
||||
`mod(.25) && snd('cp').speed(1 + usquare(0.25, 0, 0.25) * 2).out()`,true)};
|
||||
|
||||
- <icode>noise()</icode>: returns a random value between -1 and 1.
|
||||
|
||||
\`\`\`javascript
|
||||
mod(.25) && snd('cp').speed(1 + noise() * 2).out()
|
||||
\`\`\`
|
||||
${makeExample(
|
||||
"Modulating the speed of a sample player using noise",
|
||||
`mod(.25) && snd('cp').speed(1 + noise() * 2).out()`, true)};
|
||||
|
||||
## Probabilities
|
||||
|
||||
|
||||
@ -95,3 +95,17 @@ export const evaluate = async (
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const evaluateOnce = async (
|
||||
application: Editor,
|
||||
code: string
|
||||
): Promise<void> => {
|
||||
/**
|
||||
* Evaluates the code once without any caching or error-handling mechanisms besides the tryCatchWrapper.
|
||||
*
|
||||
* @param application - The application object that contains the Editor API.
|
||||
* @param code - The code to be evaluated.
|
||||
* @returns A promise that resolves when the code has been evaluated.
|
||||
*/
|
||||
await tryCatchWrapper(application, code);
|
||||
};
|
||||
|
||||
55
src/examples/excerpts.ts
Normal file
55
src/examples/excerpts.ts
Normal file
@ -0,0 +1,55 @@
|
||||
export const examples = [
|
||||
`
|
||||
// Ancient rhythms - Bubobubobubo
|
||||
mod(1)::snd('kick').out();
|
||||
mod(2)::snd('sd').room(0.9).size(0.9).out();
|
||||
mod(0.25)::snd('hh').out();
|
||||
mod(2)::snd('square')
|
||||
.cutoff(500).note(50-12).resonance(20).sustain(0.2).out()
|
||||
mod(1/4)::snd(divseq(1, 'sawtooth', 'triangle', 'pulse'))
|
||||
.note(divseq(4, 50, 53, 55, 50, 50, 52, 58, 50+12, 50+15) + divseq(0.5, 0, 12, 24))
|
||||
.cutoff(usine(.5)*10000).resonance(divseq(2, 10,20))
|
||||
.fmi($(1) % 10).fmh($(2) % 5)
|
||||
.room(0.8).size(0.9)
|
||||
.delay(0.5).delaytime(0.25)
|
||||
.delayfb(0.6)
|
||||
.sustain(0.01 + usine(.25) / 10).out()
|
||||
mod(4)::snd('amencutup').n($(19)).cut(1).orbit(2).pan(rand(0.0,1.0)).out()
|
||||
log(bar(), beat(), pulse())`,
|
||||
`
|
||||
// Crazy arpeggios - Bubobubobubo
|
||||
bpm(110)
|
||||
mod(0.125) && sound('sawtooth')
|
||||
.note([60, 62, 63, 67, 70].div(.125) +
|
||||
[-12,0,12].beat() + [0, 0, 5, 7].bar())
|
||||
.sustain(0.1).fmi(0.25).fmh(2).room(0.9)
|
||||
.gain(0.75).cutoff(500 + usine(8) * [500, 1000, 2000].bar())
|
||||
.delay(0.5).delayt(0.25).delayfb(0.25)
|
||||
.out();
|
||||
mod(1) && snd('kick').out();
|
||||
mod(2) && snd('snare').out();
|
||||
mod(.5) && snd('hat').out();
|
||||
`, `
|
||||
// Obscure Shenanigans - Bubobubobubo
|
||||
mod([1/4,1/8,1/16].div(8)):: sound('sine')
|
||||
.freq([100,50].div(16) + 50 * ($(1)%10))
|
||||
.gain(0.5).room(0.9).size(0.9)
|
||||
.sustain(0.1).out()
|
||||
mod(1) :: sound('kick').out()
|
||||
mod(2) :: sound('dr').n(5).out()
|
||||
div(3) :: mod([.25,.5].div(.5)) :: sound('dr')
|
||||
.n([8,9].pick()).gain([.8,.5,.25,.1,.0].div(.25)).out()
|
||||
`, `
|
||||
// Resonance bliss - Bubobubobubo
|
||||
mod(.25)::snd('arpy')
|
||||
.note(30 + [0,3,7,10].beat())
|
||||
.cutoff(usine(.5) * 5000).resonance(10).gain(0.3)
|
||||
.end(0.8).room(0.9).size(0.9).n(0).out();
|
||||
mod([.25,.125].div(2))::snd('arpy')
|
||||
.note(30 + [0,3,7,10].beat())
|
||||
.cutoff(usine(.5) * 5000).resonance(20).gain(0.3)
|
||||
.end(0.8).room(0.9).size(0.9).n(3).out();
|
||||
mod(.5) :: snd('arpy').note(
|
||||
[30, 33, 35].repeatAll(4).div(1) - [12,0].div(0.5)).out()
|
||||
`
|
||||
]
|
||||
69
src/main.ts
69
src/main.ts
@ -3,6 +3,7 @@ import {
|
||||
colors,
|
||||
animals,
|
||||
} from "unique-names-generator";
|
||||
import { examples } from "./examples/excerpts";
|
||||
import { EditorState, Compartment } from "@codemirror/state";
|
||||
import { ViewUpdate, lineNumbers, keymap } from "@codemirror/view";
|
||||
import { javascript } from "@codemirror/lang-javascript";
|
||||
@ -90,6 +91,9 @@ export class Editor {
|
||||
public _mouseX: number = 0;
|
||||
public _mouseY: number = 0;
|
||||
|
||||
// Topos Logo
|
||||
topos_logo: HTMLElement = document.getElementById('topos-logo') as HTMLElement;
|
||||
|
||||
// Transport elements
|
||||
play_buttons: HTMLButtonElement[] = [
|
||||
document.getElementById("play-button-1") as HTMLButtonElement,
|
||||
@ -130,6 +134,10 @@ export class Editor {
|
||||
close_settings_button: HTMLButtonElement = document.getElementById(
|
||||
"close-settings-button"
|
||||
) as HTMLButtonElement;
|
||||
close_universes_button: HTMLButtonElement = document.getElementById(
|
||||
"close-universes-button"
|
||||
) as HTMLButtonElement;
|
||||
|
||||
universe_viewer: HTMLDivElement = document.getElementById(
|
||||
"universe-viewer"
|
||||
) as HTMLDivElement;
|
||||
@ -184,9 +192,12 @@ export class Editor {
|
||||
// Loading the universe from local storage
|
||||
// ================================================================================
|
||||
|
||||
this.selected_universe = this.settings.selected_universe;
|
||||
this.universe_viewer.innerHTML = `Topos: ${this.selected_universe}`;
|
||||
this.universes = { ...template_universes, ...this.settings.universes };
|
||||
this.selected_universe = "Welcome";
|
||||
this.universe_viewer.innerHTML = `Topos: ${this.selected_universe}`;
|
||||
let random_example = examples[Math.floor(Math.random() * examples.length)];
|
||||
this.universes[this.selected_universe].global.committed = random_example;
|
||||
this.universes[this.selected_universe].global.candidate = random_example;
|
||||
|
||||
// ================================================================================
|
||||
// Audio context and clock
|
||||
@ -237,9 +248,6 @@ export class Editor {
|
||||
|
||||
// ================================================================================
|
||||
// Building the documentation
|
||||
// loadSamples().then(() => {
|
||||
// this.docs = documentation_factory(this);
|
||||
// });
|
||||
let pre_loading = async () => { await loadSamples(); };
|
||||
pre_loading();
|
||||
this.docs = documentation_factory(this);
|
||||
@ -307,18 +315,7 @@ export class Editor {
|
||||
// This is the modal to switch between universes
|
||||
if (event.ctrlKey && event.key === "b") {
|
||||
this.hideDocumentation();
|
||||
let existing_universes = document.getElementById("existing-universes");
|
||||
let known_universes = Object.keys(this.universes);
|
||||
let final_html = "<ul class='lg:h-80 lg:w-80 lg:pb-2 lg:pt-2 overflow-y-scroll text-white lg:mb-4 border rounded-lg bg-gray-800'>";
|
||||
known_universes.forEach((name) => {
|
||||
final_html += `
|
||||
<li onclick="_loadUniverseFromInterface('${name}')" class="hover:fill-black hover:bg-white py-2 hover:text-black flex justify-between px-4">
|
||||
<p >${name}</p>
|
||||
<button onclick=_deleteUniverseFromInterface('${name}')>🗑</button>
|
||||
</li>`;
|
||||
});
|
||||
final_html = final_html + "</ul>";
|
||||
existing_universes!.innerHTML = final_html;
|
||||
this.updateKnownUniversesView();
|
||||
this.openBuffersModal();
|
||||
}
|
||||
|
||||
@ -408,6 +405,12 @@ export class Editor {
|
||||
});
|
||||
}
|
||||
|
||||
this.topos_logo.addEventListener("click", () => {
|
||||
this.hideDocumentation();
|
||||
this.updateKnownUniversesView();
|
||||
this.openBuffersModal();
|
||||
})
|
||||
|
||||
this.play_buttons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
if (this.isPlaying) {
|
||||
@ -495,6 +498,10 @@ export class Editor {
|
||||
editor?.classList.remove("invisible");
|
||||
});
|
||||
|
||||
this.close_universes_button.addEventListener("click", () => {
|
||||
this.openBuffersModal();
|
||||
});
|
||||
|
||||
this.font_size_slider.addEventListener("input", () => {
|
||||
const new_value = this.font_size_slider.value;
|
||||
this.settings.font_size = parseInt(new_value);
|
||||
@ -665,6 +672,21 @@ export class Editor {
|
||||
return JSON.parse(hash);
|
||||
};
|
||||
|
||||
updateKnownUniversesView = () => {
|
||||
let existing_universes = document.getElementById("existing-universes");
|
||||
let known_universes = Object.keys(this.universes);
|
||||
let final_html = "<ul class='lg:h-80 lg:w-80 lg:pb-2 lg:pt-2 overflow-y-scroll text-white lg:mb-4 border rounded-lg bg-gray-800'>";
|
||||
known_universes.forEach((name) => {
|
||||
final_html += `
|
||||
<li onclick="_loadUniverseFromInterface('${name}')" class="hover:fill-black hover:bg-white py-2 hover:text-black flex justify-between px-4">
|
||||
<p >${name}</p>
|
||||
<button onclick=_deleteUniverseFromInterface('${name}')>🗑</button>
|
||||
</li>`;
|
||||
});
|
||||
final_html = final_html + "</ul>";
|
||||
existing_universes!.innerHTML = final_html;
|
||||
}
|
||||
|
||||
share() {
|
||||
const hashed_table = btoa(
|
||||
JSON.stringify({
|
||||
@ -744,6 +766,7 @@ export class Editor {
|
||||
button.children[0].classList.remove("text-white");
|
||||
button.children[0].classList.add("text-orange-300");
|
||||
button.classList.add("text-orange-300");
|
||||
button.classList.add("fill-orange-300");
|
||||
};
|
||||
|
||||
switch (mode) {
|
||||
@ -968,12 +991,8 @@ export class Editor {
|
||||
}
|
||||
}
|
||||
|
||||
// Creating the application
|
||||
const app = new Editor();
|
||||
|
||||
|
||||
|
||||
// When the user leaves the page, all the universes should be saved in the localStorage
|
||||
window.addEventListener("beforeunload", () => {
|
||||
// @ts-ignore
|
||||
event.preventDefault();
|
||||
@ -984,11 +1003,3 @@ window.addEventListener("beforeunload", () => {
|
||||
app.clock.stop();
|
||||
return null;
|
||||
});
|
||||
|
||||
// function reportMouseCoordinates(event: MouseEvent) {
|
||||
// app._mouseX = event.clientX;
|
||||
// app._mouseY = event.clientY;
|
||||
// }
|
||||
|
||||
// onmousemove = function(e){console.log("mouse location:", e.clientX, e.clientY)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user