diff --git a/src/API.ts b/src/API.ts index 6de4545..887aee6 100644 --- a/src/API.ts +++ b/src/API.ts @@ -148,6 +148,28 @@ export class UserAPI { silence = this.stop; hush = this.stop; + // ============================================================= + // Time warp functions + // ============================================================= + + public warp = (n: number): void => { + /** + * Time-warp the clock by using the tick you wish to jump to. + */ + this.app.clock.tick = n; + this.app.clock.time_position = this.app.clock.convertTicksToTimeposition(n); + }; + + public beat_warp = (beat: number): void => { + /** + * Time-warp the clock by using the tick you wish to jump to. + */ + this.app.clock.tick = beat * this.app.clock.ppqn; + this.app.clock.time_position = this.app.clock.convertTicksToTimeposition( + beat * this.app.clock.ppqn + ); + }; + // ============================================================= // Mouse functions // ============================================================= @@ -910,7 +932,7 @@ export class UserAPI { /** * Returns the current number of pulses elapsed since origin of time */ - return this.app.clock.pulses_since_origin + 1; + return this.app.clock.pulses_since_origin + 1; }; nominator = (): number => { @@ -918,7 +940,7 @@ export class UserAPI { * Returns the current nominator of the time signature */ return this.app.clock.time_signature[0]; - } + }; meter = (): number => { /** @@ -935,20 +957,25 @@ export class UserAPI { public mod = (...n: number[]): boolean => { const results: boolean[] = n.map( - (value) => this.app.clock.pulses_since_origin % Math.floor(value * this.ppqn()) === 0 + (value) => + this.app.clock.pulses_since_origin % Math.floor(value * this.ppqn()) === + 0 ); return results.some((value) => value === true); }; public modpulse = (...n: number[]): boolean => { - const results: boolean[] = n.map((value) => this.app.clock.pulses_since_origin % value === 0); + 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 + (value) => + this.app.clock.time_position.bar % Math.floor(value * this.ppqn()) === 0 ); return results.some((value) => value === true); }; @@ -1000,7 +1027,7 @@ export class UserAPI { return final_pulses.some((p) => p == true); }; - oncount = (beats: number[]|number, count: number): boolean => { + oncount = (beats: number[] | number, count: number): boolean => { /** * Returns true if the current beat is in the given list of beats. * @@ -1010,11 +1037,11 @@ export class UserAPI { * @param beat - The beats to check * @returns True if the current beat is in the given list of beats */ - if(typeof beats === "number") beats = [beats]; + if (typeof beats === "number") beats = [beats]; const origin = this.app.clock.pulses_since_origin; let final_pulses: boolean[] = []; beats.forEach((b) => { - b = b<1 ? 0 : b-1; + b = b < 1 ? 0 : b - 1; const beatInTicks = Math.ceil(b * this.ppqn()); const meterPosition = origin % (this.ppqn() * count); return final_pulses.push(meterPosition === beatInTicks); diff --git a/src/Clock.ts b/src/Clock.ts index e50029d..aca16bc 100644 --- a/src/Clock.ts +++ b/src/Clock.ts @@ -59,6 +59,15 @@ export class Clock { }); } + convertTicksToTimeposition(ticks: number): TimePosition { + const beatsPerBar = this.app.clock.time_signature[0]; + const ppqnPosition = ticks % this.app.clock.ppqn; + const beatNumber = Math.floor(ticks / this.app.clock.ppqn); + const barNumber = Math.floor(beatNumber / beatsPerBar); + const beatWithinBar = Math.floor(beatNumber % beatsPerBar); + return { bar: barNumber, beat: beatWithinBar, pulse: ppqnPosition }; + } + get ticks_before_new_bar(): number { /** * This function returns the number of ticks separating the current moment diff --git a/src/TransportNode.js b/src/TransportNode.js index a515b5e..64c2fef 100644 --- a/src/TransportNode.js +++ b/src/TransportNode.js @@ -18,8 +18,7 @@ export class TransportNode extends AudioWorkletNode { this.app.clock.tick++ - const futureTimeStamp = this.convertTicksToTimeposition(this.app.clock.tick); - //console.log("BANG", this.logicalTime, futureTimeStamp); + const futureTimeStamp = this.app.clock.convertTicksToTimeposition(this.app.clock.tick); this.app.clock.time_position = futureTimeStamp; tryEvaluate(this.app, this.app.global_buffer); @@ -27,14 +26,6 @@ export class TransportNode extends AudioWorkletNode { } }; - convertTicksToTimeposition(ticks) { - const beatsPerBar = this.app.clock.time_signature[0]; - const ppqnPosition = (ticks % this.app.clock.ppqn); - const beatNumber = Math.floor(ticks / this.app.clock.ppqn); - const barNumber = Math.floor(beatNumber / beatsPerBar); - const beatWithinBar = Math.floor(beatNumber % beatsPerBar); - return {bar: barNumber, beat: beatWithinBar, pulse: ppqnPosition}; - } start() { this.port.postMessage("start"); diff --git a/src/documentation/time.ts b/src/documentation/time.ts index a0c5ae4..ef5bddb 100644 --- a/src/documentation/time.ts +++ b/src/documentation/time.ts @@ -223,7 +223,53 @@ prob(80)::mod(.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. + +- 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. + + +${makeExample( + "Jumping back and forth in time", + ` +// Obscure Shenanigans - Bubobubobubo +mod([1/4,1/8,1/16].div(8)):: sound('sine') + .freq([100,50].div(16) + 50 * ($(1)%10)) + .gain(0.5).room(0.9).size(0.9) + .sustain(0.1).out() +mod(1) :: sound('kick').out() +mod(2) :: sound('dr').n(5).out() +div(3) :: mod([.25,.5].div(.5)) :: sound('dr') + .n([8,9].pick()).gain([.8,.5,.25,.1,.0].div(.25)).out() +// Time is elastic now! +mod(.25) :: warp([12, 48, 24, 1, 120, 30].pick()) +`, + true +)} +- beat_warp(beat: number): this function jumps to the _n_ beat of the clock. The first beat is 1. + +${makeExample( + "Jumping back and forth with beats", + ` +// Resonance bliss - Bubobubobubo +mod(.25)::snd('arpy') + .note(30 + [0,3,7,10].beat()) + .cutoff(usine(.5) * 5000).resonance(10).gain(0.3) + .end(0.8).room(0.9).size(0.9).n(0).out(); +mod([.25,.125].div(2))::snd('arpy') + .note(30 + [0,3,7,10].beat()) + .cutoff(usine(.5) * 5000).resonance(20).gain(0.3) + .end(0.8).room(0.9).size(0.9).n(3).out(); +mod(.5) :: snd('arpy').note( + [30, 33, 35].repeatAll(4).div(1) - [12,0].div(0.5)).out() +// Comment me to stop warping! +mod(1) :: beat_warp([2,4,5,10,11].pick()) +`, + true +)} + ## Larger time divisions 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**.