diff --git a/src/API.ts b/src/API.ts index 8385477..0a916b5 100644 --- a/src/API.ts +++ b/src/API.ts @@ -938,7 +938,7 @@ export class UserAPI { // Time markers // ============================================================= - bar = (): number => { + cbar = (): number => { /** * Returns the current bar number * @@ -947,7 +947,7 @@ export class UserAPI { return this.app.clock.time_position.bar + 1; }; - tick = (): number => { + ctick = (): number => { /** * Returns the current tick number * @@ -956,7 +956,7 @@ export class UserAPI { return this.app.clock.tick + 1; }; - pulse = (): number => { + cpulse = (): number => { /** * Returns the current pulse number * @@ -965,7 +965,7 @@ export class UserAPI { return this.app.clock.time_position.pulse + 1; }; - beat = (): number => { + cbeat = (): number => { /** * Returns the current beat number * @@ -1008,7 +1008,7 @@ export class UserAPI { // Time Filters // ============================================================= - public mod = (...n: number[]): boolean => { + public beat = (...n: number[]): boolean => { const results: boolean[] = n.map( (value) => this.app.clock.pulses_since_origin % Math.floor(value * this.ppqn()) === @@ -1017,33 +1017,22 @@ export class UserAPI { return results.some((value) => value === true); }; - public modpulse = (...n: number[]): boolean => { + public pulse = (...n: number[]): boolean => { const results: boolean[] = n.map( (value) => this.app.clock.pulses_since_origin % value === 0 ); return results.some((value) => value === true); }; - modp = this.modpulse; - public modbar = (...n: number[]): boolean => { - const results: boolean[] = n.map( - (value) => - this.app.clock.time_position.bar % Math.floor(value * this.ppqn()) === 0 - ); - return results.some((value) => value === true); - }; - modb = this.modbar; + // ============================================================= + // Modulo based time filters + // ============================================================= - // Original implementation - // public div = (chunk: number): boolean => { - // const time_pos = this.app.clock.pulses_since_origin; - // const current_chunk = Math.floor( - // time_pos / Math.floor(chunk * this.ppqn()) - // ); - // return current_chunk % 2 === 0; - // }; + // ============================================================= + // Other core temporal functions + // ============================================================= - public div = (chunk: number, ratio: number = 50): boolean => { + public flip = (chunk: number, ratio: number = 50): boolean => { /** * Determines if the current time position is in the first * or second half of a given time chunk. @@ -1059,12 +1048,16 @@ export class UserAPI { return pos_within_chunk < threshold; }; - public divbar = (chunk: number): boolean => { + public flipbar = (chunk: number = 1): boolean => { const time_pos = this.app.clock.time_position.bar; const current_chunk = Math.floor(time_pos / chunk); return current_chunk % 2 === 0; }; + // ============================================================= + // "On" Filters + // ============================================================= + public onbar = ( bars: number[] | number, n: number = this.app.clock.time_signature[0] @@ -1095,7 +1088,7 @@ export class UserAPI { if (decimal_part <= 0) decimal_part = decimal_part + this.ppqn() * this.nominator(); final_pulses.push( - integral_part === this.beat() && this.pulse() === decimal_part + integral_part === this.cbeat() && this.cpulse() === decimal_part ); }); return final_pulses.some((p) => p == true); @@ -1199,7 +1192,7 @@ export class UserAPI { rotate: number = 0 ): boolean => { return ( - this.mod(div) && this._euclidean_cycle(pulses, length, rotate).div(div) + this.beat(div) && this._euclidean_cycle(pulses, length, rotate).beat(div) ); }; @@ -1249,7 +1242,7 @@ export class UserAPI { */ let convert: string = n.toString(2); let tobin: boolean[] = convert.split("").map((x: string) => x === "1"); - return this.mod(div) && tobin.div(div); + return this.beat(div) && tobin.beat(div); }; // ============================================================= diff --git a/src/ArrayExtensions.ts b/src/ArrayExtensions.ts index 54771fa..b87e53a 100644 --- a/src/ArrayExtensions.ts +++ b/src/ArrayExtensions.ts @@ -6,7 +6,7 @@ declare global { add(amount: number): number[]; sub(amount: number): number[]; mult(amount: number): number[]; - division(amount: number): number[]; + div(amount: number): number[]; palindrome(): T[]; random(index: number): T; rand(index: number): T; @@ -14,12 +14,11 @@ declare global { repeatAll(amount: number): T; repeatPair(amount: number): T; repeatOdd(amount: number): T; - beat(): T; + beat(division: number): T; bar(): T; pulse(): T; pick(): T; loop(index: number): T; - div(division: number): T; shuffle(): this; rotate(steps: number): this; unique(): this; @@ -75,7 +74,7 @@ export const makeArrayExtensions = (api: UserAPI) => { return this.map((x: number) => x * amount); }; - Array.prototype.division = function (amount: number): number[] { + Array.prototype.div = 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. @@ -93,15 +92,6 @@ export const makeArrayExtensions = (api: UserAPI) => { return this[Math.floor(api.randomGen() * this.length)]; }; - 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.app.clock.beats_since_origin / beat) % this.length]; - }; - Array.prototype.gen = function (min: number, max: number, times: number) { /** * Returns an array of random numbers. @@ -110,10 +100,13 @@ export const makeArrayExtensions = (api: UserAPI) => { * @param times - The number of random numbers to generate * @returns An array of random numbers */ - if(times < 1) { + if (times < 1) { return []; } - return Array.from({ length: times }, () => Math.floor(api.randomGen() * (max - min + 1)) + min); + return Array.from( + { length: times }, + () => Math.floor(api.randomGen() * (max - min + 1)) + min + ); }; Array.prototype.bar = function () { @@ -134,7 +127,7 @@ export const makeArrayExtensions = (api: UserAPI) => { return this[api.app.clock.time_position.pulse % this.length]; }; - Array.prototype.div = function (divisor: number) { + Array.prototype.beat = function (divisor: number = 1) { const chunk_size = divisor; // Get the first argument (chunk size) const timepos = api.app.clock.pulses_since_origin; const slice_count = Math.floor( diff --git a/src/classes/SoundEvent.ts b/src/classes/SoundEvent.ts index cd36250..748df3e 100644 --- a/src/classes/SoundEvent.ts +++ b/src/classes/SoundEvent.ts @@ -10,8 +10,19 @@ import { export class SoundEvent extends AudibleEvent { constructor(sound: string | object, public app: Editor) { super(app); - if (typeof sound === "string") this.values = { s: sound, dur: 0.5 }; - else this.values = sound; + if (typeof sound === "string") { + if (sound.includes(":")) { + this.values = { + s: sound.split(":")[0], + n: sound.split(":")[1], + dur: 0.5, + }; + } else { + this.values = { s: sound, dur: 0.5 }; + } + } else { + this.values = sound; + } } private updateValue(key: string, value: T): this { @@ -103,11 +114,13 @@ export class SoundEvent extends AudibleEvent { public begin = (value: number) => this.updateValue("begin", value); public end = (value: number) => this.updateValue("end", value); public gain = (value: number) => this.updateValue("gain", value); - public dbgain = (value: number) => this.updateValue("gain", Math.min(Math.pow(10, value / 20), 10)); + public dbgain = (value: number) => + this.updateValue("gain", Math.min(Math.pow(10, value / 20), 10)); public db = this.dbgain; public cutoff = (value: number) => this.updateValue("cutoff", value); public lpf = this.cutoff; - public resonance = (value: number) => this.updateValue("resonance", Math.min(Math.max(value, 0), 50)); + public resonance = (value: number) => + this.updateValue("resonance", Math.min(Math.max(value, 0), 50)); public lpq = this.resonance; public hcutoff = (value: number) => this.updateValue("hcutoff", value); public hpf = this.hcutoff; @@ -164,9 +177,9 @@ export class SoundEvent extends AudibleEvent { }; out = (): void => { - if(this.values.chord) { + if (this.values.chord) { this.values.chord.forEach((freq: number) => { - const copy = {...this.values}; + const copy = { ...this.values }; copy.freq = freq; superdough(copy, 1 / 4, this.values.dur || 0.5); }); diff --git a/src/documentation/bonus.ts b/src/documentation/bonus.ts index 3a43ea4..abab2bb 100644 --- a/src/documentation/bonus.ts +++ b/src/documentation/bonus.ts @@ -19,7 +19,7 @@ Some features are not part of the core of Topos but are still very useful. They ${makeExample( "Hydra integration", - `mod(4) :: app.hydra.osc(3, 0.5, 2).out()`, + `beat(4) :: app.hydra.osc(3, 0.5, 2).out()`, true )} @@ -34,8 +34,8 @@ Stopping **Hydra** is simple: ${makeExample( "Stopping Hydra", ` -mod(4) :: stop_hydra() // this one -mod(4) :: app.hydra.hush() // or this one +beat(4) :: stop_hydra() // this one +beat(4) :: app.hydra.hush() // or this one `, true )} diff --git a/src/documentation/chaining.ts b/src/documentation/chaining.ts index 6375057..49a5450 100644 --- a/src/documentation/chaining.ts +++ b/src/documentation/chaining.ts @@ -16,7 +16,7 @@ All functions from the sound object can be used to modify the event, for example ${makeExample( "Modifying sound events with probabilities", ` -mod(.5) && sound('numbers') +beat(.5) && sound('numbers') .odds(1/4, s => s.speed(irand(1,4))) .rarely(s => s.crush(3)) .out() @@ -27,7 +27,7 @@ ${makeExample( "Chance to change to a different note", ` rhythm(.5, 3, 8) && sound('pluck').note(38).out() -mod(.5) && sound('pluck').note(60) +beat(.5) && sound('pluck').note(60) .often(s => s.note(57)) .sometimes(s => s.note(64).n(irand(1,4))) .note(62) @@ -41,7 +41,7 @@ All the functions from the MIDI object can be used to modify the event with prob ${makeExample( "Modifying midi events with probabilities", - `mod(.5) && midi(60).channel(1) + `beat(.5) && midi(60).channel(1) .odds(1/4, n => n.channel(2)) .often(n => n.note+=4) .sometimes(s => s.velocity(irand(50,100))) @@ -63,7 +63,7 @@ ${makeExample( "Ziffer player using a sound chain and probabilities!", ` z1('s 0 5 7 0 3 7 0 2 7 0 1 7 0 1 6 5 4 3 2') - .octave([0, 1].div(2) - 1) + .octave([0, 1].beat(2) - 1) .scale('pentatonic').sound('pluck') .odds(1/4, n => n.delay(0.5).delayt(0.25)) .odds(1/2, n => n.speed(0.5)) diff --git a/src/documentation/code.ts b/src/documentation/code.ts index 8406639..1ed3f25 100644 --- a/src/documentation/code.ts +++ b/src/documentation/code.ts @@ -34,12 +34,12 @@ ${makeExample( "Shortening your if conditions", ` // 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() +beat(.75) :: snd('linnhats').n([1,4,5].beat()).out() +beat(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() +// beat(1) && snd('bd').out() +//// beat(1) :: snd('bd').out() `, true @@ -49,7 +49,7 @@ ${makeExample( "More complex conditions using ?", ` // The ? symbol can be used to write a if/true/false condition -mod(4) ? snd('kick').out() : mod(2)::snd('snare').out() +beat(4) ? snd('kick').out() : beat(2) :: snd('snare').out() // (true) ? log('very true') : log('very false') `, false @@ -60,14 +60,12 @@ ${makeExample( "Using not and other short symbols", ` // 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() +beat(4) ? snd('kick').out() : beat(2) :: snd('snare').out() +!beat(2) :: beat(0.5) :: snd('clap').out() `, false )} - - ## About crashes and bugs Things will crash, that's also part of the show. You will learn progressively to avoid mistakes and to write safer code. Do not hesitate to kill the page or to stop the transport if you feel overwhelmed by an algorithm blowing up. There are no safeties in place to save you. This is to ensure that you have all the available possible room to write bespoke code and experiment with your ideas through code. diff --git a/src/documentation/engine.ts b/src/documentation/engine.ts index 209aa10..6d60303 100644 --- a/src/documentation/engine.ts +++ b/src/documentation/engine.ts @@ -15,8 +15,8 @@ The basic function to play a sound is... sound(name: string) (you can a ${makeExample( "Playing sounds is easy", ` -mod(1) && sound('bd').out() -mod(0.5) && sound('hh').out() +beat(1) && sound('bd').out() +beat(0.5) && sound('hh').out() `, true )} @@ -31,8 +31,8 @@ Let's make it slightly more complex: ${makeExample( "Adding some effects", ` -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(); +beat(1) && sound('bd').coarse(0.25).room(0.5).orbit(2).out(); +beat(0.5) && sound('hh').delay(0.25).delaytime(0.125).out(); `, true )} @@ -51,7 +51,7 @@ 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') +beat(1) :: sound('pad') .begin(rand(0, 0.4)) .freq([50,52].beat()) .size(0.9) @@ -85,7 +85,7 @@ The .n(number) method can be used to pick a sample from the currently s ${makeExample( "Picking a sample", ` -mod(1) && sound('kick').n([1,2,3,4,5,6,7,8].pick()).out() +beat(1) && sound('kick').n([1,2,3,4,5,6,7,8].pick()).out() `, true )} @@ -96,7 +96,7 @@ ${makeExample( "Picking a sample... with your mouse!", ` // Move your mouse to change the sample being used! -mod(.25) && sound('numbers').n(Math.floor(mouseX())).out()`, +beat(.25) && sound('numbers').n(Math.floor(mouseX())).out()`, true )} @@ -108,7 +108,7 @@ As we said earlier, the sound('sample_name') function can be chained to ${makeExample( "Learning through repetition", ` -mod(0.5) && sound('hh') +beat(0.5) && sound('hh') .sometimes(s=>s.speed([1,5,10].pick())) .room(0.5) .cutoff(usine(2) * 5000) @@ -129,7 +129,7 @@ There is a special method to choose the _orbit_ that your sound is going to use: | Method | Alias | Description | |----------|-------|------------------------------------------------------------| -| orbit | | Orbit number | +| orbit | | Orbit number | ## Amplitude @@ -145,7 +145,7 @@ Simple controls over the amplitude (volume) of a given sound. ${makeExample( "Velocity manipulated by a counter", ` -mod(.5)::snd('cp').vel($(1)%10 / 10).out()`, +beat(.5)::snd('cp').vel($(1)%10 / 10).out()`, true )} @@ -165,10 +165,10 @@ 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).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)) +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() `, true @@ -192,9 +192,9 @@ ${makeExample( "Complex sampling duties", ` // Using some of the modifiers described above :) -mod(.5)::snd('pad').begin(0.2) - .speed([1, 0.9, 0.8].div(4)) - .n([0, 0, 2, 4].div(4)).pan(usine(.5)) +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)) .end(rand(0.3,0.8)) .room(0.8).size(0.5) .clip(1).out() @@ -220,7 +220,7 @@ There are three basic filters: a _lowpass_, _highpass_ and _bandpass_ filters wi ${makeExample( "Filter sweep using a low frequency oscillator", ` -mod(.5) && snd('sawtooth') +beat(.5) && snd('sawtooth') .cutoff([2000,500].pick() + usine(.5) * 4000) .resonance(0.9).freq([100,150].pick()) .out() @@ -240,7 +240,7 @@ A basic reverberator that you can use to give some depth to your sounds. This si ${makeExample( "Clapping in the cavern", ` -mod(2)::snd('cp').room(1).size(0.9).out() +beat(2)::snd('cp').room(1).size(0.9).out() `, true )}; @@ -259,9 +259,9 @@ A good sounding delay unit that can go into feedback territory. Use it without m ${makeExample( "Who doesn't like delay?", ` -mod(2)::snd('cp').delay(0.5).delaytime(0.75).delayfb(0.8).out() -mod(4)::snd('snare').out() -mod(1)::snd('kick').out() +beat(2)::snd('cp').delay(0.5).delaytime(0.75).delayfb(0.8).out() +beat(4)::snd('snare').out() +beat(1)::snd('kick').out() `, true )}; @@ -278,8 +278,8 @@ mod(1)::snd('kick').out() ${makeExample( "Crunch... crunch... crunch!", ` -mod(.5)::snd('pad').coarse($(1) % 16).clip(.5).out(); // Comment me -mod(.5)::snd('pad').crush([16, 8, 4].div(2)).clip(.5).out() +beat(.5)::snd('pad').coarse($(1) % 16).clip(.5).out(); // Comment me +beat(.5)::snd('pad').crush([16, 8, 4].beat(2)).clip(.5).out() `, true )}; diff --git a/src/documentation/functions.ts b/src/documentation/functions.ts index 99fafb5..c9a3c6f 100644 --- a/src/documentation/functions.ts +++ b/src/documentation/functions.ts @@ -86,7 +86,7 @@ You can control scripts programatically. This is the core concept of Topos after ${makeExample( "Calling a script! The most important feature!", ` -mod(1) :: script(1) +beat(1) :: script(1) `, true )} @@ -94,7 +94,7 @@ mod(1) :: script(1) ${makeExample( "Calling mutliple scripts at the same time.", ` -mod(1) :: script(1, 3, 5) +beat(1) :: script(1, 3, 5) `, false )} @@ -111,7 +111,7 @@ You can get the current position of the mouse on the screen by using the followi ${makeExample( "FM Synthesizer controlled using the mouse", ` -mod(.25) :: sound('sine') +beat(.25) :: sound('sine') .fmi(mouseX() / 100) .fmh(mouseY() / 100) .vel(0.2) @@ -129,7 +129,7 @@ Current mouse position can also be used to generate notes: ${makeExample( "The same synthesizer, with note control!", ` -mod(.25) :: sound('sine') +beat(.25) :: sound('sine') .fmi(mouseX() / 100) .note(noteX()) .fmh(mouseY() / 100) @@ -148,7 +148,7 @@ Low Frequency Oscillators (_LFOs_) are an important piece in any digital audio w ${makeExample( "Modulating the speed of a sample player using a sine LFO", - `mod(.25) && snd('cp').speed(1 + usine(0.25) * 2).out()`, + `beat(.25) && snd('cp').speed(1 + usine(0.25) * 2).out()`, true )}; @@ -158,7 +158,7 @@ ${makeExample( ${makeExample( "Modulating the speed of a sample player using a triangle LFO", - `mod(.25) && snd('cp').speed(1 + utriangle(0.25) * 2).out()`, + `beat(.25) && snd('cp').speed(1 + utriangle(0.25) * 2).out()`, true )} @@ -168,7 +168,7 @@ ${makeExample( ${makeExample( "Modulating the speed of a sample player using a saw LFO", - `mod(.25) && snd('cp').speed(1 + usaw(0.25) * 2).out()`, + `beat(.25) && snd('cp').speed(1 + usaw(0.25) * 2).out()`, true )} @@ -177,7 +177,7 @@ ${makeExample( ${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()`, + `beat(.25) && snd('cp').speed(1 + usquare(0.25, 0, 0.25) * 2).out()`, true )}; @@ -185,7 +185,7 @@ ${makeExample( ${makeExample( "Modulating the speed of a sample player using noise", - `mod(.25) && snd('cp').speed(1 + noise() * 2).out()`, + `beat(.25) && snd('cp').speed(1 + noise() * 2).out()`, true )}; @@ -254,8 +254,8 @@ ${makeExample( ${makeExample( "Using chance with other operators", ` - frequently() :: mod(1) :: sound('kick').out(); - often() :: mod(0.5) :: sound('hh').out(); + frequently() :: beat(1) :: sound('kick').out(); + often() :: beat(0.5) :: sound('hh').out(); sometimes() :: onbeat(1,3) :: sound('snare').out(); `, true @@ -264,12 +264,12 @@ ${makeExample( ${makeExample( "Using chance with chaining", ` - mod(0.5) && sound("bd") + beat(0.5) && sound("bd") .freq(100) .sometimes(s=>s.crush(2.5)) .out() - mod(0.5) && sound('arp').freq(100) + beat(0.5) && sound('arp').freq(100) .sometimes(n=>n.freq(200).delay(0.5)) .rarely(n=>n.freq(300).delay(2.5)) .almostNever(n=>n.freq(400)) @@ -293,8 +293,8 @@ ${makeExample( "Phased woodblocks", ` // Some very low-budget version of phase music -mod(.5) :: delay(usine(.125) * 80, () => sound('east').out()) -mod(.5) :: delay(50, () => sound('east').out()) +beat(.5) :: delay(usine(.125) * 80, () => sound('east').out()) +beat(.5) :: delay(50, () => sound('east').out()) `, true )} @@ -304,8 +304,8 @@ mod(.5) :: delay(50, () => sound('east').out()) ${makeExample( "Another woodblock texture", ` -mod(1) :: delayr(50, 4, () => sound('east').speed([0.5,.25].beat()).out()) -div(2) :: mod(2) :: delayr(150, 4, () => sound('east').speed([0.5,.25].beat() * 4).out()) +beat(1) :: delayr(50, 4, () => sound('east').speed([0.5,.25].beat()).out()) +flip(2) :: beat(2) :: delayr(150, 4, () => sound('east').speed([0.5,.25].beat() * 4).out()) `, true )}; diff --git a/src/documentation/inlineHelp.ts b/src/documentation/inlineHelp.ts index 112617b..9ed59b5 100644 --- a/src/documentation/inlineHelp.ts +++ b/src/documentation/inlineHelp.ts @@ -23,7 +23,7 @@ const completionDatabase: CompletionDatabase = { name: "delayr", category: "time", description: "Delay a function n times by t ms", - example: "delayr(50, 3, () => mod(1) :: log('delayed'))", + example: "delayr(50, 3, () => beat(1) :: log('delayed'))", }, toss: { name: "toss", @@ -115,12 +115,12 @@ const completionDatabase: CompletionDatabase = { description: "Log a value in the console", example: "log('Hello, world')", }, - div: { - name: "div", + flip: { + name: "flip", category: "patterns", description: - "Returns next value every n beats or true and false alternatively", - example: "div(4, 50) // 2 beats of true, 2 beats of false, 50/50.", + "Returns true and false alternatively or next value every n beats (arrays)", + example: "flip(4, 50) // 2 beats of true, 2 beats of false, 50/50.", }, n: { name: "n", @@ -192,7 +192,7 @@ const completionDatabase: CompletionDatabase = { name: "coarse", category: "synthesis", description: "Artificial sample-rate lowering", - example: "mod(.5)::snd('pad').coarse($(1) % 16).clip(.5).out();", + example: "beat(.5)::snd('pad').coarse($(1) % 16).clip(.5).out();", }, crush: { name: "crush", @@ -243,12 +243,6 @@ const completionDatabase: CompletionDatabase = { description: "Returns list index for the current bar (with wrapping)", example: "[0,1,2,3].bar()", }, - beat: { - name: "beat", - category: "patterns", - description: "Returns list index for the current beat (with wrapping)", - example: "[0,1,2,3].beat()", - }, room: { name: "room", category: "effect", @@ -335,17 +329,17 @@ const completionDatabase: CompletionDatabase = { example: "oncount([1,2,3], 4) // true on beats 1, 2 and 3 in a 4 beats period", }, - mod: { - name: "mod", + beat: { + name: "beat", category: "rhythm", - description: "return true every n pulsations.", - example: "mod(1) :: log(rand(1,5))", + description: "return true every n beats.", + example: "beat(1) :: log(rand(1,5))", }, - modp: { - name: "modp", + pulse: { + name: "pulse", category: "rhythm", - description: "return true every n ticks.", - example: "modp(8) :: log(rand(1,5))", + description: "return true every n pulses.", + example: "pulse(8) :: log(rand(1,5))", }, euclid: { name: "euclid", @@ -368,7 +362,7 @@ const completionDatabase: CompletionDatabase = { binrhythm: { name: "binrhythm", category: "rhythm", - description: "Binary rhythm generator", + description: "Binary rhythm generator (time, number)", example: "binrhythm(.5, 9223) :: sound('cp').out()", }, prob: { @@ -399,61 +393,61 @@ const completionDatabase: CompletionDatabase = { name: "odds", category: "randomness", description: "Return true with a probability of n %", - example: "odds(1/2) // 50% probability" + example: "odds(1/2) // 50% probability", }, never: { name: "never", category: "randomness", description: "Return false", - example: "never() // false" + example: "never() // false", }, almostNever: { name: "almostNever", category: "randomness", description: "Return true with a probability of 2.5%", - example: "almostNever() // 2.5% chance" + example: "almostNever() // 2.5% chance", }, rarely: { name: "rarely", category: "randomness", description: "Return true with a probability of 10%", - example: "rarely() // 10% chance" + example: "rarely() // 10% chance", }, scarcely: { name: "scarcely", category: "randomness", description: "Return true with a probability of 25%", - example: "scarcely() // 25% chance" + example: "scarcely() // 25% chance", }, sometimes: { name: "sometimes", category: "randomness", description: "Return true with a probability of 50%", - example: "sometimes() // 50% chance" + example: "sometimes() // 50% chance", }, often: { name: "often", category: "randomness", description: "Return true with a probability of 75%", - example: "often() // 75% chance" + example: "often() // 75% chance", }, frequently: { name: "frequently", category: "randomness", description: "Return true with a probability of 90%", - example: "frequently() // chance" + example: "frequently() // chance", }, almostAlways: { name: "almostAlways", category: "randomness", description: "Return true with a probability of 98.5%", - example: "almostAlways() // 98.5% chance" + example: "almostAlways() // 98.5% chance", }, always: { name: "always", category: "randomness", description: "Return true", - example: "always() // true" + example: "always() // true", }, sound: { name: "sound", @@ -484,7 +478,7 @@ const completionDatabase: CompletionDatabase = { name: "script", category: "core", description: "Execute one or more local scripts", - example: "mod(1) :: script(1)", + example: "beat(1) :: script(1)", }, clear_script: { name: "clear_script", @@ -510,18 +504,18 @@ const completionDatabase: CompletionDatabase = { description: "jumps to the n beat of the clock.", example: "beat_warp(1) :: log('back to the first beat!')", }, - divbar: { - name: "divbar", + flipbar: { + name: "flipbar", category: "time", description: - "works just like div but at the level of bars instead of beats", - example: "divbar(2)::mod(1)::snd('kick').out()", + "works just like flip at the level of bars instead of beats", + example: "flipbar(2)::beat(1)::snd('kick').out()", }, onbar: { name: "onbar", category: "time", description: "return true when targetted bar(s) is/are reached in period", - example: "onbar(4, 4)::mod(.5)::snd('hh').out();", + example: "onbar(4, 4)::beat(.5)::snd('hh').out();", }, begin: { name: "begin", @@ -629,7 +623,7 @@ const completionDatabase: CompletionDatabase = { name: "speak", category: "synthesis", description: "Text to speech synthesizer", - example: "mod(2) :: speak('Topos!','fr',irand(0,5))", + example: "beat(2) :: speak('Topos!','fr',irand(0,5))", }, midi_outputs: { name: "midi_outputs", @@ -775,11 +769,11 @@ const completionDatabase: CompletionDatabase = { description: "Multiply each element of the given array by a value", example: "[0,1,2,3].mul(2)", }, - division: { + div: { name: "div", category: "patterns", description: "Divide each element of the given array by a value", - example: "[0,1,2,3].division(2)", + example: "[0,1,2,3].div(2)", }, scale: { name: "scale", diff --git a/src/documentation/interface.ts b/src/documentation/interface.ts index a8d510f..7ab56b8 100644 --- a/src/documentation/interface.ts +++ b/src/documentation/interface.ts @@ -35,8 +35,8 @@ Every Topos session is composed of several small scripts. A set of scripts is ca ${makeExample( "To take the most out of Topos...", `// Write your code in multiple scripts. Use all the code buffers! -mod(1) :: script(1) -div(4) :: mod(.5) :: script(2) +beat(1) :: script(1) +flip(4) :: beat(.5) :: script(2) `, true )} @@ -44,8 +44,8 @@ div(4) :: mod(.5) :: script(2) ${makeExample( "Script execution can become musical too!", `// You can play your scripts... algorithmically. -mod(1) :: script([1,3,5].pick()) -div(4) :: mod([.5, .25].div(16)) :: script([5,6,7,8].loop($(2))) +beat(1) :: script([1,3,5].pick()) +flip(4) :: beat([.5, .25].beat(16)) :: script([5,6,7,8].loop($(2))) `, false )} diff --git a/src/documentation/introduction.ts b/src/documentation/introduction.ts index d403e3e..d7e0a13 100644 --- a/src/documentation/introduction.ts +++ b/src/documentation/introduction.ts @@ -16,17 +16,16 @@ ${makeExample( bpm(110) -mod(0.125) && sound('sawtooth') - .note([60, 62, 63, 67, 70].div(.125) + +beat(0.125) && sound('sawtooth') + .note([60, 62, 63, 67, 70].beat(.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(); - +beat(1) && snd('kick').out(); +beat(2) && snd('snare').out(); +beat(.5) && snd('hat').out(); `, true )} @@ -39,16 +38,16 @@ 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(.5) :: sound('jvbass').note(35 + [0,12].beat()).out() -mod([0.5, 0.25, 1, 2].div(1)) :: sound('east') +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 )} ${makeExample( "Computer music should be immediate and intuitive", - `mod(.5)::snd('sine') + `beat(.5)::snd('sine') .delay(0.5).delayt(0.25).delayfb(0.7) .room(0.8).size(0.8) .freq(mouseX()).out()`, @@ -58,9 +57,9 @@ ${makeExample( ${makeExample( "Making the web less dreadful, one beep at at time", ` -mod(.5) :: sound('sid').n($(2)).out() -mod(.25) :: sound('sid').note( - [34, 36, 41].div(.25) + [[0,-24].pick(),12].beat()) +beat(.5) :: sound('sid').n($(2)).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 )} diff --git a/src/documentation/midi.ts b/src/documentation/midi.ts index 217cea0..26eef0d 100644 --- a/src/documentation/midi.ts +++ b/src/documentation/midi.ts @@ -81,10 +81,10 @@ ${makeExample( "Playing some piano", ` bpm(80) // Setting a default BPM -mod(.5) && midi(36 + seqbeat(0,12)).sustain(0.02).out() -mod(.25) && midi([64, 76].pick()).sustain(0.05).out() -mod(.75) && midi(seqbeat(64, 67, 69)).sustain(0.05).out() -sometimes() && mod(.25) && midi(seqbeat(64, 67, 69) + 24).sustain(0.05).out() +beat(.5) && midi(36 + [0,12].beat()).sustain(0.02).out() +beat(.25) && midi([64, 76].pick()).sustain(0.05).out() +beat(.75) && midi([64, 67, 69].beat()).sustain(0.05).out() +beat(.25) && midi([64, 67, 69].beat() + 24).sustain(0.05).out() `, true )} @@ -133,7 +133,7 @@ sysex(0x90, 0x40, 0x7f) ${makeExample( "Tic, tac, tic, tac...", ` -mod(.25) && midi_clock() // Sending clock to MIDI device from the global buffer +beat(.25) && midi_clock() // Sending clock to MIDI device from the global buffer `, true )} diff --git a/src/documentation/patterns.ts b/src/documentation/patterns.ts index a7dfcfb..0f73585 100644 --- a/src/documentation/patterns.ts +++ b/src/documentation/patterns.ts @@ -12,30 +12,30 @@ Music really comes to life when you start playing with algorithmic patterns. The JavaScript is using [Arrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) as a data structure for lists. Topos is extending them with custom methods that allow you to enter softly into a universe of musical patterns. These methods can often be chained to compose a more complex expression: [1, 2, 3].repeatOdd(5).palindrome().beat(). -- div(division: number): this method will return the next value in the list every _n_ pulses. By default, 1 equals to one beat but integer and floating point number values are supported as well. This method is extremely powerful and can be used for many different purposes. Check out the examples. +- beat(division: number): this method will return the next value in the list every _n_ pulses. By default, 1 equals to one beat but integer and floating point number values are supported as well. This method is extremely powerful and can be used for many different purposes. Check out the examples. ${makeExample( "Light drumming", ` // 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() +beat([1, 0.75].beat(4)) :: sound('cp').out() +beat([0.5, 1].beat(4)) :: sound('kick').out() +beat(2)::snd('snare').shape(.5).out() `, true )} ${makeExample( - "Using div to create arpeggios", + "Using beat to create arpeggios", ` // Arpeggio using pulse divisions -mod([.5, .25].div(2)) :: sound('sine') +beat([.5, .25].beat(2)) :: sound('sine') .hcutoff(400) - .fmi([1,2].div(8)) - .fmh([0.5,0.25,1].div(2)) - .note([50,53,57].div(.25) + [12,24].div(2)) - .sustain([0.25, 0.5].div(8)) + .fmi([1,2].beat(8)) + .fmh([0.5,0.25,1].beat(2)) + .note([50,53,57].beat(.25) + [12,24].beat(2)) + .sustain([0.25, 0.5].beat(8)) .room(0.9).size(0.5) - .delay(0.25).delayt([0.5,0.25].div(16)) + .delay(0.25).delayt([0.5,0.25].beat(16)) .delayfb(0.5) .out() `, @@ -44,48 +44,44 @@ mod([.5, .25].div(2)) :: sound('sine') ${makeExample( "Cool ambiance", ` -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() +beat(.5) :: snd(['kick', 'hat'].beat(4)).out() +beat([2,4].beat(2)) :: snd('shaker').delay(.5).delayfb(.75).delayt(0.125).out() +flip(2)::beat(1)::snd('clap').out() +flip(4)::beat(2)::snd('pad').n(2).shape(.5).orbit(2).room(0.9).size(0.9).release(0.5).out() `, false )} - - -- beat(): returns the index of the list corresponding to current beat (with wrapping). This allows you to return a different value for each beat. - pulse(): returns the index of the list corresponding to the current pulse (with wrapping). This method will return a different value for each pulse. - bar(): returns the index of the list corresponding to the current bar (with wrapping). This method will return a different value for each bar. ${makeExample( "A simple drumbeat in no time!", ` -mod(1)::sound(['kick', 'hat', 'snare', 'hat'].beat()).out() -mod(1.5)::sound(['jvbass', 'clap'].beat()).out() +beat(1)::sound(['kick', 'hat', 'snare', 'hat'].beat()).out() +beat(1.5)::sound(['jvbass', 'clap'].beat()).out() `, true )} ${makeExample( "Using beat, pulse and bar in the same code", - `mod(2)::snd('snare').out() -mod([1, 0.5].beat()) :: sound(['bass3'].bar()) + `beat(2)::snd('snare').out() +beat([1, 0.5].beat()) :: sound(['bass3'].bar()) .freq(100).n([12, 14].bar()) .speed([1,2,3].pulse()) .out() ` )} - - palindrome(): Concatenates a list with the same list in reverse. ${makeExample( "Palindrome filter sweep", ` -mod([1,.5,.25].beat()) :: snd('sine') - .freq([100,200,300].div(0.25)) - .fmi([1,2,3].palindrome().div(0.5)) +beat([1,.5,.25].beat()) :: snd('sine') + .freq([100,200,300].beat(0.25)) + .fmi([1,2,3].palindrome().beat(0.5)) .fmh([4, 8].palindrome().beat()) .cutoff([500,1000,2000,4000].palindrome().beat()) .sustain(0.1) @@ -94,7 +90,6 @@ mod([1,.5,.25].beat()) :: snd('sine') true )} - - random(index: number): pick a random element in the given list. - rand(index: number): shorter alias for the same method. - pick(): pick a random element in the list. @@ -103,8 +98,8 @@ mod([1,.5,.25].beat()) :: snd('sine') ${makeExample( "Sipping some gasoline at the robot bar", ` -mod(1)::snd('kick').shape(0.5).out() -mod([.5, 1].random() / 2) :: snd( +beat(1)::snd('kick').shape(0.5).out() +beat([.5, 1].random() / 2) :: snd( ['amencutup', 'synth2'].random()) .n(irand(4,10)) .cutoff(2000) @@ -116,9 +111,7 @@ mod([.5, 1].random() / 2) :: snd( ${makeExample( "Generate a list of random numbers", - ` - mod(0.5) && sound('arp').freq([].gen(300,600,10).div(3)).out() - `, + `beat(0.5) && sound('arp').freq([].gen(300,600,10).div(3)).out()`, true )} @@ -128,7 +121,7 @@ ${makeExample( "Amen break suffering from data loss", ` // 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() +beat(.25)::snd('amencutup').n([1,2,3,4,5,6,7,8,9].degrade(20).loop($(1))).out() `, true )} @@ -141,7 +134,7 @@ ${makeExample( "Repeating samples a given number of times", ` // 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() +beat(.25)::sound('amencutup').n([1,2,3,4,5,6,7,8].repeatAll(4).beat()).out() `, true )} @@ -151,7 +144,7 @@ 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() +beat(1) :: sound('numbers').n([1,2,3,4,5].loop($(3, 10, 2))).out() `, true )} @@ -161,7 +154,7 @@ mod(1) :: sound('numbers').n([1,2,3,4,5].loop($(3, 10, 2))).out() ${makeExample( "Shuffling a list for extra randomness", ` -mod(1) :: sound('numbers').n([1,2,3,4,5].shuffle().loop($(1)).out() +beat(1) :: sound('numbers').n([1,2,3,4,5].shuffle().loop($(1)).out() `, true )} @@ -171,7 +164,7 @@ mod(1) :: sound('numbers').n([1,2,3,4,5].shuffle().loop($(1)).out() ${makeExample( "To make things more complex... here you go", ` -mod(.5) :: snd('sine') +beat(.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! @@ -187,7 +180,7 @@ ${makeExample( "Demonstrative filtering. Final list is [100, 200]", ` // Remove unique and 100 will repeat four times! -mod(1)::snd('sine').sustain(0.1).freq([100,100,100,100,200].unique().beat()).out() +beat(1)::snd('sine').sustain(0.1).freq([100,100,100,100,200].unique().beat()).out() `, true )} @@ -199,9 +192,5 @@ mod(1)::snd('sine').sustain(0.1).freq([100,100,100,100,200].unique().beat()).out ${makeExample("Simple addition", `[1, 2 ,3].add(2).beat()`, true)} -## Simple patterns - -- divseq(div: number, ...values:any[]) - `; }; diff --git a/src/documentation/synths.ts b/src/documentation/synths.ts index c5842df..2d1a98c 100644 --- a/src/documentation/synths.ts +++ b/src/documentation/synths.ts @@ -19,7 +19,7 @@ The sound function can take the name of a synthesizer as first argument ${makeExample( "Simple synthesizer voice with filter", ` -mod(.5) && snd('sawtooth') +beat(.5) && snd('sawtooth') .cutoff([2000,500].pick() + usine(.5) * 4000) .resonance(0.9).freq([100,150].pick()) .out() @@ -30,10 +30,9 @@ mod(.5) && snd('sawtooth') ${makeExample( "Listening to the different waveforms from the sweetest to the harshest", ` -mod(.5) && snd(['sine', 'triangle', 'sawtooth', 'square'].beat()).freq(100).out() +beat(.5) && snd(['sine', 'triangle', 'sawtooth', 'square'].beat()).freq(100).out() .freq(50) - .out() - `, + .out()`, false )} @@ -41,11 +40,11 @@ mod(.5) && snd(['sine', 'triangle', 'sawtooth', 'square'].beat()).freq(100).out( ${makeExample( "Blessed by the square wave", ` -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') +beat(4) :: [100,101].forEach((freq) => sound('square').freq(freq).sustain(0.1).out()) +beat(.5) :: [100,101].forEach((freq) => sound('square').freq(freq*2).sustain(0.01).out()) +beat([.5, .75, 2].beat()) :: [100,101].forEach((freq) => sound('square') .freq(freq*4 + usquare(2) * 200).sustain(0.125).out()) -mod(.25) :: sound('square').freq(100*[1,2,4,8].beat()).sustain(0.1).out()`, +beat(.25) :: sound('square').freq(100*[1,2,4,8].beat()).sustain(0.1).out()`, false )} @@ -53,7 +52,7 @@ mod(.25) :: sound('square').freq(100*[1,2,4,8].beat()).sustain(0.1).out()`, ${makeExample( "Ghost carillon", ` -mod(1/8)::sound('sine') +beat(1/8)::sound('sine') .velocity(rand(0.0, 1.0)) .delay(0.75).delayt(.5) .sustain(0.4) @@ -76,9 +75,9 @@ The same basic waveforms can take additional methods to switch to a basic two op ${makeExample( "80s nostalgia", ` -mod(.25) && snd('sine') +beat(.25) && snd('sine') .fmi([1,2,4,8].pick()) - .fmh([1,2,4,8].div(8)) + .fmh([1,2,4,8].beat(8)) .freq([100,150].pick()) .sustain(0.1) .out() @@ -89,9 +88,9 @@ mod(.25) && snd('sine') ${makeExample( "Giving some love to weird ratios", ` -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') +beat([.5, .25].bar()) :: sound('sine').fm('2.2183:3.18293').sustain(0.05).out() +beat([4].bar()) :: sound('sine').fm('5.2183:4.5').sustain(0.05).out() +beat(.5) :: sound('sine') .fmh([1, 1.75].beat()) .fmi($(1) % 30).orbit(2).room(0.5).out()`, false @@ -101,7 +100,7 @@ mod(.5) :: sound('sine') ${makeExample( "Some peace and serenity", ` -mod(0.25) :: sound('sine') +beat(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) @@ -123,10 +122,10 @@ There is also a more advanced set of parameters you can use to control the envel ${makeExample( "FM Synthesis with envelope control", ` - mod(.5) :: sound('sine') - .note([50,53,55,57].div(.5) - 12) +beat(.5) :: sound('sine') + .note([50,53,55,57].beat(.5) - 12) .fmi(0.5 + usine(.25) * 1.5) - .fmh([2,4].div(.125)) + .fmh([2,4].beat(.125)) .fmwave('triangle') .fmsus(0).fmdec(0.2).out() `, @@ -142,16 +141,16 @@ ZZfX can be triggered by picking a default ZZfX waveform in the following list: ${makeExample( "Picking a waveform", ` -mod(.5) :: sound(['z_sine', 'z_triangle', 'z_sawtooth', 'z_tan', 'z_noise'].beat()).out() +beat(.5) :: sound(['z_sine', 'z_triangle', 'z_sawtooth', 'z_tan', 'z_noise'].beat()).out() `, true )} ${makeExample( "Minimalist chiptune", ` -mod(.5) :: sound('z_triangle') - .note([60, 67, 72, 63, 65, 70].div(.5)) - .zrand(0).curve([1,2,3,4].div(1)) +beat(.5) :: sound('z_triangle') + .note([60, 67, 72, 63, 65, 70].beat(.5)) + .zrand(0).curve([1,2,3,4].beat(1)) .slide(0.01).tremolo(12) .noise([0,0.5].beat()) .decay(0.3).sustain(0) @@ -187,7 +186,7 @@ It comes with a set of parameters that can be used to tweak the sound. Don't und ${makeExample( "Chaotic Noise source", ` -mod(.25) :: sound('z_tan') +beat(.25) :: sound('z_tan') .note(40).noise(rand(0.0, 1.0)) .pitchJump(84).pitchJumpTime(rand(0.0, 1.0)) .zcrush([0,1,2,3,4].beat()) @@ -201,7 +200,7 @@ mod(.25) :: sound('z_tan') ${makeExample( "What is happening to me?", ` -mod(1) :: snd('zzfx').zzfx([ +beat(1) :: snd('zzfx').zzfx([ [4.77,,25,,.15,.2,3,.21,,2.4,,,,,,,.23,.35], [1.12,,97,.11,.16,.01,4,.77,,,30,.17,,,-1.9,,.01,.67,.2] ].beat()).out() @@ -211,9 +210,9 @@ mod(1) :: snd('zzfx').zzfx([ ${makeExample( "Les voitures dans le futur", ` -mod(1) :: sound(['z_triangle', 'z_sine'].pick()) +beat(1) :: sound(['z_triangle', 'z_sine'].pick()) .note([60,63,72,75].pick()).tremolo(16) - .zmod([0, 1/2, 1/8].division(2).pick()) + .zmod([0, 1/2, 1/8].div(2).pick()) .attack(0.5).release(0.5).sustain(2).delay(0.8) .room(0.9).size(0.9) .delayt(0.75).delayfb(0.5).out() @@ -226,7 +225,7 @@ ${makeExample( "Designing a sound on the ZzFX website", ` -mod(2) :: sound('zzfx').zzfx([3.62,,452,.16,.1,.21,,2.5,,,403,.05,.29,,,,.17,.34,.22,.68]).out() +beat(2) :: sound('zzfx').zzfx([3.62,,452,.16,.1,.21,,2.5,,,403,.05,.29,,,,.17,.34,.22,.68]).out() `, true )} @@ -246,7 +245,7 @@ Topos can also speak using the [Web Speech API](https://developer.mozilla.org/en ${makeExample( "Hello world!", ` -mod(4) :: speak("Hello world!") +beat(4) :: speak("Hello world!") `, true )} @@ -254,7 +253,7 @@ mod(4) :: speak("Hello world!") ${makeExample( "Different voices", ` -mod(2) :: speak("Topos!","fr",irand(0,5)) +beat(2) :: speak("Topos!","fr",irand(0,5)) `, true )} @@ -278,7 +277,7 @@ ${makeExample( const object = ["happy","sad","tired"].pick() const sentence = subject+" "+verb+" "+" "+object - mod(6) :: sentence.pitch(0).rate(0).voice([0,2].pick()).speak() + beat(6) :: sentence.pitch(0).rate(0).voice([0,2].pick()).speak() `, true )} diff --git a/src/documentation/time.ts b/src/documentation/time.ts index 93ca4be..5a8ce7e 100644 --- a/src/documentation/time.ts +++ b/src/documentation/time.ts @@ -20,13 +20,13 @@ To change the tempo, use the bpm(number) function. The transport is con Let's study two very simple rhythmic functions, mod(n: ...number[]) and onbeat(...n:number[]). They are both easy to understand and powerful enough to get you to play your first rhythms. -- mod(...n: number[]): this function will return true every _n_ pulsations. The value 1 will return true at the beginning of each beat. Floating point numbers like 0.5 or 0.25 are also accepted. Multiple values can be passed to mod to generate more complex rhythms. +- beat(...n: number[]): this function will return true every _n_ beats. The value 1 will return true at the beginning of each beat. Floating point numbers like 0.5 or 0.25 are also accepted. Multiple values can be passed to beat to generate more complex rhythms. ${makeExample( "Using different mod values", ` // This code is alternating between different mod values -mod([1,1/2,1/4,1/8].div(2)) :: sound('bd').n(0).out() +beat([1,1/2,1/4,1/8].beat(2)) :: sound('bd').n(0).out() `, true )} @@ -35,37 +35,37 @@ ${makeExample( "Some sort of ringtone", ` 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(); -mod([1,0.75].div(2)) :: blip([50, 100].div(2)).out(); +beat(1) :: blip(200).out(); +beat(1/3) :: blip(400).out(); +flip(3) :: beat(1/6) :: blip(800).out(); +beat([1,0.75].beat(2)) :: blip([50, 100].beat(2)).out(); `, false )} -- modp(...n: number[]): extreme version of the mod 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. +- pulse(...n: number[]): faster version of the beat 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. ${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() +pulse(36) :: snd('east') + .n([2,4].beat(1)).out() +pulse([12, 36].beat(4)) :: snd('east') + .n([2,4].add(5).beat(1)).out() `, true )} ${makeExample( - "modp is the OG rhythmic function in Topos", + "pulse is the OG rhythmic function in Topos", ` -modp([48, 24, 16].div(4)) :: sound('linnhats').out() -mod(1)::snd('bd').out() +pulse([48, 24, 16].beat(4)) :: sound('linnhats').out() +beat(1)::snd('bd').out() `, false )}; -- onbeat(...n: number[]): By default, the bar is set in 4/4 with four beats per bar. The onbeat 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. +- onbeat(...n: number[]): The onbeat 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 4/4 bar meaning that you can target any of these beats (or in-between) with this function. ${makeExample( "Some simple yet detailed rhythms", @@ -82,20 +82,21 @@ ${makeExample( ` 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) +beat([.25, 1/8].beat(1.5))::snd('hat').n(2) .gain(rand(0.4, 0.7)) .pan(usine()).out() `, false )} -- oncount(beats: number[], meter: number): This function is similar to onbeat but it allows you to specify a custom meter (time signature denominator) for the beats. +- oncount(beats: number[], meter: number): This function is similar to onbeat but it allows you to specify a custom number of beats as the last argument. ${makeExample( "Using oncount to create more variation in the rhythm", ` bpm(120) - z1('q (0 4 2 9)+(0 3 1 5)').sound('sine').cutoff(400).delay(0.5).out() + z1('q (0 4 2 9)+(0 3 1 5)').sound('sawtooth').cutoff([400,500,1000,2000].beat(1)) + .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() `, @@ -106,10 +107,10 @@ ${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) :: + 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() `, true @@ -124,8 +125,9 @@ We included a bunch of popular rhythm generators in Topos such as the euclidian ${makeExample( "Classic euclidian club music patterns", ` -mod(.5) && euclid($(1), 5, 8) && snd('kick').out() -mod(.5) && euclid($(2), 2, 8) && snd('sd').out() +beat(.5) && euclid($(1), 5, 8) && snd('kick').out() +beat(.5) && euclid($(2), 2, 8) && snd('sd').out() +beat(4) :: sound('cp').out() `, true )} @@ -134,9 +136,9 @@ ${makeExample( "And now for more interesting rhythmic constructions", ` 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() +beat(.5) && euclid($(1), 5, 8) :: sound('bd').out() +beat(.5) && euclid($(2), [1,0].beat(8), 8) :: sound('sd').out() +beat(.5) && euclid($(6), [6,7].beat(8), 8) :: sound('hh').out() `, false )} @@ -144,10 +146,10 @@ mod(.5) && euclid($(6), [6,7].div(8), 8) :: sound('hh').out() ${makeExample( "Adding more rhythmic density", ` -mod(.5) && euclid($(1), 5, 9) && snd('kick').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() +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(.25) && euclid($(4), 7, 9, 1) && snd('hh').out() `, false )} @@ -159,7 +161,7 @@ ${makeExample( "Using oneuclid to create a rhythm without iterators", ` // Change speed using bpm - // bpm(250) + bpm(250) oneuclid(5, 9) :: snd('kick').out() oneuclid(7,16) :: snd('east').end(0.5).n(irand(3,5)).out() `, @@ -170,7 +172,7 @@ ${makeExample( ${makeExample( "rhythm is a beginner friendly rhythmic function!", ` -let speed = [0.5, 0.25].div(8); +let speed = [0.5, 0.25].beat(8); bpm(140); 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() @@ -179,16 +181,14 @@ rhythm(speed, 7, 12) :: snd('east').n(9).out() true )} - - - bin(iterator: number, n: number): boolean: a binary rhythm generator. It transforms the given number into its binary representation (_e.g_ 34 becomes 100010). It then returns a boolean value based on the iterator in order to generate a rhythm. - binrhythm(divisor: number, n: number): boolean: boolean: iterator-less version of the binary rhythm generator. ${makeExample( "Change the integers for a surprise rhythm!", ` -mod(.5) && bin($(1), 34) && snd('kick').out() -mod(.5) && bin($(2), 48) && snd('sd').out() +beat(.5) && bin($(1), 34) && snd('kick').out() +beat(.5) && bin($(2), 48) && snd('sd').out() `, true )} @@ -203,20 +203,21 @@ binrhythm(.5, 18) && snd('sd').out() )} ${makeExample( - "Calling 911", + "Submarine jungle music", ` -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() +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() +beat(.5) && sound('amencutup').n(irand(2,7)).shape(0.3).out() `, false )} ${makeExample( - "Playing around with simple numbers", + "Using tabla to play unpredictable rhythms", ` -mod(.5) && bin($(1), [123, 456, 789].div(4)) +beat(.5) && bin($(1), [123, 456, 789].beat(4)) && snd('tabla').n($(2)).delay(0.5).delayt(0.25).out() -mod(1) && sound('kick').shape(0.5).out() +beat(1) && sound('kick').shape(0.5).out() `, false )} @@ -226,34 +227,34 @@ If you don't find it spicy enough, you can add some more probabilities to your r ${makeExample( "Probablistic drums in one line!", ` -prob(60)::mod(.5) && euclid($(1), 5, 8) && snd('kick').out() -prob(60)::mod(.5) && euclid($(2), 3, 8) && snd('sd').out() -prob(80)::mod(.5) && sound('hh').out() +prob(60)::beat(.5) && euclid($(1), 5, 8) && snd('kick').out() +prob(60)::beat(.5) && euclid($(2), 3, 8) && snd('sd').out() +prob(80)::beat(.5) && sound('hh').out() `, true )} ## Time Warping -Time is cool. But it's even cooler when you can manipulate it to your liking. Think about jumping back or forward in time. Think about looping a specific part of your current pattern or song. This is all possible thanks to two simple functions: warp(n: number) and beat_warp(n: number). They are both very easy to use and very powerful. Let's see how they work. +Time generally flows from the past to the future. However, it's even cooler when you can manipulate it to your liking by jumping back and forth. Think about looping a specific part of your current pattern or song or jumping all of the sudden in the future. This is entirely possible thanks to two simple functions: warp(n: number) and beat_warp(n: number). They are both very easy to use and very powerful. Let's see how they work. -- warp(n: number): this function jumps to the _n_ tick of the clock. 1 is the first pulsation ever, and the number keeps increasing to the infinite. +- warp(n: number): this function jumps to the _n_ tick of the clock. 1 is the first pulsation ever and the number keeps increasing indefinitely. You are most likely currently listening to tick n°12838123. ${makeExample( - "Jumping back and forth in time", + "Time is now super elastic!", ` // Obscure Shenanigans - Bubobubobubo -mod([1/4,1/8,1/16].div(8)):: sound('sine') - .freq([100,50].div(16) + 50 * ($(1)%10)) +beat([1/4,1/8,1/16].beat(8)):: sound('sine') + .freq([100,50].beat(16) + 50 * ($(1)%10)) .gain(0.5).room(0.9).size(0.9) .sustain(0.1).out() -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() -// Time is elastic now! -mod(.25) :: warp([12, 48, 24, 1, 120, 30].pick()) +beat(1) :: sound('kick').out() +beat(2) :: sound('dr').n(5).out() +flip(3) :: beat([.25,.5].beat(.5)) :: sound('dr') + .n([8,9].pick()).gain([.8,.5,.25,.1,.0].beat(.25)).out() +// Jumping back and forth in time +beat(.25) :: warp([12, 48, 24, 1, 120, 30].pick()) `, true )} @@ -264,18 +265,18 @@ ${makeExample( "Jumping back and forth with beats", ` // Resonance bliss - Bubobubobubo -mod(.25)::snd('arpy') +beat(.25)::snd('arpy') .note(30 + [0,3,7,10].beat()) .cutoff(usine(.5) * 5000).resonance(10).gain(0.3) .end(0.8).room(0.9).size(0.9).n(0).out(); -mod([.25,.125].div(2))::snd('arpy') +beat([.25,.125].beat(2))::snd('arpy') .note(30 + [0,3,7,10].beat()) .cutoff(usine(.5) * 5000).resonance(20).gain(0.3) .end(0.8).room(0.9).size(0.9).n(3).out(); -mod(.5) :: snd('arpy').note( - [30, 33, 35].repeatAll(4).div(1) - [12,0].div(0.5)).out() +beat(.5) :: snd('arpy').note( + [30, 33, 35].repeatAll(4).beat(1) - [12,0].beat(0.5)).out() // Comment me to stop warping! -mod(1) :: beat_warp([2,4,5,10,11].pick()) +beat(1) :: beat_warp([2,4,5,10,11].pick()) `, true )} @@ -284,13 +285,13 @@ mod(1) :: beat_warp([2,4,5,10,11].pick()) Now you know how to play some basic rhythmic music but you are a bit stuck in a one-bar long loop. Let's see how we can think about time flowing on longer periods. The functions you are going to learn now are _very fundamental_ and all the fun comes from mastering them. **Read and experiment a lot with the following examples**. -- div(n: number, ratio: number = 50): the div method is a temporal switch. If the value 2 is given, the function will return true for two beats and false for two beats. There are multiple ways to use it effectively. You can pass an integer or a floating point number. +- flip(n: number, ratio: number = 50): the flip method is a temporal switch. If the value 2 is given, the function will return true for two beats and false for two beats. There are multiple ways to use it effectively. You can pass an integer or a floating point number. - ratio: number = 50: this argument is ratio expressed in %. It determines how much of the period should be true or false. A ratio of 75 means that 75% of the period will be true. A ratio of 25 means that 25% of the period will be true. ${makeExample( "Two beats of silence, two beats of playing", ` -div(4) :: mod(1) :: snd('kick').out() +flip(4) :: beat(1) :: snd('kick').out() `, true )} @@ -298,9 +299,10 @@ div(4) :: mod(1) :: snd('kick').out() ${makeExample( "Clapping on the edge", ` -div(2.5, 10) :: mod(.25) :: snd('cp').out() -div(2.5, 75) :: mod(.25) :: snd('click').speed(2).end(0.2).out() -div(2.5) :: mod(.5) :: snd('bd').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) :: beat(.5) :: snd('bd').out() +beat(.25) :: sound('hh').out() `, false )} @@ -308,35 +310,35 @@ div(2.5) :: mod(.5) :: snd('bd').out() ${makeExample( "Good old true and false", ` -if (div(4, 75)) { - mod(1) :: snd('kick').out() +if (flip(4, 75)) { + beat(1) :: snd('kick').out() } else { - mod(.5) :: snd('snare').out() + beat(.5) :: snd('snare').out() } `, true )} -div 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. +flip 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 flip to generate a 4 bars long pattern. ${makeExample( "Clunky algorithmic rap music", ` // Rap God VS Lil Wild -- Adel Faure -if (div(16)) { +if (flip(16)) { // Playing this part for two bars - mod(1.5)::snd('kick').out() - mod(2)::snd('snare').out() - mod(.5)::snd('hh').out() + beat(1.5)::snd('kick').out() + beat(2)::snd('snare').out() + beat(.5)::snd('hh').out() } else { // Now adding some birds and tablas - mod(1.5)::snd('kick').out() - mod(2)::snd('snare').out() - mod(.5)::snd('hh').out() - mod(.5)::snd('tabla').speed([1,2].pick()).end(0.5).out() - mod(2.34)::snd('birds').n(irand(1,10)) + beat(1.5)::snd('kick').out() + beat(2)::snd('snare').out() + beat(.5)::snd('hh').out() + beat(.5)::snd('tabla').speed([1,2].pick()).end(0.5).out() + beat(2.34)::snd('birds').n(irand(1,10)) .delay(0.5).delaytime(0.5).delayfb(0.25).out() - mod(.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 @@ -345,29 +347,29 @@ if (div(16)) { You can use it everywhere to spice things up, including as a method parameter picker: ${makeExample( - "div is great for parameter variation", + "flip is great for parameter variation", ` -mod(.5)::snd(div(4) ? 'kick' : 'hat').out() +beat(.5)::snd(flip(4) ? 'kick' : 'hat').out() `, true )} -- divbar(n: number): works just like div but at the level of bars instead of beats. It allows you to think about even bigger time cycles. You can also pair it with regular div for making complex algorithmic beats. +- flipbar(n: number = 1): this method works just like flip but counts in bars instead of beats. It allows you to think about even larger time cycles. You can also pair it with regular flip for writing complex and long-spanning algorithmic beats. ${makeExample( "Thinking music over bars", ` -divbar(2)::mod(1)::snd('kick').out() -divbar(3)::mod(.5)::snd('hat').out() +flipbar(2) :: beat(1):: snd('kick').out() +flipbar(3) :: beat(.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() +flipbar(2) + ? beat(.5) && snd(['kick', 'hh'].beat(1)).out() + : beat(.5) && snd(['east', 'east:2'].beat(1)).out() `, false )}; @@ -378,16 +380,21 @@ divbar(2) ${makeExample( "Using onbar for filler drums", ` -// Only play on the fourth bar of a four bar cycle. -onbar(4, 4)::mod(.5)::snd('hh').out(); - -// Here comes a longer version using JavaScript normal control flow -if (onbar([4, 1], 3)) { - mod(1)::snd('kick').out(); +bpm(150); +// Only play on the third and fourth bar of the cycle. +onbar([3,4], 4)::beat(.25)::snd('hh').out(); +// Using JavaScript regular control flow +if (onbar([1,2], 4)) { + beat(.5) :: sometimes() :: sound('east').out() + rhythm(.5, 3, 7) :: snd('kick').out(); + rhythm(.5, 1, 7) :: snd('jvbass').out(); + rhythm(.5, 2, 7) :: snd('snare').n(5).out(); } else { - mod(.5)::snd('sd').out(); -} -`, + beat(.5) :: rarely() :: sound('east').n($(1)).out() + rhythm(.5, 3, 7) :: snd('kick').n(4).out(); + rhythm(.5, 1, 7) :: snd('jvbass').n(2).out(); + rhythm(.5, 2, 7) :: snd('snare').n(3).out(); +}`, true )} @@ -401,13 +408,13 @@ To make a beat, you need a certain number of time grains or **pulses**. The **pu Every script can access the current time by using the following functions: -- bar(n: number): returns the current bar since the origin of time. +- cbar(n: number): returns the current bar since the origin of time. -- beat(n: number): returns the current beat since the beginning of the bar. +- cbeat(n: number): returns the current beat since the beginning of the bar. - ebeat(): returns the current beat since the origin of time (counting from 1). -- pulse(): returns the current bar since the origin of the beat. +- cpulse(): returns the current bar since the origin of the beat. - ppqn(): returns the current **PPQN** (see above). @@ -420,15 +427,22 @@ These values are **extremely useful** to craft more complex syntax or to write m ${makeExample( "Manual mode: using time primitives!", ` -if((bar() % 4) > 1) { - mod(1) && sound('kick').out() - rarely() && mod(.5) && sound('sd').out() - mod(.5) && sound('jvbass').freq(500).out() +// Manual time condition +if((cbar() % 4) > 1) { + beat(2) && sound('kick').out() + rarely() && beat(.5) && sound('sd').out() + beat([.5, .25].beat()) && sound('jvbass') + .freq(100 * [2, 1].pick()).dec(2) + .room(0.9).size(0.9).orbit(2).out() } else { - mod(.5) && sound('hh').out() - mod(.75) && sound('cp').out() - mod(.5) && sound('jvbass').freq(250).out() + beat(.5) && sound('hh').out() + beat(2) && sound('cp').out() + beat([.5, .5, .25].beat(.5)) && sound('jvbass') + .freq(100 * [3, 1].pick()).dec(2) + .room(0.9).size(0.9).orbit(2).out() } +// This is always playing no matter what happens +beat([.5, .5, 1, .25].beat(0.5)) :: sound('shaker').out() `, true )} diff --git a/src/documentation/ziffers.ts b/src/documentation/ziffers.ts index dc3d9df..104bbb5 100644 --- a/src/documentation/ziffers.ts +++ b/src/documentation/ziffers.ts @@ -43,7 +43,7 @@ z1('s 0 1 2 3 4 5 6 7 8 9').sound('pluck').release(0.1).sustain(0.25).out() ${makeExample( "Escaped pitches using curly brackets", ` -let pattern = div(4) ? z1('s _ _ 0 0 {9 11}') : z1('s _ 0 0 {10 12}'); +let pattern = flip(4) ? z1('s _ _ 0 0 {9 11}') : z1('s _ 0 0 {10 12}'); pattern.sound('pluck').sustain(0.1).room(0.9).out(); `, false @@ -52,7 +52,7 @@ pattern.sound('pluck').sustain(0.1).room(0.9).out(); ${makeExample( "Durations using letters and floating point numbers", ` -div(8) ? z1('s 0 e 1 q 2 h 3 w 4').sound('sine').scale("locrian").out() +flip(8) ? z1('s 0 e 1 q 2 h 3 w 4').sound('sine').scale("locrian").out() : z1('0.125 0 0.25 2').sound('sine').scale("locrian").out() `, false @@ -62,8 +62,8 @@ ${makeExample( "Disco was invented thanks to Ziffers", ` z1('e _ _ 0 ^ 0 _ 0 ^ 0').sound('jvbass').out() -mod(1)::snd('bd').out(); mod(2)::snd('sd').out() -mod(3) :: snd('cp').room(0.5).size(0.5).orbit(2).out() +beat(1)::snd('bd').out(); beat(2)::snd('sd').out() +beat(3) :: snd('cp').room(0.5).size(0.5).orbit(2).out() `, false )} @@ -132,7 +132,7 @@ ${makeExample( "Transposing chords", ` z1('q Fmaj Amin Dmin Cmaj Cdim') - .key(["F3","E3","D3","E3"].div(3)) + .key(["F3","E3","D3","E3"].beat(3)) .sound('sawtooth').out() ` )} @@ -177,7 +177,7 @@ z1("s (0,8) 0 0 (0,5) 0 0").sound('sine') .scale('minor').fmi(2).fmh(2).room(0.5) .size(0.5).sustain(0.1) .delay(0.5) .delay(0.125).delayfb(0.25).out(); -mod(.5) :: snd(['kick', 'hat'].div(.5)).out() +beat(.5) :: snd(['kick', 'hat'].beat(.5)).out() `, true )} @@ -188,17 +188,17 @@ Ziffers supports all the keys and scales. Keys can be defined by using [scientif | Scale name | Intervals | |------------|------------------------| -| Lydian | 2221221 | -| Mixolydian | 2212212 | -| Aeolian  | 2122122 | -| Locrian | 1221222 | -| Ionian | 2212221 | -| Dorian | 2122212 | -| Phrygian | 1222122 | -| Soryllic | 11122122| -| Modimic | 412122 | -| Ionalian   | 1312122 | -| ... | And it goes on for **1490** scales | +| Lydian | 2221221 | +| Mixolydian | 2212212 | +| Aeolian | 2122122 | +| Locrian | 1221222 | +| Ionian | 2212221 | +| Dorian | 2122212 | +| Phrygian | 1222122 | +| Soryllic | 11122122| +| Modimic | 412122 | +| Ionalian | 1312122 | +| ... | And it goes on for **1490** scales | ${makeExample( "What the hell is the Modimic scale?", @@ -207,7 +207,7 @@ z1("s (0,8) 0 0 (0,5) 0 0").sound('sine') .scale('modimic').fmi(2).fmh(2).room(0.5) .size(0.5).sustain(0.1) .delay(0.5) .delay(0.125).delayfb(0.25).out(); -mod(.5) :: snd(['kick', 'hat'].div(.5)).out() +beat(.5) :: snd(['kick', 'hat'].beat(.5)).out() `, true )} @@ -221,16 +221,16 @@ You can also use more traditional { - div(2) :: mod(.5) :: sound('square').fmi(2) + flip(2) :: beat(.5) :: sound('square').fmi(2) .cutoff(500 + usine(1/2)).n(irand(1,10)) - .note(e + oscillation + [0, 5].div(.5)).out() - !div(2) :: mod(.5) :: sound('sawtooth').fmi(2) + .note(e + oscillation + [0, 5].beat(.5)).out() + !flip(2) :: beat(.5) :: sound('sawtooth').fmi(2) .cutoff(500 + usine(1/2) * 5000).n(irand(1,10)) - .note(e + oscillation + [0, 5].div(.5)).out() + .note(e + oscillation + [0, 5].beat(.5)).out() }); oncount([2, 3.5, [5,0].pick()], 6) :: sound('snare').out() `, `// Computer Music Classroom, Monday (8AM) -- Bubobubobubo -let ur = [0, 12, 7].div(24), +let ur = [0, 12, 7].beat(24), fundamental = [0, 5, 10, 8, 6].repeatAll(4).bar(); -mod(.25) :: sound('square') +beat(.25) :: sound('square') .note(ur + fundamental + 40).n(1 + $(1) % 8) .atk(0.05).sustain(0.1).release(0.1) .room(0.9).size(0.9) .out() -mod(.25) :: sound('sawtooth') +beat(.25) :: sound('sawtooth') .note(ur + fundamental + 47).n(1 + $(2) % 8) .atk(0.05).sustain(0.1).release(0.1) .room(0.9).size(0.9) .out() -mod(.25) :: sound(['sawtooth', 'square'].bar()) +beat(.25) :: sound(['sawtooth', 'square'].bar()) .note(ur + fundamental + 40+[10,12].bar()).n(1 + $(3) % 8) .atk(0.05).sustain(0.1).release(0.1) .room(0.9).size(0.9).out() `, `// Lamento for Digital Harpists -- Bubobubobubo -mod(4) :: sound('triangle') +beat(4) :: sound('triangle') .note(60).fmwave('triangle').fmi(3.95) .fmh(0.25).release(1.5).sustain(0.5) .decay(1.125).vel(0.35).room(1.5) .size(1.9).out() -mod([.5,.25].div(1)) :: sound('triangle') - .note([67,72,75,77,79].shuffle().div(.25) - [12, 24].pick()) +beat([.5,.25].beat(1)) :: sound('triangle') + .note([67,72,75,77,79].shuffle().beat(.25) - [12, 24].pick()) .fmwave('triangle').fmi(3.99).fmh([1.001].pick() + usine() / 100) .release(.125).sustain(0.125) .room(1.5).size(1.9).out() -mod([4, 2, 8].pick() / [2,1].bar()) :: sound('triangle') +beat([4, 2, 8].pick() / [2,1].bar()) :: sound('triangle') .note([67,72,75,77,79].shuffle().loop($('lezgo'))) .fmwave('triangle').fmi(3.99).fmh([1.001].pick() + usine() / 100) .release(2).sustain(0.125).gain(1.5) @@ -50,35 +79,35 @@ mod([4, 2, 8].pick() / [2,1].bar()) :: sound('triangle') `, `// Super gentle computing - Bubobubobubo let melody = [30,30,34,35,37].palindrome() - .beat() + [0, -12].repeatAll(2).div(2) -if (div(8, 75)) { + .beat() + [0, -12].repeatAll(2).beat(2) +if (flip(8, 75)) { log('first section') - rhythm(.5, 4, 8) :: sound('ST12').n([0,1,2].div(0.5)).speed(0.5).out() - rhythm(.5, 6, 8) :: sound('ST20').n([0,1,2].div(0.5) + 20) + rhythm(.5, 4, 8) :: sound('ST12').n([0,1,2].beat(0.5)).speed(0.5).out() + rhythm(.5, 6, 8) :: sound('ST20').n([0,1,2].beat(0.5) + 20) .speed(0.25).end(0.1).orbit(2).room(0.5).size(0.5).out() - mod(.5) :: sound('ST01').note(melody) + beat(.5) :: sound('ST01').note(melody) .n($(1)).speed(0.5).room(0.5).size(0.5).out() } else { log('second section') rhythm(.5, 2, 8) :: sound('ST20') - .n([0,1,2].div(0.5)).speed(0.5) + .n([0,1,2].beat(0.5)).speed(0.5) .end(0.1).out() - mod(.5) :: sound('ST01').note(melody).n($(1)).speed(0.5).end(0.1).out() - mod(1) :: sound('ST02').note(melody).n($(1)).speed(0.5).end(0.1).out() + beat(.5) :: sound('ST01').note(melody).n($(1)).speed(0.5).end(0.1).out() + beat(1) :: sound('ST02').note(melody).n($(1)).speed(0.5).end(0.1).out() } `, `// Race day - Bubobubobubo bpm(125); -mod(.5) :: sound('STB6') +beat(.5) :: sound('STB6') .n(irand(1,10)).speed(0.5).rel(1) .sus(0.1).out() -rhythm(div(4) ? 1 : .5, 5, 8) :: sound('kick').out() -rhythm(div(2) ? .5 : .25, 7, 8) :: sound('click') +rhythm(flip(4) ? 1 : .5, 5, 8) :: sound('kick').out() +rhythm(flip(2) ? .5 : .25, 7, 8) :: sound('click') .vel(0.1 + utriangle(.25)).n(irand(1,5)).out() rhythm(.5, 2, 8) :: sound('snare').out() `, `// Structure et approximation - Bubobubobubo -mod(.25) :: sound('zzfx').zzfx( +beat(.25) :: sound('zzfx').zzfx( // Randomized chaos :) [ rand(1,5),,rand(500,1000),rand(.01, 0.02),, @@ -89,91 +118,91 @@ mod(.25) :: sound('zzfx').zzfx( .vel(0.1).gain(toss() ? .5 : .125) .delay(toss() ? 0.5 : 0).delayt(0.045).delayfb(0.1).out() rhythm(.5, toss() ? 5 : 7, 12) :: sound('kick').n(13).out() -rhythm(toss() ? .25 : .5, div(2) ? 3 : 5, 12) :: sound( +rhythm(toss() ? .25 : .5, flip(2) ? 3 : 5, 12) :: sound( toss() ? 'snare' : 'cp').n(5).out() -rhythm(div(2) ? .5 : .25, div(4) ? 8 : 11, 12) :: sound('hat') +rhythm(flip(2) ? .5 : .25, flip(4) ? 8 : 11, 12) :: sound('hat') .orbit(3).room(0.5).size(0.5).n(0).out() `, `// Part-Dieu - Bubobubobubo bpm(90); -mod(rarely(12) ? .5 : .25) :: sound('ST22') - .note([30, 30, 30, 31].repeatAll(8).div(.5)) - .cut(1).n([19, 21].div(.75)) +beat(rarely(12) ? .5 : .25) :: sound('ST22') + .note([30, 30, 30, 31].repeatAll(8).beat(.5)) + .cut(1).n([19, 21].beat(.75)) .cutoff(irand(200, 5000)) .resonance(rand(0.2,0.8)) .room(0.9).size(1).orbit(2) .speed(0.25).vel(0.3).end(0.5) .out() -mod(.5) :: snd('dr') +beat(.5) :: snd('dr') .n([0, 0, 0, 0, 2, 8].beat()) .gain(1).out() -mod(div(2) ? 1 : 0.75) :: snd('bd').n(2).out() -mod(4) :: snd('snare').n(5) +beat(flip(2) ? 1 : 0.75) :: snd('bd').n(2).out() +beat(4) :: snd('snare').n(5) .delay(0.5).delayt(bpm() / 60 / 8) .delayfb(0.25).out() `, `// Atarism - Bubobubobubo bpm(85); -let modifier = [.5, 1, 2].div(8); -let othermod = [1, .5, 4].div(4); -mod(modifier / 2):: sound('STA9').n([0,2].div(.5)).speed(0.5).vel(0.5).out() -mod(.5)::sound('STA9').n([0, 20].div(.5)).speed([1,1.5].repeatAll(4).beat() / 4) +let modifier = [.5, 1, 2].beat(8); +let othermod = [1, .5, 4].beat(4); +beat(modifier / 2):: sound('STA9').n([0,2].beat(.5)).speed(0.5).vel(0.5).out() +beat(.5)::sound('STA9').n([0, 20].beat(.5)).speed([1,1.5].repeatAll(4).beat() / 4) .cutoff(500 + usine(.25) * 3000).vel(0.5).out() -mod(modifier / 2):: sound('STA9') - .n([0,7].div(.5)).speed(div(othermod) ? 2 :4).vel(0.45).out() +beat(modifier / 2):: sound('STA9') + .n([0,7].beat(.5)).speed(flip(othermod) ? 2 :4).vel(0.45).out() rhythm(.25, 3, 8, 1) :: sound('STA9') .note([30, 33].pick()).n(32).speed(0.5).out() rhythm(othermod, 5, 8) :: sound('dr').n([0,1,2].beat()).out() -mod(1) :: sound('kick').vel(1).out() +beat(1) :: sound('kick').vel(1).out() `, `// 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') +beat(1) :: snd('kick').out(); +beat(2) :: snd('sd').room(0.9).size(0.9).out(); +beat(0.25) :: snd('hh').out(); +beat(2) :: snd('square') .cutoff(500).note(50-12).resonance(20).sustain(0.2).out() -mod(1/4)::snd(divseq(1, 'sawtooth', 'triangle', 'square')) - .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)) +beat(1/4)::snd(['sawtooth', 'triangle', 'square'].beat(1)) + .note([50, 53, 55, 50, 50, 52, 58, 50+12, 50+15].beat(4) + [0, 12, 24].beat(0.5)) + .cutoff(usine(.5)*10000).resonance([10,20].beat(2)) .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()`, +beat(4)::snd('amencutup').n($(19)).cut(1).orbit(2).pan(rand(0.0,1.0)).out()`, `// Crazy arpeggios - Bubobubobubo bpm(110) -mod(0.125) && sound('sawtooth') - .note([60, 62, 63, 67, 70].div(.125) + +beat(0.125) && sound('sawtooth') + .note([60, 62, 63, 67, 70].beat(.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(); +beat(1) && snd('kick').out(); +beat(2) && snd('snare').out(); +beat(.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)) +beat([1/4,1/8,1/16].beat(8)):: sound('sine') + .freq([100,50].beat(16) + 50 * ($(1)%10)) .gain(0.5).room(0.9).size(0.9) .sustain(0.1).out() -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() +beat(1) :: sound('kick').out() +beat(2) :: sound('dr').n(5).out() +flip(3) :: beat([.25,.5].beat(.5)) :: sound('dr') + .n([8,9].pick()).gain([.8,.5,.25,.1,.0].beat(.25)).out() `, `// Resonance bliss - Bubobubobubo -mod(.25)::snd('arpy') +beat(.25)::snd('arpy') .note(30 + [0,3,7,10].beat()) .cutoff(usine(.5) * 5000).resonance(10).gain(0.3) .end(0.8).room(0.9).size(0.9).n(0).out(); -mod([.25,.125].div(2))::snd('arpy') +beat([.25,.125].beat(2))::snd('arpy') .note(30 + [0,3,7,10].beat()) .cutoff(usine(.5) * 5000).resonance(20).gain(0.3) .end(0.8).room(0.9).size(0.9).n(3).out(); -mod(.5) :: snd('arpy').note( - [30, 33, 35].repeatAll(4).div(1) - [12,0].div(0.5)).out() +beat(.5) :: snd('arpy').note( + [30, 33, 35].repeatAll(4).beat(1) - [12,0].beat(0.5)).out() `, ]; diff --git a/vite.config.js b/vite.config.js index 2ac899d..1ccb892 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,13 +1,25 @@ -import { defineConfig } from 'vite'; +import { defineConfig } from "vite"; // import * as mdPlugin from 'vite-plugin-markdown'; -export default defineConfig({ - assetsInclude: ['**/*.md'], - // plugins: [mdPlugin(options)], - build: { - chunkSizeWarningLimit: 1600 - }, - preview: { - open: true - } +export default defineConfig(({ command, mode, ssrBuild }) => { + if (command === "serve") { + return { + assetsInclude: ["**/*.md"], + server: { + port: 8000, + strictPort: true, + }, + }; + } else { + return { + chunkSizeWarningLimit: 1600 * 2, + build: { + outDir: "dist", + emptyOutDir: true, + cssCodeSplit: true, + cssMinify: true, + minify: true, + }, + }; + } });