From 8cbd38bd09ec56c684e139f5d0d24662319b5825 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Thu, 5 Oct 2023 00:19:59 +0200 Subject: [PATCH 1/4] add warning if multiple tabs are used --- index.html | 2 +- src/documentation/engine.ts | 95 ++++++++++++++++--------------- src/documentation/introduction.ts | 39 ++++++------- src/main.ts | 18 +++++- 4 files changed, 86 insertions(+), 68 deletions(-) diff --git a/index.html b/index.html index a905713..717406a 100644 --- a/index.html +++ b/index.html @@ -57,7 +57,7 @@ } - +
diff --git a/src/documentation/engine.ts b/src/documentation/engine.ts index 5083f36..99dc921 100644 --- a/src/documentation/engine.ts +++ b/src/documentation/engine.ts @@ -50,13 +50,12 @@ Let's pause for a moment and explain what is going on: - Sounds are **composed** by adding qualifiers/parameters that will modify the sound or synthesizer being played (_e.g_ sound('...').blabla(...)..something(...).out(). Think of it as _audio chains_. ${makeExample( - '"Composing" a sound or making a sound chain', + '"Composing" a complex sonic object by making a sound chain', ` -beat(1) :: sound('pad') +beat(1) :: sound('pad').n(1) .begin(rand(0, 0.4)) .freq([50,52].beat()) - .size(0.9) - .room(0.9) + .size(0.9).room(0.9) .velocity(0.25) .pan(usine()).release(2).out()`, true @@ -110,10 +109,9 @@ ${makeExample( "Let's make something more complex", ` beat(0.25) && sound('jvbass') - .sometimes(s=>s.speed([1,5,10].pick())) - .room(0.5) - .gain(1) - .cutoff(usine(2) * 5000) + .sometimes(s=>s.speed([2, 0.5].pick())) + .room(0.9).size(0.9).gain(1) + .cutoff(usine(1/2) * 5000) .out()`, true )} @@ -131,7 +129,7 @@ There is a special method to choose the _orbit_ that your sound is going to use: | Method | Alias | Description | |----------|-------|------------------------------------------------------------| -| orbit | o | Orbit number | +| orbit | o | Orbit number | ## Amplitude @@ -140,9 +138,9 @@ Simple controls over the amplitude (volume) of a given sound. | Method | Alias | Description | |----------|-------|------------------------------------------------------------------------------------| -| gain | | Volume of the synth/sample (exponential) | -| velocity | vel | Velocity (amplitude) from 0 to 1. Multipled with gain | -| dbgain | db | Attenuation in dB from -inf to +10 (acts as a sound mixer fader) | +| gain | | Volume of the synth/sample (exponential) | +| velocity | vel | Velocity (amplitude) from 0 to 1. Multipled with gain | +| dbgain | db | Attenuation in dB from -inf to +10 (acts as a sound mixer fader) | ${makeExample( "Velocity manipulated by a counter", @@ -157,21 +155,24 @@ beat(.5)::snd('cp').vel($(1)%10 / 10).out()`, | Method | Alias | Description | |---------|-------|-----------------------------------------------| -| attack | atk | Attack value (time to maximum volume) | -| decay | dec | Decay value (time to decay to sustain level) | -| sustain | sus | Sustain value (gain when sound is held) | -| release | rel | Release value (time for the sound to die off) | +| attack | atk | Attack value (time to maximum volume) | +| decay | dec | Decay value (time to decay to sustain level) | +| sustain | sus | Sustain value (gain when sound is held) | +| release | rel | Release value (time for the sound to die off) | Note that the **sustain** value is not a duration but an amplitude value (how loud). The other values are the time for each stage to take place. Here is a fairly complete example using the sawtooth basic waveform. ${makeExample( "Simple synthesizer", ` -beat(4)::sound('sawtooth').note(50).decay(0.5).sustain(0.5).release(2).gain(0.25).out(); -beat(2)::sound('sawtooth').note(50+7).decay(0.5).sustain(0.6).release(2).gain(0.25).out(); -beat(1)::sound('sawtooth').note(50+12).decay(0.5).sustain(0.7).release(2).gain(0.25).out(); -beat(.25)::sound('sawtooth').note([50,57,62].pick() + [12, 24, 0].beat(2)) - .cutoff(5000).sustain(0.5).release(0.1).gain(0.25).out() +let smooth = (sound) => { + return sound.cutoff(r(100,500)) + .lpadsr(usaw(1/8) * 8, 0.05, .125, 0, 0) + .gain(r(0.25, 0.4)).adsr(0, r(.2,.4), r(0,0.5), 0) + .room(0.9).size(2).o(2).vib(r(2,8)).vibmod(0.125) +} +beat(.25)::smooth(sound('sawtooth').note([50,57,55,60].beat(1))).out(); +beat(.25)::smooth(sound('sawtooth').note([50,57,55,60].add(12).beat(1.5))).out(); `, true )}; @@ -182,17 +183,17 @@ There are some basic controls over the playback of each sample. This allows you | Method | Alias | Description | |---------|-------|--------------------------------------------------------| -| n | | Select a sample in the current folder (from 0 to infinity) | -| begin | | Beginning of the sample playback (between 0 and 1) | -| end | | End of the sample (between 0 and 1) | -| loopBegin | | Beginning of the loop section (between 0 and 1) | -| loopEnd | | End of the loop section (between 0 and 1) | -| loop | | Whether to loop or not the audio sample | -| stretch | | Stretches the audio playback rate of a sample over n beats | -| speed | | Playback speed (2 = twice as fast) | -| cut | | Set with 0 or 1. Will cut the sample as soon as another sample is played on the same bus | -| clip | | Multiply the duration of the sample with the given number | -| pan | | Stereo position of the audio playback (0 = left, 1 = right)| +| n | | Select a sample in the current folder (from 0 to infinity) | +| begin | | Beginning of the sample playback (between 0 and 1) | +| end | | End of the sample (between 0 and 1) | +| loopBegin | | Beginning of the loop section (between 0 and 1) | +| loopEnd | | End of the loop section (between 0 and 1) | +| loop | | Whether to loop or not the audio sample | +| stretch | | Stretches the audio playback rate of a sample over n beats | +| speed | | Playback speed (2 = twice as fast) | +| cut | | Set with 0 or 1. Will cut the sample as soon as another sample is played on the same bus | +| clip | | Multiply the duration of the sample with the given number | +| pan | | Stereo position of the audio playback (0 = left, 1 = right)| ${makeExample( "Complex sampling duties", @@ -224,13 +225,13 @@ There are three basic filters: a _lowpass_, _highpass_ and _bandpass_ filters wi | Method | Alias | Description | |------------|-------|-----------------------------------------| -| cutoff | lpf | Cutoff frequency of the lowpass filter | -| resonance | lpq | Resonance of the lowpass filter | -| hcutoff | hpf | Cutoff frequency of the highpass filter | -| hresonance | hpq | Resonance of the highpass filter | -| bandf | bpf | Cutoff frequency of the bandpass filter | -| bandq | bpq | Resonance of the bandpass filter | -| vowel | | Formant filter with (vocal quality) | +| cutoff | lpf | Cutoff frequency of the lowpass filter | +| resonance | lpq | Resonance of the lowpass filter | +| hcutoff | hpf | Cutoff frequency of the highpass filter | +| hresonance | hpq | Resonance of the highpass filter | +| bandf | bpf | Cutoff frequency of the bandpass filter | +| bandq | bpq | Resonance of the bandpass filter | +| vowel | | Formant filter with (vocal quality) | ${makeExample( "Filter sweep using a low frequency oscillator", @@ -249,8 +250,8 @@ A basic reverberator that you can use to give some depth to your sounds. This si | Method | Alias | Description | |------------|-------|---------------------------------| -| room | | The more, the bigger the reverb (between 0 and 1.| -| size | | Reverberation amount | +| room | | The more, the bigger the reverb (between 0 and 1.| +| size | | Reverberation amount | ${makeExample( "Clapping in the cavern", @@ -267,9 +268,9 @@ A good sounding delay unit that can go into feedback territory. Use it without m | Method | Alias | Description | |------------|-----------|---------------------------------| -| delay | | Delay _wet/dry_ (between 0 and 1) | -| delaytime | delayt | Delay time (in milliseconds) | -| delayfeedback| delayfb | Delay feedback (between 0 and 1) | +| delay | | Delay _wet/dry_ (between 0 and 1) | +| delaytime | delayt | Delay time (in milliseconds) | +| delayfeedback | delayfb | Delay feedback (between 0 and 1) | ${makeExample( "Who doesn't like delay?", @@ -285,9 +286,9 @@ beat(1)::snd('kick').out() | Method | Alias | Description | |------------|-----------|---------------------------------| -| coarse | | Artificial sample-rate lowering | -| crush | | bitcrushing. 1 is extreme, the more you go up, the less it takes effect. | -| shape | | Waveshaping distortion (between 0 and 1) | +| coarse | | Artificial sample-rate lowering | +| crush | | bitcrushing. 1 is extreme, the more you go up, the less it takes effect. | +| shape | | Waveshaping distortion (between 0 and 1) | ${makeExample( diff --git a/src/documentation/introduction.ts b/src/documentation/introduction.ts index 28a48c5..ca1e847 100644 --- a/src/documentation/introduction.ts +++ b/src/documentation/introduction.ts @@ -12,43 +12,44 @@ Welcome to the Topos documentation. These pages are offering you an introduction )}. Press again to make the documentation disappear. All your contributions are welcome! ${makeExample( - "Welcome! Eval to get started", - examples[Math.floor(Math.random() * examples.length)], - true -)} + "Welcome! Eval to get started", + examples[Math.floor(Math.random() * examples.length)], + true + )} ## What is Topos? Topos is an _algorithmic_ sequencer. Topos uses small algorithms to represent musical sequences and processes. These can be written in just a few lines of code. Topos is made to be _live-coded_. The _live coder_ strives for the constant interaction with algorithms and sound during a musical performance. Topos is aiming to be a digital playground for live algorithmic music. ${makeExample( - "Small algorithms for direct musical expression", - ` + "Small algorithms for direct musical expression", + ` beat(1) :: sound(['kick', 'hat', 'snare', 'hat'].beat(1)).out() beat(.5) :: sound('jvbass').note(35 + [0,12].beat()).out() -beat([0.5, 0.25, 1, 2].beat(1)) :: sound('east') - .room(.5).size(0.5).n(irand(1,5)).out()`, - false -)} +beat([0.5, 0.25].beat(1)) :: sound('east') + .room(.9).speed(flip(4) ? 1 : 0.95).size(0.9).o(2).n($(1)).out()`, + false + )} ${makeExample( - "Computer music should be immediate and intuitive", - `beat(.5)::snd('sine') + "Computer music should be immediate and intuitive", + `beat(.5)::snd('sine') .delay(0.5).delayt(0.25).delayfb(0.7) .room(0.8).size(0.8) .freq(mouseX()).out()`, - false -)} + false + )} ${makeExample( - "Making the web less dreadful, one beep at at time", - ` -beat(.5) :: sound('sid').n($(2)).out() + "Making the web less dreadful, one beep at at time", + ` +beat(.5) :: sound('sid').n($(2)) + .room(1).speed([1,2].pick()).out() beat(.25) :: sound('sid').note( [34, 36, 41].beat(.25) + [[0,-24].pick(),12].beat()) .room(0.9).size(0.9).n(4).out()`, - false -)} + false + )} Topos is deeply inspired by the [Monome Teletype](https://monome.org/). The Teletype is/was an open source hardware module for Eurorack synthesizers. While the Teletype was initially born as an hardware module, Topos aims to be a web-browser based software sequencer from the same family! It is a sequencer, a scriptable interface, a companion for algorithmic music-making. Topos wishes to fullfill the same goal than the Teletype, keeping the same spirit alive on the web. It is free, open-source, and made to be shared and used by everyone. diff --git a/src/main.ts b/src/main.ts index 119b09b..1f3f7e8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -32,6 +32,21 @@ import showdown from "showdown"; showdown.setFlavor("github"); import showdownHighlight from "showdown-highlight"; import { makeStringExtensions } from "./StringExtensions"; + + +// Broadcast that you're opening a page. +localStorage.openpages = Date.now(); +window.addEventListener('storage', function(e) { + if (e.key == "openpages") { + // Listen if anybody else is opening the same page! + localStorage.page_available = Date.now(); + } + if (e.key == "page_available") { + document.getElementById("all")!.classList.add("invisible") + alert("Topos is already opened in another tab. Close this tab now to prevent data loss."); + } +}, false); + 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", 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", @@ -1160,7 +1175,7 @@ export class Editor { } } -const app = new Editor(); +let app = new Editor(); window.addEventListener("beforeunload", () => { // @ts-ignore @@ -1172,3 +1187,4 @@ window.addEventListener("beforeunload", () => { app.clock.stop(); return null; }); + From f1525a45a32195b000ae2c97b2dc1e751264d2bb Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Thu, 5 Oct 2023 01:38:24 +0200 Subject: [PATCH 2/4] some slightly better examples --- src/Documentation.ts | 2 +- src/documentation/time.ts | 65 +++++++++++++++++++++++---------------- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/Documentation.ts b/src/Documentation.ts index 8e5969d..05fc9fe 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -28,7 +28,7 @@ export const makeExampleFactory = (application: Editor): Function => { ) => { const codeId = `codeExample${application.exampleCounter++}`; // Store the code snippet in the data structure - application.api.codeExamples[codeId] = "bpm(120);\n" + code; + application.api.codeExamples[codeId] = code; return `
diff --git a/src/documentation/time.ts b/src/documentation/time.ts index 940c1e7..3041d77 100644 --- a/src/documentation/time.ts +++ b/src/documentation/time.ts @@ -69,7 +69,7 @@ ${makeExample( "pulse is the OG rhythmic function in Topos", ` pulse([48, 24, 16].beat(4)) :: sound('linnhats').out() -beat(1)::snd('bd').out() +beat(1)::snd(['bd', '808oh'].beat(1)).out() `, false )}; @@ -80,7 +80,7 @@ ${makeExample( "Some simple yet detailed rhythms", ` onbeat(1,2,3,4)::snd('kick').out() // Bassdrum on each beat -onbeat(2,4)::snd('snare').n([0,2].beat(2.5)).out() // Snare on acccentuated beats +onbeat(2,4)::snd('snare').n([8,4].beat(4)).out() // Snare on acccentuated beats onbeat(1.5,2.5,3.5, 3.75)::snd('hat').gain(r(0.9,1.1)).out() // Cool high-hats `, true @@ -103,13 +103,12 @@ beat([.25, 1/8].beat(1.5))::snd('hat').n(2) ${makeExample( "Using oncount to create more variation in the rhythm", ` - bpm(120) - 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() - 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() +z1('1/16 (0 2 3 4)+(0 2 4 6)').scale('pentatonic').sound('sawtooth') + .cutoff([400,500,1000,2000].beat(1)) + .lpadsr(2, 0, .2, 0, 0) + .delay(0.5).delayt(0.25).room(0.9).size(0.9).out() +onbeat(1,1.5,2,3,4) :: sound('bd').gain(2.0).out() +oncount([1,3,5.5,7,7.5,8],8) :: sound('hh').gain(irand(1.0,4.0)).out() `, true )} @@ -117,12 +116,11 @@ ${makeExample( ${makeExample( "Using oncount to create rhythms with a custom meter", ` - bpm(200) - oncount([1, 5, 9, 13],16) :: sound('bd').gain(1.0).out() - oncount([5, 6, 13],16) :: sound('cp').gain(0.9).out() - oncount([2, 3, 3.5, 6, 7, 10, 15],16) :: sound('hh').n(8).gain(0.8).out() - oncount([1, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16],16) :: - sound('hh').out() +bpm(200) +oncount([1, 5, 9, 13],16) :: sound('808bd').n(4).shape(0.5).gain(1.0).out() +oncount([5, 6, 13],16) :: sound('shaker').room(0.25).gain(0.9).out() +oncount([2, 3, 3.5, 6, 7, 10, 15],16) :: sound('hh').n(8).gain(0.8).out() +oncount([1, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16],16) :: sound('hh').out() `, true )} @@ -136,9 +134,14 @@ We included a bunch of popular rhythm generators in Topos such as the euclidian ${makeExample( "Classic euclidian club music patterns", ` -beat(.5) && euclid($(1), 5, 8) && snd('kick').out() -beat(.5) && euclid($(2), 2, 8) && snd('sd').out() -beat(4) :: sound('cp').out() +beat(.5) && euclid($(1), 4, 8) && snd('kick').n(4).out() +beat(.25) && euclid($(2), 5, 8) && snd('dr').n(21).out() +beat(.25) && euclid($(3), 3, 8) && snd('shaker') + .gain(r(0.7, 1)).cutoff(1000 + usine(1/8) * 3000) + .n(11).out() +beat(.25) && euclid($(3), 6, 8) && snd('shaker') + .gain(r(0.7, 1)).cutoff(1000 + usine(1/4) * 4000) + .speed(2).n(11).out() `, true )} @@ -149,7 +152,7 @@ ${makeExample( bpm(145); // Setting a faster BPM beat(.5) && euclid($(1), 5, 8) :: sound('bd').out() beat(.5) && euclid($(2), [1,0].beat(8), 8) - :: sound('ST03').n(3).room(1).size(1).o(1).out() + :: sound('ST03').n(5).room(1).size(1).o(1).out() beat(.5) && euclid($(6), [6,7].beat(8), 8) :: sound('hh').out() `, false @@ -158,9 +161,11 @@ beat(.5) && euclid($(6), [6,7].beat(8), 8) :: sound('hh').out() ${makeExample( "Adding more rhythmic density", ` -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($(3), 6, 9, 1) && snd('east').end(0.5).n(5).freq(200).speed([2,1].beat(2)).out() +beat(.5) && euclid($(1), 5, 9) && snd('kick').shape(r(0.2,0.5)).out() +beat(.5) && euclid($(2), 2, 3, 1) && snd('dr').end(0.5).n([8,9,13].beat(0.25)) + .gain(r(0.5,1)).speed(1).out() +beat(.5) && euclid($(3), 6, 9, 1) && snd('dr').end(0.5).n(2).freq(200).speed(1) + .gain(r(0.5,1)).out() beat(.25) && euclid($(4), 7, 9, 1) && snd('hh').out() `, false @@ -200,8 +205,8 @@ ${makeExample( "Change the integers for a surprise rhythm!", ` bpm(135); -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() +beat(.5) && bin($(1), 12) && snd('kick').n([4,9].beat(1.5)).out() +beat(.5) && bin($(2), 34) && snd('snare').n([3,5].beat(1)).out() `, true )} @@ -372,8 +377,16 @@ beat(.5)::snd(flip(4) ? 'kick' : 'hat').out() ${makeExample( "Thinking music over bars", ` -flipbar(2) :: beat(1):: snd('kick').out() -flipbar(3) :: beat(.5):: snd('hat').out() +let roomy = (n) => n.room(1).size(1).cutoff(500 + usaw(1/8) * 5000); +function a() { + beat(1) && roomy(sound('kick')).out() + beat(.5) && roomy(sound('hat')).out() +} +function b() { + beat(1/4) && roomy(sound('shaker')).out() +} +flipbar(2) && a() +flipbar(3) && b() `, true )} From 959c8a99c204e8efd1a548a496c880a3a5207615 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Thu, 5 Oct 2023 11:03:41 +0200 Subject: [PATCH 3/4] Fix stretch function and sound examples --- src/classes/SoundEvent.ts | 3 ++- src/documentation/engine.ts | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/classes/SoundEvent.ts b/src/classes/SoundEvent.ts index 7381ba9..ea39327 100644 --- a/src/classes/SoundEvent.ts +++ b/src/classes/SoundEvent.ts @@ -303,7 +303,8 @@ export class SoundEvent extends AudibleEvent { // Unit public stretch = (beat: number) => { this.updateValue("unit", "c"); - this.updateValue("speed", 2 / beat) + this.updateValue("speed", 1 / beat) + this.updateValue("cut", beat) return this; } diff --git a/src/documentation/engine.ts b/src/documentation/engine.ts index 99dc921..6c2d6b2 100644 --- a/src/documentation/engine.ts +++ b/src/documentation/engine.ts @@ -201,7 +201,7 @@ ${makeExample( // Using some of the modifiers described above :) beat(.5)::snd('pad').begin(0.2) .speed([1, 0.9, 0.8].beat(4)) - .n([0, 0, 2, 4].beat(4)).pan(usine(.5)) + .n(2).pan(usine(.5)) .end(rand(0.3,0.8)) .room(0.8).size(0.5) .clip(1).out() @@ -213,11 +213,11 @@ ${makeExample( "Playing an amen break", ` // Note that stretch has the same value as beat -beat(4) :: sound('breaks165').stretch(4).out() -beat(0.25) :: sound('hh').out() -beat(1, 4, 8) :: sound('bd').out()`, +beat(4) :: sound('amen1').n(11).stretch(4).out() +beat(1) :: sound('kick').shape(0.35).out()`, true, )}; + ## Filters From 0f9c33d2df6849372ec988984e8c104899759616 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Thu, 5 Oct 2023 16:28:49 +0200 Subject: [PATCH 4/4] adding BPM to viewer --- index.html | 1 - src/TransportNode.js | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 717406a..2035ac9 100644 --- a/index.html +++ b/index.html @@ -99,7 +99,6 @@ -