Merge branch 'main' of https://github.com/Bubobubobubobubo/Topos into 58-support-for-external-midi-clock

This commit is contained in:
2023-10-04 00:28:36 +03:00
6 changed files with 243 additions and 192 deletions

View File

@ -235,6 +235,11 @@
<input id="show-tips" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"> <input id="show-tips" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
<label for="default-checkbox" class="ml-2 text-sm font-medium text-dark">Show Hovering Tips</label> <label for="default-checkbox" class="ml-2 text-sm font-medium text-dark">Show Hovering Tips</label>
</div> </div>
<div class="flex items-center mb-4 ml-8">
<input id="load-demo-songs" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
<label for="default-checkbox" class="ml-2 text-sm font-medium text-dark">Load Demo Song</label>
</div>
</div> </div>
<!-- Midi settings --> <!-- Midi settings -->
<div id="midi-settings-container" class="rounded-lg bg-gray-300 pt-4"> <div id="midi-settings-container" class="rounded-lg bg-gray-300 pt-4">

View File

@ -56,6 +56,7 @@ export interface Settings {
selected_universe: string; selected_universe: string;
line_numbers: boolean; line_numbers: boolean;
time_position: boolean; time_position: boolean;
load_demo_songs: boolean;
tips: boolean; tips: boolean;
send_clock: boolean; send_clock: boolean;
midi_clock_input: string|undefined; midi_clock_input: string|undefined;
@ -134,6 +135,7 @@ export class AppSettings {
public send_clock: boolean = false; public send_clock: boolean = false;
public midi_clock_input: string|undefined = undefined; public midi_clock_input: string|undefined = undefined;
public midi_clock_ppqn: number = 24; public midi_clock_ppqn: number = 24;
public load_demo_songs: boolean = true;
constructor() { constructor() {
const settingsFromStorage = JSON.parse( const settingsFromStorage = JSON.parse(
@ -154,6 +156,7 @@ export class AppSettings {
this.send_clock = settingsFromStorage.send_clock; this.send_clock = settingsFromStorage.send_clock;
this.midi_clock_input = settingsFromStorage.midi_clock_input; this.midi_clock_input = settingsFromStorage.midi_clock_input;
this.midi_clock_ppqn = settingsFromStorage.midi_clock_ppqn || 24; this.midi_clock_ppqn = settingsFromStorage.midi_clock_ppqn || 24;
this.load_demo_songs = settingsFromStorage.load_demo_songs;
} else { } else {
this.universes = template_universes; this.universes = template_universes;
} }
@ -180,6 +183,7 @@ export class AppSettings {
send_clock: this.send_clock, send_clock: this.send_clock,
midi_clock_input: this.midi_clock_input, midi_clock_input: this.midi_clock_input,
midi_clock_ppqn: this.midi_clock_ppqn, midi_clock_ppqn: this.midi_clock_ppqn,
load_demo_songs: this.load_demo_songs,
}; };
} }
@ -204,6 +208,7 @@ export class AppSettings {
this.send_clock = settings.send_clock; this.send_clock = settings.send_clock;
this.midi_clock_input = settings.midi_clock_input; this.midi_clock_input = settings.midi_clock_input;
this.midi_clock_ppqn = settings.midi_clock_ppqn; this.midi_clock_ppqn = settings.midi_clock_ppqn;
this.load_demo_songs = settings.load_demo_songs;
localStorage.setItem("topos", JSON.stringify(this.data)); localStorage.setItem("topos", JSON.stringify(this.data));
} }
} }

View File

@ -28,7 +28,7 @@ export const makeExampleFactory = (application: Editor): Function => {
) => { ) => {
const codeId = `codeExample${application.exampleCounter++}`; const codeId = `codeExample${application.exampleCounter++}`;
// Store the code snippet in the data structure // Store the code snippet in the data structure
application.api.codeExamples[codeId] = code; application.api.codeExamples[codeId] = "bpm(120);\n" + code;
return ` return `
<details ${open ? "open" : ""}> <details ${open ? "open" : ""}>

View File

@ -41,12 +41,13 @@ beat(.5) && snd('triangle').note([60,"F4"].pick()).out()
)} )}
Chords can also played using different parameters: Chords can also played using different parameters:
-<ic>chord(string||number[]|...number)</ic>: parses and sets notes for the chord
- <ic>chord(string||number[]|...number)</ic>: parses and sets notes for the chord
${makeExample( ${makeExample(
"Playing a named chord", "Playing a named chord",
` `
beat(1) && snd('triangle').chord(["C","Em7","Fmaj7","Emin"].beat(2)).out() beat(1) && snd('triangle').chord(["C","Em7","Fmaj7","Emin"].beat(2)).adsr(0,.2).out()
`, `,
true true
)} )}
@ -54,7 +55,7 @@ ${makeExample(
${makeExample( ${makeExample(
"Playing a chord from a list of notes and doing inversions", "Playing a chord from a list of notes and doing inversions",
` `
beat(.5) && snd('triangle').chord(60,64,67,72).invert([1,-3,4,-5].pick()).out() beat(.5) && snd('triangle').chord(60,64,67,72).invert([1,-3,4,-5].pick()).adsr(0,.2).out()
`, `,
true true
)} )}
@ -152,8 +153,8 @@ These filters all come with their own set of parameters. Note that we are descri
| Method | Alias | Description | | Method | Alias | Description |
|------------|-----------|---------------------------------| |------------|-----------|---------------------------------|
| cutoff | lpf | cutoff frequency of the lowpass filter | | <ic>cutoff</ic> | <ic>lpf</ic> | cutoff frequency of the lowpass filter |
| resonance | lpq | resonance of the lowpass filter | | <ic>resonance</ic> | <ic>lpq</ic> | resonance of the lowpass filter |
${makeExample( ${makeExample(
"Filtering a bass", "Filtering a bass",
@ -165,8 +166,8 @@ ${makeExample(
| Method | Alias | Description | | Method | Alias | Description |
|------------|-----------|---------------------------------| |------------|-----------|---------------------------------|
| hcutoff | hpf | cutoff frequency of the highpass filter | | <ic>hcutoff</ic> | <ic>hpf</ic> | cutoff frequency of the highpass filter |
| hresonance | hpq | resonance of the highpass filter | | <ic>hresonance</ic> | <ic>hpq</ic> | resonance of the highpass filter |
${makeExample( ${makeExample(
"Filtering a noise source", "Filtering a noise source",
@ -178,8 +179,8 @@ ${makeExample(
| Method | Alias | Description | | Method | Alias | Description |
|------------|-----------|---------------------------------| |------------|-----------|---------------------------------|
| bandf | bpf | cutoff frequency of the bandpass filter | | <ic>bandf</ic> | <ic>bpf</ic> | cutoff frequency of the bandpass filter |
| bandq | bpq | resonance of the bandpass filter | | <ic>bandq</ic> | <ic>bpq</ic> | resonance of the bandpass filter |
${makeExample( ${makeExample(
"Sweeping the filter on the same guitar sample", "Sweeping the filter on the same guitar sample",
@ -245,12 +246,12 @@ The examples we have studied so far are static. They filter the sound around a f
| Method | Alias | Description | | Method | Alias | Description |
|------------|-----------|---------------------------------| |------------|-----------|---------------------------------|
| lpenv | lpe | lowpass frequency modulation amount (negative or positive) | | <ic>lpenv</ic> | <ic>lpe</ic> | lowpass frequency modulation amount (negative or positive) |
| lpattack | lpa | attack of the lowpass filter | | <ic>lpattack</ic> | <ic>lpa</ic> | attack of the lowpass filter |
| lpdecay | lpd | decay of the lowpass filter | | <ic>lpdecay</ic> | <ic>lpd</ic> | decay of the lowpass filter |
| lpsustain | lps | sustain of the lowpass filter | | <ic>lpsustain</ic> | <ic>lps</ic> | sustain of the lowpass filter |
| lprelease | lpr | release of the lowpass filter | | <ic>lprelease</ic> | <ic>lpr</ic> | release of the lowpass filter |
| lpadsr | | (**takes five arguments**) set all the parameters | | <ic>lpadsr</ic> | | (**takes five arguments**) set all the parameters |
${makeExample( ${makeExample(
@ -265,12 +266,12 @@ ${makeExample(
| Method | Alias | Description | | Method | Alias | Description |
|------------|-----------|---------------------------------| |------------|-----------|---------------------------------|
| hpenv | hpe | highpass frequency modulation amount (negative or positive) | | <ic>hpenv</ic> | <ic>hpe</ic> | highpass frequency modulation amount (negative or positive) |
| hpattack | hpa | attack of the highpass filter | | <ic>hpattack</ic> | <ic>hpa</ic> | attack of the highpass filter |
| hpdecay | hpd | decay of the highpass filter | | <ic>hpdecay</ic> | <ic>hpd</ic> | decay of the highpass filter |
| hpsustain | hps | sustain of the highpass filter | | <ic>hpsustain</ic> | <ic>hps</ic> | sustain of the highpass filter |
| hprelease | hpr | release of the highpass filter | | <ic>hprelease</ic> | <ic>hpr</ic> | release of the highpass filter |
| hpadsr | | (**takes five arguments**) set all the parameters | | <ic>hpadsr</ic> | | (**takes five arguments**) set all the parameters |
${makeExample( ${makeExample(
@ -285,12 +286,12 @@ ${makeExample(
| Method | Alias | Description | | Method | Alias | Description |
|------------|-----------|---------------------------------| |------------|-----------|---------------------------------|
| bpenv | bpe | bandpass frequency modulation amount (negative or positive) | | <ic>bpenv</ic> | <ic>bpe</ic> | bandpass frequency modulation amount (negative or positive) |
| bpattack | bpa | attack of the bandpass filter | | <ic>bpattack</ic> | <ic>bpa</ic> | attack of the bandpass filter |
| bpdecay | bpd | decay of the bandpass filter | | <ic>bpdecay</ic> | <ic>bpd</ic> | decay of the bandpass filter |
| bpsustain | bps | sustain of the bandpass filter | | <ic>bpsustain</ic> | <ic>bps</ic> | sustain of the bandpass filter |
| bprelease | bpr | release of the bandpass filter | | <ic>bprelease</ic> | <ic>bpr</ic> | release of the bandpass filter |
| bpadsr | | (**takes five arguments**) set all the parameters | | <ic>bpadsr</ic> | | (**takes five arguments**) set all the parameters |
${makeExample( ${makeExample(

View File

@ -23,89 +23,100 @@ Let's study two very simple rhythmic functions, <ic>mod(n: ...number[])</ic> and
- <ic>beat(...n: number[])</ic>: this function will return true every _n_ beats. The value <ic>1</ic> will return <ic>true</ic> at the beginning of each beat. Floating point numbers like <ic>0.5</ic> or <ic>0.25</ic> are also accepted. Multiple values can be passed to <ic>beat</ic> to generate more complex rhythms. - <ic>beat(...n: number[])</ic>: this function will return true every _n_ beats. The value <ic>1</ic> will return <ic>true</ic> at the beginning of each beat. Floating point numbers like <ic>0.5</ic> or <ic>0.25</ic> are also accepted. Multiple values can be passed to <ic>beat</ic> to generate more complex rhythms.
${makeExample( ${makeExample(
"Using different mod values", "Using different mod values",
` `
// This code is alternating between different mod values // This code is alternating between different mod values
beat([1,1/2,1/4,1/8].beat(2)) :: sound('bd').n(0).out() beat([1,1/2,1/4,1/8].beat(2)) :: sound('hat').n(0).out()
`, `,
true true
)} )}
${makeExample( ${makeExample(
"Some sort of ringtone", "Some sort of ringtone",
` `
let blip = (freq) => {return sound('sine').sustain(0.1).freq(freq)}; // Blip generator :)
beat(1) :: blip(200).out(); let blip = (freq) => {
beat(1/3) :: blip(400).out(); return sound('wt_piano')
flip(3) :: beat(1/6) :: blip(800).out(); .gain(1)
beat([1,0.75].beat(2)) :: blip([50, 100].beat(2)).out(); .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 false
)} )}
- <ic>pulse(...n: number[])</ic>: faster version of the <ic>beat</ic> function. Instead of returning true for every beat, this function is returning true every _n_ clock ticks! It can be used to generate very unexpected results or to sequence by using your arithmetic ninja skills. - <ic>pulse(...n: number[])</ic>: faster version of the <ic>beat</ic> function. Instead of returning true for every beat, this function is returning true every _n_ clock ticks! It can be used to generate very unexpected rhythms.
${makeExample( ${makeExample(
"Intriguing rhythms", "Intriguing rhythms",
` `
pulse(36) :: snd('east') pulse([24,48].beat(2)) :: snd('hand')
.n([2,4].beat(1)).out() .cut(1).room(0.9).size(0.9)
pulse([12, 36].beat(4)) :: snd('east') .n([2,4].beat(2)).out()
pulse([48/2, 48/3].beat(4)) :: snd('hand')
.n([2,4].add(5).beat(1)).out() .n([2,4].add(5).beat(1)).out()
`, `,
true true
)} )}
${makeExample( ${makeExample(
"pulse is the OG rhythmic function in Topos", "pulse is the OG rhythmic function in Topos",
` `
pulse([48, 24, 16].beat(4)) :: sound('linnhats').out() pulse([48, 24, 16].beat(4)) :: sound('linnhats').out()
beat(1)::snd('bd').out() beat(1)::snd('bd').out()
`, `,
false 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. - <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( ${makeExample(
"Some simple yet detailed rhythms", "Some simple yet detailed rhythms",
` `
onbeat(1,2,3,4)::snd('kick').out() // Bassdrum on each beat onbeat(1,2,3,4)::snd('kick').out() // Bassdrum on each beat
onbeat(2,4)::snd('snare').out() // Snare on acccentuated beats onbeat(2,4)::snd('snare').n([0,2].beat(2.5)).out() // Snare on acccentuated beats
onbeat(1.5,2.5,3.5, 3.75)::snd('hat').out() // Cool high-hats onbeat(1.5,2.5,3.5, 3.75)::snd('hat').gain(r(0.9,1.1)).out() // Cool high-hats
`, `,
true true
)} )}
${makeExample( ${makeExample(
"Let's do something more complex", "Let's do something more complex",
` `
onbeat(0.5, 1.5, 2, 3, 3.75)::snd('kick').n(2).out() onbeat(0.5, 2, 3, 3.75)::snd('kick').n(2).out()
onbeat(2, [1.5, 3].pick(), 4)::snd('snare').n(7).out() onbeat(2, [1.5, 3, 4].pick(), 4)::snd('snare').n(8).out()
beat([.25, 1/8].beat(1.5))::snd('hat').n(2) beat([.25, 1/8].beat(1.5))::snd('hat').n(2)
.gain(rand(0.4, 0.7)) .gain(rand(0.4, 0.7)).end(0.05)
.pan(usine()).out() .pan(usine()).out()
`, `,
false 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. - <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( ${makeExample(
"Using oncount to create more variation in the rhythm", "Using oncount to create more variation in the rhythm",
` `
bpm(120) bpm(120)
z1('q (0 4 2 9)+(0 3 1 5)').sound('sawtooth').cutoff([400,500,1000,2000].beat(1)) z1('0.125 (0 2 3 4)+(0 2 4 6)').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() .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() 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() oncount([1,3,5.5,7,7.5,8],8) :: sound('hh').gain(irand(1.0,4.0)).out()
`, `,
true true
)} )}
${makeExample( ${makeExample(
"Using oncount to create rhythms with a custom meter", "Using oncount to create rhythms with a custom meter",
` `
bpm(200) bpm(200)
oncount([1, 5, 9, 13],16) :: sound('bd').gain(1.0).out() oncount([1, 5, 9, 13],16) :: sound('bd').gain(1.0).out()
oncount([5, 6, 13],16) :: sound('cp').gain(0.9).out() oncount([5, 6, 13],16) :: sound('cp').gain(0.9).out()
@ -113,8 +124,8 @@ ${makeExample(
oncount([1, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16],16) :: oncount([1, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16],16) ::
sound('hh').out() sound('hh').out()
`, `,
true true
)} )}
## Rhythm generators ## Rhythm generators
@ -123,116 +134,117 @@ We included a bunch of popular rhythm generators in Topos such as the euclidian
- <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. - <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( ${makeExample(
"Classic euclidian club music patterns", "Classic euclidian club music patterns",
` `
beat(.5) && euclid($(1), 5, 8) && snd('kick').out() beat(.5) && euclid($(1), 5, 8) && snd('kick').out()
beat(.5) && euclid($(2), 2, 8) && snd('sd').out() beat(.5) && euclid($(2), 2, 8) && snd('sd').out()
beat(4) :: sound('cp').out() beat(4) :: sound('cp').out()
`, `,
true true
)} )}
${makeExample( ${makeExample(
"And now for more interesting rhythmic constructions", "And now something a bit more complex",
` `
bpm(145); // Setting a faster BPM bpm(145); // Setting a faster BPM
beat(.5) && euclid($(1), 5, 8) :: sound('bd').out() beat(.5) && euclid($(1), 5, 8) :: sound('bd').out()
beat(.5) && euclid($(2), [1,0].beat(8), 8) :: sound('sd').out() beat(.5) && euclid($(2), [1,0].beat(8), 8)
:: sound('ST03').n(3).room(1).size(1).o(1).out()
beat(.5) && euclid($(6), [6,7].beat(8), 8) :: sound('hh').out() beat(.5) && euclid($(6), [6,7].beat(8), 8) :: sound('hh').out()
`, `,
false false
)} )}
${makeExample( ${makeExample(
"Adding more rhythmic density", "Adding more rhythmic density",
` `
beat(.5) && euclid($(1), 5, 9) && snd('kick').out() beat(.5) && euclid($(1), 5, 9) && snd('kick').out()
beat(.5) && euclid($(2), 2, 3, 1) && snd('east').end(0.5).n(5).speed([1,2].beat(2)).out() beat(.5) && euclid($(2), 2, 3, 1) && snd('east').end(0.5).n(5).speed([1,2].beat(2)).out()
beat(.5) && euclid($(3), 6, 9, 1) && snd('east').end(0.5).n(5).freq(200).speed([2,1].beat(2)).out() beat(.5) && euclid($(3), 6, 9, 1) && snd('east').end(0.5).n(5).freq(200).speed([2,1].beat(2)).out()
beat(.25) && euclid($(4), 7, 9, 1) && snd('hh').out() beat(.25) && euclid($(4), 7, 9, 1) && snd('hh').out()
`, `,
false false
)} )}
Alternatively, you can <ic>oneuclid</ic> or <ic>rhythm</ic> without the _iterators_: 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. - <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( ${makeExample(
"Using oneuclid to create a rhythm without iterators", "Using oneuclid to create a rhythm without iterators",
` `
// Change speed using bpm // Change speed using bpm
bpm(250) bpm(250)
oneuclid(5, 9) :: snd('kick').out() oneuclid(5, 9) :: snd('kick').out()
oneuclid(7,16) :: snd('east').end(0.5).n(irand(3,5)).out() oneuclid(7,16) :: snd('east').end(0.5).n(irand(3,5)).out()
`, `,
false 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. - <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( ${makeExample(
"rhythm is a beginner friendly rhythmic function!", "rhythm is a beginner friendly rhythmic function!",
` `
let speed = [0.5, 0.25].beat(8); bpm(140); let speed = [1, 0.5].beat(8); bpm(140);
rhythm(speed, 5, 12) :: snd('east').n(2).out() rhythm(speed, 5, 12) :: snd('linnhats').n(2).pan(noise()).out()
rhythm(speed, 2, 12) :: snd('east').out() rhythm(speed, 2, 12) :: snd('east').out()
rhythm(speed, 3, 12) :: snd('east').n(4).out() rhythm(speed, 3, 12) :: snd('linnhats').n(4).pan(noise()).out()
rhythm(speed, 7, 12) :: snd('east').n(9).out() rhythm(speed, 7, 12) :: snd('east').n(9).out()
`, `,
true 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>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. - <ic>binrhythm(divisor: number, n: number): boolean: boolean</ic>: iterator-less version of the binary rhythm generator.
${makeExample( ${makeExample(
"Change the integers for a surprise rhythm!", "Change the integers for a surprise rhythm!",
` `
beat(.5) && bin($(1), 34) && snd('kick').out() bpm(135);
beat(.5) && bin($(2), 48) && snd('sd').out() beat(.5) && bin($(1), 34) && snd('kick').n([1,3].beat(1)).out()
beat(.5) && bin($(2), 48) && snd('snare').n([1,4].beat(1)).out()
`, `,
true true
)} )}
${makeExample( ${makeExample(
"binrhythm for fast cool binary rhythms!", "binrhythm for fast cool binary rhythms!",
` `
binrhythm(.5, 13) && snd('kick').out() let a = 0;
binrhythm(.5, 18) && snd('sd').out() 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 true
)} )}
${makeExample( ${makeExample(
"Submarine jungle music", "Submarine jungle music",
` `
bpm(145);
beat(.5) && bin($(1), 911) && snd('ST69').n([2,3,4].beat()) beat(.5) && bin($(1), 911) && snd('ST69').n([2,3,4].beat())
.delay(0.125).delayt(0.25).end(0.25).speed(1/3).out() .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() beat(.5) && sound('amencutup').n(irand(2,7)).shape(0.3).out()
`, `,
false false
)} )}
${makeExample(
"Using tabla to play unpredictable rhythms",
`
beat(.5) && bin($(1), [123, 456, 789].beat(4))
&& snd('tabla').n($(2)).delay(0.5).delayt(0.25).out()
beat(1) && sound('kick').shape(0.5).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. 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( ${makeExample(
"Probablistic drums in one line!", "Probablistic drums in one line!",
` `
prob(60)::beat(.5) && euclid($(1), 5, 8) && snd('kick').out() prob(60)::beat(.5) && euclid($(1), 5, 8) && snd('kick').out()
prob(60)::beat(.5) && euclid($(2), 3, 8) && snd('sd').out() prob(60)::beat(.5) && euclid($(2), 3, 8) && snd('mash')
prob(80)::beat(.5) && sound('hh').out() .n([1,2,3].beat(1))
.pan(usine(1/4)).out()
prob(80)::beat(.5) && sound(['hh', 'hat'].pick()).out()
`, `,
true true
)} )}
## Time Warping ## Time Warping
@ -242,8 +254,8 @@ Time generally flows from the past to the future. However, it's even cooler when
${makeExample( ${makeExample(
"Time is now super elastic!", "Time is now super elastic!",
` `
// Obscure Shenanigans - Bubobubobubo // Obscure Shenanigans - Bubobubobubo
beat([1/4,1/8,1/16].beat(8)):: sound('sine') beat([1/4,1/8,1/16].beat(8)):: sound('sine')
.freq([100,50].beat(16) + 50 * ($(1)%10)) .freq([100,50].beat(16) + 50 * ($(1)%10))
@ -256,14 +268,14 @@ flip(3) :: beat([.25,.5].beat(.5)) :: sound('dr')
// Jumping back and forth in time // Jumping back and forth in time
beat(.25) :: warp([12, 48, 24, 1, 120, 30].pick()) beat(.25) :: warp([12, 48, 24, 1, 120, 30].pick())
`, `,
true true
)} )}
- <ic>beat_warp(beat: number)</ic>: this function jumps to the _n_ beat of the clock. The first beat is <ic>1</ic>. - <ic>beat_warp(beat: number)</ic>: this function jumps to the _n_ beat of the clock. The first beat is <ic>1</ic>.
${makeExample( ${makeExample(
"Jumping back and forth with beats", "Jumping back and forth with beats",
` `
// Resonance bliss - Bubobubobubo // Resonance bliss - Bubobubobubo
beat(.25)::snd('arpy') beat(.25)::snd('arpy')
.note(30 + [0,3,7,10].beat()) .note(30 + [0,3,7,10].beat())
@ -278,8 +290,8 @@ beat(.5) :: snd('arpy').note(
// Comment me to stop warping! // Comment me to stop warping!
beat(1) :: beat_warp([2,4,5,10,11].pick()) beat(1) :: beat_warp([2,4,5,10,11].pick())
`, `,
true true
)} )}
## Larger time divisions ## Larger time divisions
@ -289,41 +301,42 @@ Now you know how to play some basic rhythmic music but you are a bit stuck in a
- <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. - <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( ${makeExample(
"Two beats of silence, two beats of playing", "Two beats of silence, two beats of playing",
` `
flip(4) :: beat(1) :: snd('kick').out() flip(4) :: beat(1) :: snd('kick').out()
`, `,
true true
)} )}
${makeExample( ${makeExample(
"Clapping on the edge", "Clapping on the edge",
` `
flip(2.5, 10) :: beat(.25) :: snd('cp').out() 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, 75) :: beat(.25) :: snd('click')
.speed(2).end(0.2).out()
flip(2.5) :: beat(.5) :: snd('bd').out() flip(2.5) :: beat(.5) :: snd('bd').out()
beat(.25) :: sound('hh').out() beat(.25) :: sound('hat').end(0.1).cutoff(1200).pan(usine(1/4)).out()
`, `,
false false
)} )}
${makeExample( ${makeExample(
"Good old true and false", "Good old true and false",
` `
if (flip(4, 75)) { if (flip(4, 75)) {
beat(1) :: snd('kick').out() beat(1) :: snd('kick').out()
} else { } else {
beat(.5) :: snd('snare').out() beat(.5) :: snd('snare').out()
} }
`, `,
true 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. <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( ${makeExample(
"Clunky algorithmic rap music", "Clunky algorithmic rap music",
` `
// Rap God VS Lil Wild -- Adel Faure // Rap God VS Lil Wild -- Adel Faure
if (flip(16)) { if (flip(16)) {
// Playing this part for two bars // Playing this part for two bars
@ -341,45 +354,45 @@ if (flip(16)) {
beat(.5)::snd('diphone').end(0.5).n([1,2,3,4].pick()).out() beat(.5)::snd('diphone').end(0.5).n([1,2,3,4].pick()).out()
} }
`, `,
true true
)} )}
You can use it everywhere to spice things up, including as a method parameter picker: You can use it everywhere to spice things up, including as a method parameter picker:
${makeExample( ${makeExample(
"flip is great for parameter variation", "flip is great for parameter variation",
` `
beat(.5)::snd(flip(4) ? 'kick' : 'hat').out() beat(.5)::snd(flip(4) ? 'kick' : 'hat').out()
`, `,
true 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. - <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( ${makeExample(
"Thinking music over bars", "Thinking music over bars",
` `
flipbar(2) :: beat(1):: snd('kick').out() flipbar(2) :: beat(1):: snd('kick').out()
flipbar(3) :: beat(.5):: snd('hat').out() flipbar(3) :: beat(.5):: snd('hat').out()
`, `,
true true
)} )}
${makeExample( ${makeExample(
"Alternating over four bars", "Alternating over four bars",
` `
flipbar(2) flipbar(2)
? beat(.5) && snd(['kick', 'hh'].beat(1)).out() ? beat(.5) && snd(['kick', 'hh'].beat(1)).out()
: beat(.5) && snd(['east', 'east:2'].beat(1)).out() : beat(.5) && snd(['east', 'east:2'].beat(1)).out()
`, `,
false 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. - <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( ${makeExample(
"Using onbar for filler drums", "Using onbar for filler drums",
` `
bpm(150); bpm(150);
// Only play on the third and fourth bar of the cycle. // Only play on the third and fourth bar of the cycle.
onbar([3,4], 4)::beat(.25)::snd('hh').out(); onbar([3,4], 4)::beat(.25)::snd('hh').out();
@ -395,8 +408,8 @@ if (onbar([1,2], 4)) {
rhythm(.5, 1, 7) :: snd('jvbass').n(2).out(); rhythm(.5, 1, 7) :: snd('jvbass').n(2).out();
rhythm(.5, 2, 7) :: snd('snare').n(3).out(); rhythm(.5, 2, 7) :: snd('snare').n(3).out();
}`, }`,
true true
)} )}
## What are pulses? ## What are pulses?
@ -425,8 +438,8 @@ Every script can access the current time by using the following functions:
These values are **extremely useful** to craft more complex syntax or to write musical scores. However, Topos is also offering more high-level sequencing functions to make it easier to play music. You can use the time functions as conditionals. The following example will play a pattern A for 2 bars and a pattern B for 2 bars: These values are **extremely useful** to craft more complex syntax or to write musical scores. However, Topos is also offering more high-level sequencing functions to make it easier to play music. You can use the time functions as conditionals. The following example will play a pattern A for 2 bars and a pattern B for 2 bars:
${makeExample( ${makeExample(
"Manual mode: using time primitives!", "Manual mode: using time primitives!",
` `
// Manual time condition // Manual time condition
if((cbar() % 4) > 1) { if((cbar() % 4) > 1) {
beat(2) && sound('kick').out() beat(2) && sound('kick').out()
@ -444,7 +457,7 @@ if((cbar() % 4) > 1) {
// This is always playing no matter what happens // This is always playing no matter what happens
beat([.5, .5, 1, .25].beat(0.5)) :: sound('shaker').out() beat([.5, .5, 1, .25].beat(0.5)) :: sound('shaker').out()
`, `,
true true
)} )}
`; `;
}; };

View File

@ -35,7 +35,7 @@ import { makeStringExtensions } from "./StringExtensions";
const classMap = { const classMap = {
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 py-2 px-2", 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 py-2 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 py-2 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 py-2 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-neutoral-700 rounded-lg py-2 px-2 lg:mt-16", 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-700 rounded-lg py-2 px-2 lg:mt-16",
ul: "text-underline pl-6", ul: "text-underline pl-6",
li: "list-disc lg:text-2xl text-base text-white lg:mx-4 mx-2 my-4 my-2 leading-normal", 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", p: "lg:text-2xl text-base text-white lg:mx-6 mx-2 my-4 leading-normal",
@ -201,6 +201,11 @@ export class Editor {
"midi-clock-ppqn-input" "midi-clock-ppqn-input"
) as HTMLSelectElement; ) as HTMLSelectElement;
// Loading demo songs when starting
load_demo_songs: HTMLInputElement = document.getElementById(
"load-demo-songs"
) as HTMLInputElement;
// Editor mode selection // Editor mode selection
normal_mode_button: HTMLButtonElement = document.getElementById( normal_mode_button: HTMLButtonElement = document.getElementById(
"normal-mode" "normal-mode"
@ -233,18 +238,10 @@ export class Editor {
public hydra: any = this.hydra_backend.synth; public hydra: any = this.hydra_backend.synth;
constructor() { constructor() {
// ================================================================================
// Loading the universe from local storage
// ================================================================================
this.universes = { ...this.settings.universes, ...template_universes }; // ================================================================================
this.selected_universe = "Welcome"; // Loading the settings
this.universe_viewer.innerHTML = `Topos: ${this.selected_universe}`; // ================================================================================
// Picking a random example to populate the landing page
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;
this.line_numbers_checkbox.checked = this.settings.line_numbers; this.line_numbers_checkbox.checked = this.settings.line_numbers;
this.time_position_checkbox.checked = this.settings.time_position; this.time_position_checkbox.checked = this.settings.time_position;
@ -254,6 +251,29 @@ export class Editor {
if (!this.settings.time_position) { if (!this.settings.time_position) {
document.getElementById("timeviewer")!.classList.add("hidden"); document.getElementById("timeviewer")!.classList.add("hidden");
} }
this.load_demo_songs.checked = this.settings.load_demo_songs;
// ================================================================================
// Loading the universe from local storage
// ================================================================================
this.universes = {
...this.settings.universes,
...template_universes
};
if (this.settings.load_demo_songs) {
let random_example = examples[Math.floor(Math.random() * examples.length)];
this.selected_universe = "Welcome"
this.universes[this.selected_universe].global.committed = random_example;
this.universes[this.selected_universe].global.candidate = random_example;
} else {
this.selected_universe = this.settings.selected_universe;
if (this.universes[this.selected_universe] === undefined)
this.universes[this.selected_universe] = structuredClone(template_universe)
}
this.universe_viewer.innerHTML = `Topos: ${this.selected_universe}`;
// ================================================================================ // ================================================================================
// Audio context and clock // Audio context and clock
@ -509,7 +529,7 @@ export class Editor {
button.addEventListener("click", () => { button.addEventListener("click", () => {
this.setButtonHighlighting("clear", true); this.setButtonHighlighting("clear", true);
if (confirm("Do you want to reset the current universe?")) { if (confirm("Do you want to reset the current universe?")) {
this.universes[this.selected_universe] = template_universe; this.universes[this.selected_universe] = structuredClone(template_universe);
this.updateEditorView(); this.updateEditorView();
} }
}); });
@ -571,6 +591,7 @@ export class Editor {
this.tips_checkbox.checked = this.settings.tips; this.tips_checkbox.checked = this.settings.tips;
this.midi_clock_checkbox.checked = this.settings.send_clock; this.midi_clock_checkbox.checked = this.settings.send_clock;
this.midi_clock_ppqn.value = this.settings.midi_clock_ppqn.toString(); this.midi_clock_ppqn.value = this.settings.midi_clock_ppqn.toString();
this.load_demo_songs.checked = this.settings.load_demo_songs;
if (this.settings.vimMode) { if (this.settings.vimMode) {
let vim = document.getElementById("vim-mode-radio") as HTMLInputElement; let vim = document.getElementById("vim-mode-radio") as HTMLInputElement;
@ -676,6 +697,12 @@ export class Editor {
this.settings.midi_clock_ppqn = value; this.settings.midi_clock_ppqn = value;
}); });
this.load_demo_songs.addEventListener("change", () => {
let checked = this.load_demo_songs.checked ? true : false;
this.settings.load_demo_songs = checked;
});
this.vim_mode_button.addEventListener("click", () => { this.vim_mode_button.addEventListener("click", () => {
this.settings.vimMode = true; this.settings.vimMode = true;
this.view.dispatch({ this.view.dispatch({