@blibliki/transport 0.3.10 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- var l=class{context;clockTimeAt;contextTimeAt;audioClockOffset;_isRunning=!1;constructor(t,e){this.context=t,this.audioClockOffset=e,this.clockTimeAt={start:0,stop:0},this.contextTimeAt={start:0,stop:0}}get isRunning(){return this._isRunning}time(){if(!this.isRunning)return this.clockTimeAt.stop;let t=this.context.currentTime;return this.clockTimeAt.start+(t-this.contextTimeAt.start)}start(t){this.contextTimeAt.start=t,this.clockTimeAt.start=this.clockTimeAt.stop,this._isRunning=!0}stop(t){this._isRunning=!1,this.clockTimeAt.stop=this.clockTimeAt.start+(t-this.contextTimeAt.start),this.contextTimeAt.stop=t}jumpTo(t){this.clockTimeAt.stop=t}clockTimeToContextTime(t){let e=this.isRunning?"start":"stop";return this.audioClockOffset+this.contextTimeAt[e]+t-this.clockTimeAt[e]}};import{isNumber as M}from"@blibliki/utils";var c=15360,b=n=>60/n/c,S=n=>n/60*c,_=(n,t,e)=>{let i=n.findIndex(s=>e(t,s)<0);return i===-1?n.length:i};function C(n,t,e){return t+n*(e-t)}function g(n,t,e){return(n-t)/(e-t)}function E(n,t){if(t<.5||t>.75)throw new Error("Invalid swing amount");let e=n/7680,i=Math.floor(e),s=e-i,o=0;return s<=.5?o=C(g(s,0,.5),0,t):o=C(g(s,.5,1),t,1),Math.floor((i+o)*7680)}var a=class{_ticks;timeSignature;constructor(t,e){this.timeSignature=e,M(t)?this._ticks=t:typeof t=="string"?this._ticks=this.parseStringPosition(t):this._ticks=this.convertObjectToTicks(t)}get ticks(){return this._ticks}get bars(){return Math.floor(this.totalBeats/this.timeSignature[0])+1}get beats(){return this.totalBeats%this.timeSignature[0]+1}get sixteenths(){return Math.floor(this.beatFraction*4)+1}get totalBeats(){return Math.floor(this._ticks/c)}get beatFraction(){return this._ticks/c-this.totalBeats}toString(){return`${this.bars}:${this.beats}:${this.sixteenths}`}toObject(){return{bars:this.bars,beats:this.beats,sixteenths:this.sixteenths}}parseStringPosition(t){let e=t.split(":"),[i,s,o]=e,r=Number(i),k=Number(s),B=Number(o),P={bars:r,beats:k,sixteenths:B};return this.convertObjectToTicks(P)}convertObjectToTicks(t){let e=(t.bars-1)*this.timeSignature[0]+(t.beats-1),i=t.sixteenths/16;return(e+i)*c}};var u=class{_events=[];get events(){return this._events}add(t){let e=_(this._events,t,(i,s)=>i.time-s.time);this._events.splice(e,0,t)}find(t,e){return this._events.filter(i=>i.time>=t&&i.time<e)}lastEventBefore(t){for(let e=this._events.length-1;e>=0;e--){let i=this._events[e];if(i.time<=t)return i}}remove(t,e){this._events=this._events.filter(i=>!(i.time>=t&&i.time<e))}removeAllBefore(t){this._events=this._events.filter(e=>e.time>=t)}clear(){this._events=[]}};var T=class{timeline=new u;scheduleAheadTime;consumedTime;generator;consumer;constructor(t,e,i){this.generator=t,this.consumer=e,this.scheduleAheadTime=i,this.consumedTime=-this.scheduleAheadTime}_schedule(t,e){this.generator(t,e).forEach(i=>{this.timeline.add(i)})}runUntil(t){if(t<=this.consumedTime)throw new Error("Scheduling time is <= current time");this._schedule(this.consumedTime+this.scheduleAheadTime,t+this.scheduleAheadTime);let e=this.timeline.find(this.consumedTime,t);this.consumer(e),this.consumedTime=t,this.timeline.removeAllBefore(this.consumedTime)}jumpTo(t){this.timeline.clear(),this.consumedTime=t-this.scheduleAheadTime}};var h=class{clockTimeAtLastTempoChange=0;ticksAtLastTempoChange=0;_bpm=120;get bpm(){return this._bpm}update(t,e){let i=this.getTicks(t);this.clockTimeAtLastTempoChange=t,this.ticksAtLastTempoChange=i,this._bpm=e}getTicks(t){let i=(t-this.clockTimeAtLastTempoChange)*S(this.bpm);return Math.floor(this.ticksAtLastTempoChange+i)}getClockTime(t){let i=Math.floor(t-this.ticksAtLastTempoChange)*b(this.bpm);return this.clockTimeAtLastTempoChange+i}reset(t,e){this.clockTimeAtLastTempoChange=t,this.ticksAtLastTempoChange=e}};function N(n){let t=1;for(;n.has(t);)t++;return t}var v=class{_audioBuffer=null;_audioContext=null;_sampleDuration=null;get audioContext(){return this._audioContext??=new AudioContext,this._audioContext}get audioBuffer(){return this._audioBuffer??=new AudioBuffer({length:2,sampleRate:this.audioContext.sampleRate}),this._audioBuffer}get sampleDuration(){return this._sampleDuration??=2/this.audioContext.sampleRate,this._sampleDuration}},m=new v,A=new Map,p=new Map;var D=(n,t)=>{let e=t==="interval"?p:A;if(e.has(n)){let i=e.get(n);i!==void 0&&(i(),t==="timeout"&&A.delete(n))}},f=(n,t,e)=>{let i=performance.now(),s=new AudioBufferSourceNode(m.audioContext,{buffer:m.audioBuffer});s.onended=()=>{let o=performance.now()-i;o>=t?D(n,e):f(n,t-o,e),s.disconnect(m.audioContext.destination)},s.connect(m.audioContext.destination),s.start(Math.max(0,m.audioContext.currentTime+t/1e3-m.sampleDuration))},y=typeof window>"u",L=n=>{p.delete(n)};var O=(n,t)=>{let e=N(p);return p.set(e,()=>{n(),f(e,t,"interval")}),f(e,t,"interval"),e};var R=y?clearInterval:L;var w=y?setInterval:O;var d=class{timerId=void 0;interval;callback;constructor(t,e){this.callback=t,this.interval=e}start(){this.timerId=w(()=>{this.callback()},this.interval)}stop(){this.timerId!==void 0&&(R(this.timerId),this.timerId=void 0)}get isRunning(){return this.timerId!==void 0}};var I=(i=>(i.playing="playing",i.stopped="stopped",i.paused="paused",i))(I||{}),x=class{_initialized=!1;context;clock;timer;scheduler;_timeSignature=[4,4];tempo=new h;clockTime=0;_swingAmount=.5;listener;_clockCallbacks=[];constructor(t,e){this.context=t,this.listener=e,this.clock=new l(this.context,200/1e3),this.scheduler=new T(this.generateEvents,this.consumeEvents,200/1e3),this.timer=new d(()=>{let o=this.clock.time();o<=this.clockTime||(this.clockTime=o,this.scheduler.runUntil(o),this._clockCallbacks.forEach(r=>{r(this.clockTime)}))},20/1e3),this._initialized=!0}addClockCallback(t){this._clockCallbacks.push(t)}addBarCallback(t){let e=1/0;this.addClockCallback(()=>{let i=this.position;i.bars!==e&&(t(i.bars),e=i.bars)})}get time(){return this.clock.time()}get position(){let t=this.clock.time(),e=this.tempo.getTicks(t);return new a(e,this.timeSignature)}set position(t){this.jumpTo(t.ticks)}getPositionOfEvent(t){return new a(t.ticks,this.timeSignature)}start(){if(!this._initialized)throw new Error("Not initialized");if(this.clock.isRunning)return;let t=this.context.currentTime;this.listener.onStart(t),this.clock.start(t),this.timer.start()}stop(){if(!this._initialized)throw new Error("Not initialized");let t=this.context.currentTime;this.listener.silence(t),this.listener.onStop(t),this.clock.stop(t),this.timer.stop()}reset(){if(!this._initialized)throw new Error("Not initialized");let t=this.context.currentTime;this.listener.silence(t),this.jumpTo(0)}get bpm(){return this.tempo.bpm}set bpm(t){this.tempo.update(this.clockTime,t)}get timeSignature(){return this._timeSignature}set timeSignature(t){this._timeSignature=t}get state(){return this.clock.isRunning?"playing":this.time>0?"paused":"stopped"}get swingAmount(){return this._swingAmount}set swingAmount(t){this._swingAmount=t}jumpTo(t){let e=this.tempo.getClockTime(t);this.tempo.reset(e,t),this.clock.jumpTo(e),this.scheduler.jumpTo(e),this.clockTime=e,this.listener.onJump(t),this._clockCallbacks.forEach(i=>{i(this.clockTime)})}generateEvents=(t,e)=>{let i=this.tempo.getTicks(t),s=this.tempo.getTicks(e);return this.listener.generator(i,s).map(o=>({...o,ticks:E(o.ticks,this.swingAmount)})).map(o=>{let r=this.tempo.getClockTime(o.ticks),k=this.clock.clockTimeToContextTime(r);return{...o,time:r,contextTime:k}})};consumeEvents=t=>{t.forEach(e=>{this.listener.consumer(e)})}};export{a as Position,x as Transport,I as TransportState};
1
+ var l=class{context;clockTimeAt;contextTimeAt;audioClockOffset;_isRunning=!1;constructor(t,e){this.context=t,this.audioClockOffset=e,this.clockTimeAt={start:0,stop:0},this.contextTimeAt={start:0,stop:0}}get isRunning(){return this._isRunning}time(){if(!this.isRunning)return this.clockTimeAt.stop;let t=this.context.currentTime;return this.clockTimeAt.start+(t-this.contextTimeAt.start)}start(t){this.contextTimeAt.start=t,this.clockTimeAt.start=this.clockTimeAt.stop,this._isRunning=!0}stop(t){this._isRunning=!1,this.clockTimeAt.stop=this.clockTimeAt.start+(t-this.contextTimeAt.start),this.contextTimeAt.stop=t}jumpTo(t){this.clockTimeAt.stop=t}clockTimeToContextTime(t){let e=this.isRunning?"start":"stop";return this.audioClockOffset+this.contextTimeAt[e]+t-this.clockTimeAt[e]}};import{isNumber as M}from"@blibliki/utils";var c=15360,b=n=>60/n/c,S=n=>n/60*c,_=(n,t,e)=>{let i=n.findIndex(s=>e(t,s)<0);return i===-1?n.length:i};function C(n,t,e){return t+n*(e-t)}function g(n,t,e){return(n-t)/(e-t)}function E(n,t){if(t<.5||t>.75)throw new Error("Invalid swing amount");let e=n/7680,i=Math.floor(e),s=e-i,o=0;return s<=.5?o=C(g(s,0,.5),0,t):o=C(g(s,.5,1),t,1),Math.floor((i+o)*7680)}var a=class{_ticks;timeSignature;constructor(t,e){this.timeSignature=e,M(t)?this._ticks=t:typeof t=="string"?this._ticks=this.parseStringPosition(t):this._ticks=this.convertObjectToTicks(t)}get ticks(){return this._ticks}get bars(){return Math.floor(this.totalBeats/this.timeSignature[0])+1}get beats(){return this.totalBeats%this.timeSignature[0]+1}get sixteenths(){return Math.floor(this.beatFraction*4)+1}get totalBeats(){return Math.floor(this._ticks/c)}get beatFraction(){return this._ticks/c-this.totalBeats}toString(){return`${this.bars}:${this.beats}:${this.sixteenths}`}toObject(){return{bars:this.bars,beats:this.beats,sixteenths:this.sixteenths}}parseStringPosition(t){let e=t.split(":"),[i,s,o]=e,r=Number(i),k=Number(s),B=Number(o),P={bars:r,beats:k,sixteenths:B};return this.convertObjectToTicks(P)}convertObjectToTicks(t){let e=(t.bars-1)*this.timeSignature[0]+(t.beats-1),i=t.sixteenths/16;return(e+i)*c}};var u=class{_events=[];get events(){return this._events}add(t){let e=_(this._events,t,(i,s)=>i.time-s.time);this._events.splice(e,0,t)}find(t,e){return this._events.filter(i=>i.time>=t&&i.time<e)}lastEventBefore(t){for(let e=this._events.length-1;e>=0;e--){let i=this._events[e];if(i.time<=t)return i}}remove(t,e){this._events=this._events.filter(i=>!(i.time>=t&&i.time<e))}removeAllBefore(t){this._events=this._events.filter(e=>e.time>=t)}clear(){this._events=[]}};var T=class{timeline=new u;scheduleAheadTime;consumedTime;generator;consumer;constructor(t,e,i){this.generator=t,this.consumer=e,this.scheduleAheadTime=i,this.consumedTime=-this.scheduleAheadTime}_schedule(t,e){this.generator(t,e).forEach(i=>{this.timeline.add(i)})}runUntil(t){if(t<=this.consumedTime)throw new Error("Scheduling time is <= current time");this._schedule(this.consumedTime+this.scheduleAheadTime,t+this.scheduleAheadTime);let e=this.timeline.find(this.consumedTime,t);this.consumer(e),this.consumedTime=t,this.timeline.removeAllBefore(this.consumedTime)}jumpTo(t){this.timeline.clear(),this.consumedTime=t-this.scheduleAheadTime}};var h=class{clockTimeAtLastTempoChange=0;ticksAtLastTempoChange=0;_bpm=120;get bpm(){return this._bpm}update(t,e){let i=this.getTicks(t);this.clockTimeAtLastTempoChange=t,this.ticksAtLastTempoChange=i,this._bpm=e}getTicks(t){let i=(t-this.clockTimeAtLastTempoChange)*S(this.bpm);return Math.floor(this.ticksAtLastTempoChange+i)}getClockTime(t){let i=Math.floor(t-this.ticksAtLastTempoChange)*b(this.bpm);return this.clockTimeAtLastTempoChange+i}reset(t,e){this.clockTimeAtLastTempoChange=t,this.ticksAtLastTempoChange=e}};import{AudioContext as N,AudioBuffer as D,AudioBufferSourceNode as L}from"@blibliki/utils/web-audio-api";function O(n){let t=1;for(;n.has(t);)t++;return t}var f=class{_audioBuffer=null;_audioContext=null;_sampleDuration=null;get audioContext(){return this._audioContext??=new N,this._audioContext}get audioBuffer(){return this._audioBuffer??=new D({length:2,sampleRate:this.audioContext.sampleRate}),this._audioBuffer}get sampleDuration(){return this._sampleDuration??=2/this.audioContext.sampleRate,this._sampleDuration}},m=new f,A=new Map,p=new Map;var U=(n,t)=>{let e=t==="interval"?p:A;if(e.has(n)){let i=e.get(n);i!==void 0&&(i(),t==="timeout"&&A.delete(n))}},v=(n,t,e)=>{let i=performance.now(),s=new L(m.audioContext,{buffer:m.audioBuffer});s.onended=()=>{let o=performance.now()-i;o>=t?U(n,e):v(n,t-o,e),s.disconnect(m.audioContext.destination)},s.connect(m.audioContext.destination),s.start(Math.max(0,m.audioContext.currentTime+t/1e3-m.sampleDuration))},y=typeof window>"u",j=n=>{p.delete(n)};var z=(n,t)=>{let e=O(p);return p.set(e,()=>{n(),v(e,t,"interval")}),v(e,t,"interval"),e};var R=y?clearInterval:j;var w=y?setInterval:z;var d=class{timerId=void 0;interval;callback;constructor(t,e){this.callback=t,this.interval=e}start(){this.timerId=w(()=>{this.callback()},this.interval)}stop(){this.timerId!==void 0&&(R(this.timerId),this.timerId=void 0)}get isRunning(){return this.timerId!==void 0}};var I=(i=>(i.playing="playing",i.stopped="stopped",i.paused="paused",i))(I||{}),x=class{_initialized=!1;context;clock;timer;scheduler;_timeSignature=[4,4];tempo=new h;clockTime=0;_swingAmount=.5;listener;_clockCallbacks=[];constructor(t,e){this.context=t,this.listener=e,this.clock=new l(this.context,200/1e3),this.scheduler=new T(this.generateEvents,this.consumeEvents,200/1e3),this.timer=new d(()=>{let o=this.clock.time();o<=this.clockTime||(this.clockTime=o,this.scheduler.runUntil(o),this._clockCallbacks.forEach(r=>{r(this.clockTime)}))},20/1e3),this._initialized=!0}addClockCallback(t){this._clockCallbacks.push(t)}addBarCallback(t){let e=1/0;this.addClockCallback(()=>{let i=this.position;i.bars!==e&&(t(i.bars),e=i.bars)})}get time(){return this.clock.time()}get position(){let t=this.clock.time(),e=this.tempo.getTicks(t);return new a(e,this.timeSignature)}set position(t){this.jumpTo(t.ticks)}getPositionOfEvent(t){return new a(t.ticks,this.timeSignature)}start(){if(!this._initialized)throw new Error("Not initialized");if(this.clock.isRunning)return;let t=this.context.currentTime;this.listener.onStart(t),this.clock.start(t),this.timer.start()}stop(){if(!this._initialized)throw new Error("Not initialized");let t=this.context.currentTime;this.listener.silence(t),this.listener.onStop(t),this.clock.stop(t),this.timer.stop()}reset(){if(!this._initialized)throw new Error("Not initialized");let t=this.context.currentTime;this.listener.silence(t),this.jumpTo(0)}get bpm(){return this.tempo.bpm}set bpm(t){this.tempo.update(this.clockTime,t)}get timeSignature(){return this._timeSignature}set timeSignature(t){this._timeSignature=t}get state(){return this.clock.isRunning?"playing":this.time>0?"paused":"stopped"}get swingAmount(){return this._swingAmount}set swingAmount(t){this._swingAmount=t}jumpTo(t){let e=this.tempo.getClockTime(t);this.tempo.reset(e,t),this.clock.jumpTo(e),this.scheduler.jumpTo(e),this.clockTime=e,this.listener.onJump(t),this._clockCallbacks.forEach(i=>{i(this.clockTime)})}generateEvents=(t,e)=>{let i=this.tempo.getTicks(t),s=this.tempo.getTicks(e);return this.listener.generator(i,s).map(o=>({...o,ticks:E(o.ticks,this.swingAmount)})).map(o=>{let r=this.tempo.getClockTime(o.ticks),k=this.clock.clockTimeToContextTime(r);return{...o,time:r,contextTime:k}})};consumeEvents=t=>{t.forEach(e=>{this.listener.consumer(e)})}};export{a as Position,x as Transport,I as TransportState};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/Clock.ts","../src/Position.ts","../src/utils.ts","../src/Timeline.ts","../src/Scheduler.ts","../src/Tempo.ts","../src/audio-context-timers.ts","../src/Timer.ts","../src/Transport.ts"],"sourcesContent":["import { Context } from \"@blibliki/utils\";\nimport { ClockTime, ContextTime, Seconds } from \"./types\";\n\nexport class Clock {\n private context: Readonly<Context>;\n private clockTimeAt: { start: ClockTime; stop: ClockTime };\n private contextTimeAt: { start: ContextTime; stop: ContextTime };\n private audioClockOffset: number;\n\n private _isRunning = false;\n\n constructor(context: Readonly<Context>, audioClockOffset: Seconds) {\n this.context = context;\n this.audioClockOffset = audioClockOffset;\n\n this.clockTimeAt = { start: 0, stop: 0 };\n this.contextTimeAt = { start: 0, stop: 0 };\n }\n\n get isRunning() {\n return this._isRunning;\n }\n\n time() {\n if (!this.isRunning) return this.clockTimeAt.stop;\n\n const now = this.context.currentTime;\n return this.clockTimeAt.start + (now - this.contextTimeAt.start);\n }\n\n start(actionAt: ContextTime) {\n this.contextTimeAt.start = actionAt;\n this.clockTimeAt.start = this.clockTimeAt.stop;\n this._isRunning = true;\n }\n\n stop(actionAt: ContextTime) {\n this._isRunning = false;\n this.clockTimeAt.stop =\n this.clockTimeAt.start + (actionAt - this.contextTimeAt.start);\n this.contextTimeAt.stop = actionAt;\n }\n\n jumpTo(time: ClockTime) {\n this.clockTimeAt.stop = time;\n }\n\n clockTimeToContextTime(clockTime: ClockTime): ContextTime {\n const type = this.isRunning ? \"start\" : \"stop\";\n\n return (\n this.audioClockOffset +\n this.contextTimeAt[type] +\n clockTime -\n this.clockTimeAt[type]\n );\n }\n}\n","import { isNumber } from \"@blibliki/utils\";\nimport { Ticks, TimeSignature, TPosition, TStringPosition } from \"./types\";\nimport { TPB } from \"./utils\";\n\nexport const sixteenthPerBeat = (timeSignature: Readonly<TimeSignature>) => {\n const denominator = timeSignature[1];\n return 16 / denominator;\n};\n\n/**\n * Represents a musical position that can be expressed in multiple formats:\n * - Ticks (raw MIDI time units)\n * - Bars:Beats:Sixteenths (string format like \"1:2:8\")\n * - Object format with bars, beats, and sixteenths properties\n */\nexport class Position {\n private readonly _ticks: Ticks;\n private timeSignature: Readonly<TimeSignature>;\n\n /**\n * Creates a new Position instance\n * @param value - Position value in ticks, string format (\"bars:beats:sixteenths\"), or object format\n * @param timeSignature - Time signature as [numerator, denominator] (e.g., [4, 4] for 4/4)\n */\n constructor(\n value: Ticks | TPosition | TStringPosition,\n timeSignature: TimeSignature,\n ) {\n this.timeSignature = timeSignature;\n\n if (isNumber(value)) {\n this._ticks = value;\n } else if (typeof value === \"string\") {\n this._ticks = this.parseStringPosition(value);\n } else {\n this._ticks = this.convertObjectToTicks(value);\n }\n }\n\n get ticks() {\n return this._ticks;\n }\n\n get bars() {\n return Math.floor(this.totalBeats / this.timeSignature[0]) + 1;\n }\n\n get beats() {\n return (this.totalBeats % this.timeSignature[0]) + 1;\n }\n\n get sixteenths() {\n return Math.floor(this.beatFraction * 4) + 1;\n }\n\n get totalBeats() {\n return Math.floor(this._ticks / TPB);\n }\n\n get beatFraction() {\n return this._ticks / TPB - this.totalBeats;\n }\n\n toString(): string {\n return `${this.bars}:${this.beats}:${this.sixteenths}`;\n }\n\n toObject(): TPosition {\n return {\n bars: this.bars,\n beats: this.beats,\n sixteenths: this.sixteenths,\n };\n }\n\n private parseStringPosition(positionString: TStringPosition): Ticks {\n const parts = positionString.split(\":\");\n\n const [barsStr, beatsStr, sixteenthsStr] = parts;\n const bars = Number(barsStr);\n const beats = Number(beatsStr);\n const sixteenths = Number(sixteenthsStr);\n\n const position: TPosition = { bars, beats, sixteenths };\n\n return this.convertObjectToTicks(position);\n }\n\n private convertObjectToTicks(position: TPosition): Ticks {\n const totalBeats =\n (position.bars - 1) * this.timeSignature[0] + (position.beats - 1);\n const beatFraction = position.sixteenths / 16;\n return (totalBeats + beatFraction) * TPB;\n }\n}\n","import { BPM, NormalRange, Ticks } from \"./types\";\n\n// Ticks per beat\nexport const TPB = 15360;\n\nexport const secondsPerTick = (tempo: BPM) => {\n return 60 / tempo / TPB;\n};\n\nexport const ticksPerSecond = (tempo: BPM) => {\n return (tempo / 60) * TPB;\n};\n\nexport const insertionIndexBy = <T>(\n arr: T[],\n n: T,\n comparatorFn: (a: T, b: T) => number,\n) => {\n const index = arr.findIndex((el) => comparatorFn(n, el) < 0);\n return index === -1 ? arr.length : index;\n};\n\nexport function lerp(t: number, a: number, b: number): number {\n return a + t * (b - a);\n}\n\nexport function unlerp(t: number, a: number, b: number): number {\n return (t - a) / (b - a);\n}\n\n/**\n * Compute swing.\n *\n * This function is an attempt at mirroring the behaviour in the Reason DAW,\n * where events before the 50% eighth mark are \"expanded\" and events past the\n * mark are \"compressed\". That is, this function transforms the underlying\n * transport time grid:\n *\n * No swing |<--50%-->|<--50%-->|<--50%-->| ...\n * 16ths 0 1 2 3 ...\n * 8ths 0 | 1 | ...\n * Notes X======X X===X | |\n * | | | |\n * | \\ | \\\n * 60% swing |<---60%--->|<-40%->|<---60%--->| ...\n * 16ths 0 1 2 3 ...\n * 8ths 0 | 1 | ...\n * Notes X=======X X==X | |\n *\n * If it is challenging to explain this mechanism to end users, you could\n * use the approach taken in the Logic DAW, which has 6 \"swing modes\":\n *\n * 16A: 50%\n * 16B: 54%\n * 16C: 58%\n * 16D: 62%\n * 16E: 66%\n * 16F: 71%\n *\n * For details, see\n * https://www.attackmagazine.com/technique/passing-notes/daw-drum-machine-swing/2/\n *\n * @param time Time of event, in transport ticks (1/4 = 15360 pulses)\n * @param amount Swing amount in range [0.5, 0.75].\n * @returns New time of the event, with swing applied.\n */\nexport function swing(time: Ticks, amount: NormalRange): Ticks {\n if (amount < 0.5 || amount > 0.75) {\n throw new Error(\"Invalid swing amount\");\n }\n\n /*\n Note lengths in ticks:\n\n 1/4 = 15360\n 1/8 = 7680\n 1/16 = 3840\n */\n\n const t8 = time / 7680; // Input time in 8ths\n const t8i = Math.floor(t8); // 8th in which the time sits\n const t = t8 - t8i; // Percentage position in the 8th in which the time sits\n\n /* \n ### We could make this a bit more efficient; we can simplify the \n <=0.5 case to\n\n (t / 0.5) * amount\n\n But the >0.5 case is more difficult:\n\n amount + ((t - 0.5) / 0.5) * (1 - amount)\n\n Using the lerp/unlerp functions here makes it clear what's going on,\n though, so we'll keep them for now.\n */\n let tn = 0;\n if (t <= 0.5) {\n tn = lerp(unlerp(t, 0.0, 0.5), 0.0, amount);\n } else {\n tn = lerp(unlerp(t, 0.5, 1.0), amount, 1.0);\n }\n\n // Map transformed time back into ticks\n return Math.floor((t8i + tn) * 7680);\n}\n","import { ClockTime } from \"./types\";\nimport { insertionIndexBy } from \"./utils\";\n\nexport type TimelineEvent = {\n time: ClockTime;\n};\n\nexport class Timeline<T extends TimelineEvent = TimelineEvent> {\n private _events: Readonly<T>[] = [];\n\n get events(): readonly Readonly<T>[] {\n return this._events;\n }\n\n /**\n * Add a new event to the timeline. It will be inserted according to its\n * `time` property (which must be present; otherwise an exception is thrown).\n *\n * If the time of the new event is exactly equal to the time of an existing\n * event in the timeline, the existing event is considered to occur before\n * the new event.\n */\n add(event: Readonly<T>) {\n const idx = insertionIndexBy(this._events, event, (a, b) => {\n return a.time - b.time;\n });\n this._events.splice(idx, 0, event);\n }\n\n find(start: ClockTime, end: ClockTime): readonly Readonly<T>[] {\n return this._events.filter((event) => {\n return event.time >= start && event.time < end;\n });\n }\n\n /** Return the last event that occurred at or before the specified time. */\n lastEventBefore(time: ClockTime) {\n for (let i = this._events.length - 1; i >= 0; i--) {\n const event = this._events[i];\n if (event.time <= time) {\n return event;\n }\n }\n return undefined;\n }\n\n remove(start: ClockTime, end: ClockTime) {\n this._events = this._events.filter((event) => {\n return !(event.time >= start && event.time < end);\n });\n }\n\n removeAllBefore(time: ClockTime) {\n this._events = this._events.filter((event) => event.time >= time);\n }\n\n clear() {\n this._events = [];\n }\n}\n","import { Timeline, TimelineEvent } from \"./Timeline\";\nimport { ClockTime, Seconds } from \"./types\";\n\n/**\n * Return all events that occur in the interval [start, end).\n */\nexport type EventGenerator<T extends TimelineEvent> = (\n start: ClockTime,\n end: ClockTime,\n) => readonly Readonly<T>[];\n\n/** Consume the speified event. */\nexport type EventConsumer<T extends TimelineEvent> = (\n events: readonly Readonly<T>[],\n) => void;\n\n/**\n * The scheduler is responsible for tracking the scheduling/playback window.\n *\n * Scheduling of events always precedes playback of events. The left hand\n * edge of the scheduling window is the point in time up to where events have\n * been consumed so far. The right hand edge of the window is the point in time\n * up to where events have been scheduled/prepared. The width of the window is\n * always the same (specified when the Scheduler instance is created).\n */\nexport class Scheduler<T extends TimelineEvent = TimelineEvent> {\n private timeline = new Timeline<T>();\n\n private scheduleAheadTime: Seconds;\n private consumedTime: ClockTime;\n\n private generator: EventGenerator<T>;\n private consumer: EventConsumer<T>;\n\n constructor(\n generator: EventGenerator<T>,\n consumer: EventConsumer<T>,\n scheduleAheadTime: Seconds,\n ) {\n this.generator = generator;\n this.consumer = consumer;\n this.scheduleAheadTime = scheduleAheadTime;\n this.consumedTime = -this.scheduleAheadTime;\n }\n\n private _schedule(start: Seconds, end: Seconds) {\n this.generator(start, end).forEach((e) => {\n this.timeline.add(e);\n });\n }\n\n /**\n * Consume events up to the specified time.\n *\n * This will also generate new events up until `time + scheduleAheadTime`.\n */\n runUntil(time: ClockTime) {\n if (time <= this.consumedTime) {\n throw new Error(\"Scheduling time is <= current time\");\n }\n\n /* \n Generate events that occur between the previous and new right-hand edges \n of the scheduling window. I.e., move scheduling time forwards by the \n specified amount.\n */\n this._schedule(\n this.consumedTime + this.scheduleAheadTime,\n time + this.scheduleAheadTime,\n );\n\n /*\n Consume events that occur between the previous and new left-hand edges\n of the scheduling window. I.e., move playback time forwards by the\n specified amount.\n */\n const events = this.timeline.find(this.consumedTime, time);\n this.consumer(events);\n this.consumedTime = time;\n\n /*\n Remove all events that were consumed from the timeline.\n */\n this.timeline.removeAllBefore(this.consumedTime);\n }\n\n /**\n * Jump to the specified destination time.\n */\n jumpTo(time: ClockTime) {\n this.timeline.clear();\n this.consumedTime = time - this.scheduleAheadTime;\n }\n}\n","import { BPM, ClockTime, Ticks } from \"./types\";\nimport { secondsPerTick, ticksPerSecond } from \"./utils\";\n\nexport class Tempo {\n private clockTimeAtLastTempoChange = 0;\n private ticksAtLastTempoChange = 0;\n private _bpm = 120;\n\n get bpm() {\n return this._bpm;\n }\n\n update(clockTime: ClockTime, tempo: BPM) {\n const ticks = this.getTicks(clockTime);\n this.clockTimeAtLastTempoChange = clockTime;\n this.ticksAtLastTempoChange = ticks;\n this._bpm = tempo;\n }\n\n getTicks(clockTime: ClockTime): Ticks {\n const clockDelta = clockTime - this.clockTimeAtLastTempoChange;\n const tickDelta = clockDelta * ticksPerSecond(this.bpm);\n return Math.floor(this.ticksAtLastTempoChange + tickDelta);\n }\n\n getClockTime(ticks: Ticks): ClockTime {\n const tickDelta = Math.floor(ticks - this.ticksAtLastTempoChange);\n const clockDelta = tickDelta * secondsPerTick(this.bpm);\n return this.clockTimeAtLastTempoChange + clockDelta;\n }\n\n reset(clockTime: ClockTime, ticks: Ticks) {\n this.clockTimeAtLastTempoChange = clockTime;\n this.ticksAtLastTempoChange = ticks;\n }\n}\n","function generateUniqueNumber(map: Map<number, () => void>): number {\n let nextId = 1;\n\n // Keep incrementing until we find an unused ID\n while (map.has(nextId)) {\n nextId++;\n }\n\n return nextId;\n}\n\nclass WebAudioWrapper {\n private _audioBuffer: AudioBuffer | null = null;\n private _audioContext: AudioContext | null = null;\n private _sampleDuration: number | null = null;\n\n get audioContext(): AudioContext {\n this._audioContext ??= new AudioContext();\n return this._audioContext;\n }\n\n get audioBuffer(): AudioBuffer {\n this._audioBuffer ??= new AudioBuffer({\n length: 2,\n sampleRate: this.audioContext.sampleRate,\n });\n return this._audioBuffer;\n }\n\n get sampleDuration(): number {\n this._sampleDuration ??= 2 / this.audioContext.sampleRate;\n return this._sampleDuration;\n }\n}\n\nconst webAudioWrapper = new WebAudioWrapper();\n\nconst SCHEDULED_TIMEOUT_FUNCTIONS = new Map<number, () => void>();\nconst SCHEDULED_INTERVAL_FUNCTIONS = new Map<number, () => void>();\n\nenum TimerType {\n interval = \"interval\",\n timeout = \"timeout\",\n}\n\nconst callIntervalFunction = (id: number, type: TimerType) => {\n const functions =\n type === TimerType.interval\n ? SCHEDULED_INTERVAL_FUNCTIONS\n : SCHEDULED_TIMEOUT_FUNCTIONS;\n\n if (functions.has(id)) {\n const func = functions.get(id);\n\n if (func !== undefined) {\n func();\n\n if (type === TimerType.timeout) {\n SCHEDULED_TIMEOUT_FUNCTIONS.delete(id);\n }\n }\n }\n};\n\nconst scheduleFunction = (id: number, delay: number, type: TimerType) => {\n const now = performance.now();\n\n const audioBufferSourceNode = new AudioBufferSourceNode(\n webAudioWrapper.audioContext,\n { buffer: webAudioWrapper.audioBuffer },\n );\n\n audioBufferSourceNode.onended = () => {\n const elapsedTime = performance.now() - now;\n\n if (elapsedTime >= delay) {\n callIntervalFunction(id, type);\n } else {\n scheduleFunction(id, delay - elapsedTime, type);\n }\n\n audioBufferSourceNode.disconnect(webAudioWrapper.audioContext.destination);\n };\n audioBufferSourceNode.connect(webAudioWrapper.audioContext.destination);\n audioBufferSourceNode.start(\n Math.max(\n 0,\n webAudioWrapper.audioContext.currentTime +\n delay / 1000 -\n webAudioWrapper.sampleDuration,\n ),\n );\n};\n\nconst isNode = typeof window === \"undefined\";\n\n// exported methods\n\nconst acClearInterval = (id: number) => {\n SCHEDULED_INTERVAL_FUNCTIONS.delete(id);\n};\n\nconst acClearTimeout = (id: number) => {\n SCHEDULED_TIMEOUT_FUNCTIONS.delete(id);\n};\n\nconst acSetInterval = (func: () => void, delay: number) => {\n const id = generateUniqueNumber(SCHEDULED_INTERVAL_FUNCTIONS);\n\n SCHEDULED_INTERVAL_FUNCTIONS.set(id, () => {\n func();\n\n scheduleFunction(id, delay, TimerType.interval);\n });\n\n scheduleFunction(id, delay, TimerType.interval);\n\n return id;\n};\n\nconst acSetTimeout = (func: () => void, delay: number) => {\n const id = generateUniqueNumber(SCHEDULED_TIMEOUT_FUNCTIONS);\n\n SCHEDULED_TIMEOUT_FUNCTIONS.set(id, func);\n\n scheduleFunction(id, delay, TimerType.timeout);\n\n return id;\n};\n\nconst exportedClearInterval = isNode ? clearInterval : acClearInterval;\nconst exportedClearTimeout = isNode ? clearTimeout : acClearTimeout;\nconst exportedSetInterval = isNode ? setInterval : acSetInterval;\nconst exportedSetTimeout = isNode ? setTimeout : acSetTimeout;\n\nexport {\n exportedClearInterval as clearInterval,\n exportedClearTimeout as clearTimeout,\n exportedSetInterval as setInterval,\n exportedSetTimeout as setTimeout,\n};\n","import { Context } from \"@blibliki/utils\";\nimport { clearInterval, setInterval } from \"./audio-context-timers\";\nimport { ContextTime } from \"./types\";\n\nexport const scheduleAtContextTime = ({\n scheduleAt,\n context,\n callback,\n}: {\n scheduleAt: ContextTime;\n context: Context;\n callback: () => void;\n}) => {\n const now = context.currentTime;\n if (now > scheduleAt) throw Error(\"Could not schedule event at past\");\n\n const ms = (scheduleAt - now) * 1000;\n\n setTimeout(callback, ms);\n};\n\nexport class Timer {\n private timerId: number | undefined = undefined;\n\n private interval: number;\n private callback: () => void;\n\n constructor(callback: () => void, intervalMs: number) {\n this.callback = callback;\n this.interval = intervalMs;\n }\n\n start() {\n this.timerId = setInterval(() => {\n this.callback();\n }, this.interval);\n }\n\n stop() {\n if (this.timerId === undefined) return;\n\n clearInterval(this.timerId);\n this.timerId = undefined;\n }\n\n get isRunning() {\n return this.timerId !== undefined;\n }\n}\n","import { Context } from \"@blibliki/utils\";\nimport { Clock } from \"./Clock\";\nimport { Position } from \"./Position\";\nimport { Scheduler } from \"./Scheduler\";\nimport { Tempo } from \"./Tempo\";\nimport { TimelineEvent } from \"./Timeline\";\nimport { Timer } from \"./Timer\";\nimport {\n BPM,\n ClockTime,\n ContextTime,\n NormalRange,\n Ticks,\n TimeSignature,\n} from \"./types\";\nimport { swing } from \"./utils\";\n\nexport interface TransportEvent extends TimelineEvent {\n ticks: Ticks;\n contextTime: ContextTime;\n}\n\n/**\n * Given a (future) transport time window (in ticks), return all events that should\n * occur within the window. This function is responsible for setting the ticks\n * value for the events returned.\n *\n * IMPORTANT: Subsequent calls to this function may have overlapping time windows.\n * Be careful to not return the same event more than once.\n */\nexport type TransportEventGenerator<T extends TransportEvent> = (\n start: Ticks,\n end: Ticks,\n) => readonly Readonly<T>[];\n\n/**\n * Schedule the specified event with the audio system. The transport class is\n * responsible for setting the `contextTime` of the events.\n */\nexport type TransportEventConsumer<T extends TransportEvent> = (\n event: Readonly<T>,\n) => void;\n\nexport type TransportListener<T extends TransportEvent> = {\n generator: TransportEventGenerator<T>;\n consumer: TransportEventConsumer<T>;\n onJump: (ticks: Ticks) => void;\n onStart: (contextTime: ContextTime) => void;\n onStop: (contextTime: ContextTime) => void;\n silence: (contextTime: ContextTime) => void;\n};\n\n/**\n * Transport callback that gets invoked at (roughly) sixteenth note intervals.\n *\n * Returns the current clock time in seconds.\n *\n * IMPORTANT! Do not rely on this callback for audio precision mechanisms!\n * It is only accurate up to the precision of the event sheduler rate, and may\n * \"jump\" if the scheduling is struggling to keep up.\n */\nexport type TransportClockCallback = (time: ClockTime) => void;\n\nexport enum TransportState {\n playing = \"playing\",\n stopped = \"stopped\",\n paused = \"paused\",\n}\n\n/**\n * This class converts (music) transport time into audio clock time.\n */\nexport class Transport<T extends TransportEvent> {\n private _initialized = false;\n\n private context: Readonly<Context>;\n private clock: Readonly<Clock>;\n private timer: Readonly<Timer>;\n private scheduler: Readonly<Scheduler<T>>;\n\n private _timeSignature: TimeSignature = [4, 4];\n\n private tempo: Readonly<Tempo> = new Tempo();\n private clockTime = 0;\n private _swingAmount: NormalRange = 0.5;\n\n private listener: Readonly<TransportListener<T>>;\n\n private _clockCallbacks: TransportClockCallback[] = [];\n\n constructor(\n context: Readonly<Context>,\n listener: Readonly<TransportListener<T>>,\n ) {\n // ### Make these adapt to performance of app? Or let user set them?\n const SCHEDULE_INTERVAL_MS = 20;\n const SCHEDULE_WINDOW_SIZE_MS = 200;\n\n this.context = context;\n this.listener = listener;\n this.clock = new Clock(this.context, SCHEDULE_WINDOW_SIZE_MS / 1000);\n\n this.scheduler = new Scheduler<T>(\n this.generateEvents,\n this.consumeEvents,\n SCHEDULE_WINDOW_SIZE_MS / 1000,\n );\n\n this.timer = new Timer(() => {\n const time = this.clock.time();\n if (time <= this.clockTime) return;\n\n this.clockTime = time;\n this.scheduler.runUntil(time);\n this._clockCallbacks.forEach((callback) => {\n callback(this.clockTime);\n });\n }, SCHEDULE_INTERVAL_MS / 1000);\n\n this._initialized = true;\n }\n\n addClockCallback(callback: TransportClockCallback) {\n this._clockCallbacks.push(callback);\n }\n\n // ??? Make this more efficient (no need to compute full position, for example)\n addBarCallback(callback: (bar: number) => void) {\n let currentBar = Infinity;\n this.addClockCallback(() => {\n const pos = this.position;\n if (pos.bars !== currentBar) {\n callback(pos.bars);\n currentBar = pos.bars;\n }\n });\n }\n\n get time() {\n return this.clock.time();\n }\n\n /**\n * Return the (approximate) current Transport time, in ticks.\n */\n get position(): Readonly<Position> {\n const clockTime = this.clock.time();\n const ticks = this.tempo.getTicks(clockTime);\n return new Position(ticks, this.timeSignature);\n }\n\n /**\n * Set the current Transport time.\n */\n set position(position: Readonly<Position>) {\n this.jumpTo(position.ticks);\n }\n\n getPositionOfEvent(event: Readonly<TransportEvent>): Readonly<Position> {\n return new Position(event.ticks, this.timeSignature);\n }\n\n /**\n * Start the Transport.\n */\n start() {\n if (!this._initialized) throw new Error(\"Not initialized\");\n if (this.clock.isRunning) return;\n\n const actionAt = this.context.currentTime;\n\n this.listener.onStart(actionAt);\n this.clock.start(actionAt);\n this.timer.start();\n }\n\n /**\n * Stop the Transport.\n */\n stop() {\n if (!this._initialized) throw new Error(\"Not initialized\");\n\n const actionAt = this.context.currentTime;\n\n this.listener.silence(actionAt);\n this.listener.onStop(actionAt);\n this.clock.stop(actionAt);\n this.timer.stop();\n }\n\n /**\n * Reset the Transport to zero.\n */\n reset() {\n if (!this._initialized) throw new Error(\"Not initialized\");\n\n const actionAt = this.context.currentTime;\n this.listener.silence(actionAt);\n this.jumpTo(0);\n }\n\n get bpm(): BPM {\n return this.tempo.bpm;\n }\n\n set bpm(bpm: BPM) {\n this.tempo.update(this.clockTime, bpm);\n }\n\n get timeSignature() {\n return this._timeSignature;\n }\n\n set timeSignature(value: TimeSignature) {\n this._timeSignature = value;\n }\n\n get state() {\n if (this.clock.isRunning) return TransportState.playing;\n else if (this.time > 0) return TransportState.paused;\n else return TransportState.stopped;\n }\n\n get swingAmount() {\n return this._swingAmount;\n }\n\n set swingAmount(amount: NormalRange) {\n this._swingAmount = amount;\n }\n\n private jumpTo(ticks: Ticks) {\n const clockTime = this.tempo.getClockTime(ticks);\n this.tempo.reset(clockTime, ticks);\n this.clock.jumpTo(clockTime);\n this.scheduler.jumpTo(clockTime);\n this.clockTime = clockTime;\n this.listener.onJump(ticks);\n this._clockCallbacks.forEach((callback) => {\n callback(this.clockTime);\n });\n }\n\n private generateEvents = (\n start: ClockTime,\n end: ClockTime,\n ): readonly Readonly<T>[] => {\n // Get transport-time events and return them as clock-time events\n const transportStart = this.tempo.getTicks(start);\n const transportEnd = this.tempo.getTicks(end);\n return this.listener\n .generator(transportStart, transportEnd)\n .map((event) => {\n // Apply swing\n return {\n ...event,\n ticks: swing(event.ticks, this.swingAmount),\n };\n })\n .map((event) => {\n const time = this.tempo.getClockTime(event.ticks);\n const contextTime = this.clock.clockTimeToContextTime(time);\n return {\n ...event,\n time,\n contextTime,\n };\n });\n };\n\n private consumeEvents = (events: readonly Readonly<T>[]) => {\n events.forEach((event) => {\n this.listener.consumer(event);\n });\n };\n}\n"],"mappings":"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","names":["Clock","context","audioClockOffset","now","actionAt","time","clockTime","type","isNumber","TPB","secondsPerTick","tempo","ticksPerSecond","insertionIndexBy","arr","n","comparatorFn","index","el","lerp","t","a","b","unlerp","swing","time","amount","t8","t8i","tn","Position","value","timeSignature","isNumber","TPB","positionString","parts","barsStr","beatsStr","sixteenthsStr","bars","beats","sixteenths","position","totalBeats","beatFraction","Timeline","event","idx","insertionIndexBy","a","b","start","end","time","i","Scheduler","Timeline","generator","consumer","scheduleAheadTime","start","end","e","time","events","Tempo","clockTime","tempo","ticks","tickDelta","ticksPerSecond","clockDelta","secondsPerTick","generateUniqueNumber","map","nextId","WebAudioWrapper","webAudioWrapper","SCHEDULED_TIMEOUT_FUNCTIONS","SCHEDULED_INTERVAL_FUNCTIONS","callIntervalFunction","id","type","functions","SCHEDULED_INTERVAL_FUNCTIONS","SCHEDULED_TIMEOUT_FUNCTIONS","func","scheduleFunction","delay","now","audioBufferSourceNode","webAudioWrapper","elapsedTime","isNode","acClearInterval","acSetInterval","func","delay","id","generateUniqueNumber","SCHEDULED_INTERVAL_FUNCTIONS","scheduleFunction","exportedClearInterval","isNode","acClearInterval","exportedSetInterval","isNode","acSetInterval","Timer","callback","intervalMs","exportedSetInterval","exportedClearInterval","TransportState","Transport","Tempo","context","listener","Clock","Scheduler","Timer","time","callback","currentBar","pos","clockTime","ticks","Position","position","event","actionAt","bpm","value","amount","start","end","transportStart","transportEnd","swing","contextTime","events"]}
1
+ {"version":3,"sources":["../src/Clock.ts","../src/Position.ts","../src/utils.ts","../src/Timeline.ts","../src/Scheduler.ts","../src/Tempo.ts","../src/audio-context-timers.ts","../src/Timer.ts","../src/Transport.ts"],"sourcesContent":["import { Context } from \"@blibliki/utils\";\nimport { ClockTime, ContextTime, Seconds } from \"./types\";\n\nexport class Clock {\n private context: Readonly<Context>;\n private clockTimeAt: { start: ClockTime; stop: ClockTime };\n private contextTimeAt: { start: ContextTime; stop: ContextTime };\n private audioClockOffset: number;\n\n private _isRunning = false;\n\n constructor(context: Readonly<Context>, audioClockOffset: Seconds) {\n this.context = context;\n this.audioClockOffset = audioClockOffset;\n\n this.clockTimeAt = { start: 0, stop: 0 };\n this.contextTimeAt = { start: 0, stop: 0 };\n }\n\n get isRunning() {\n return this._isRunning;\n }\n\n time() {\n if (!this.isRunning) return this.clockTimeAt.stop;\n\n const now = this.context.currentTime;\n return this.clockTimeAt.start + (now - this.contextTimeAt.start);\n }\n\n start(actionAt: ContextTime) {\n this.contextTimeAt.start = actionAt;\n this.clockTimeAt.start = this.clockTimeAt.stop;\n this._isRunning = true;\n }\n\n stop(actionAt: ContextTime) {\n this._isRunning = false;\n this.clockTimeAt.stop =\n this.clockTimeAt.start + (actionAt - this.contextTimeAt.start);\n this.contextTimeAt.stop = actionAt;\n }\n\n jumpTo(time: ClockTime) {\n this.clockTimeAt.stop = time;\n }\n\n clockTimeToContextTime(clockTime: ClockTime): ContextTime {\n const type = this.isRunning ? \"start\" : \"stop\";\n\n return (\n this.audioClockOffset +\n this.contextTimeAt[type] +\n clockTime -\n this.clockTimeAt[type]\n );\n }\n}\n","import { isNumber } from \"@blibliki/utils\";\nimport { Ticks, TimeSignature, TPosition, TStringPosition } from \"./types\";\nimport { TPB } from \"./utils\";\n\nexport const sixteenthPerBeat = (timeSignature: Readonly<TimeSignature>) => {\n const denominator = timeSignature[1];\n return 16 / denominator;\n};\n\n/**\n * Represents a musical position that can be expressed in multiple formats:\n * - Ticks (raw MIDI time units)\n * - Bars:Beats:Sixteenths (string format like \"1:2:8\")\n * - Object format with bars, beats, and sixteenths properties\n */\nexport class Position {\n private readonly _ticks: Ticks;\n private timeSignature: Readonly<TimeSignature>;\n\n /**\n * Creates a new Position instance\n * @param value - Position value in ticks, string format (\"bars:beats:sixteenths\"), or object format\n * @param timeSignature - Time signature as [numerator, denominator] (e.g., [4, 4] for 4/4)\n */\n constructor(\n value: Ticks | TPosition | TStringPosition,\n timeSignature: TimeSignature,\n ) {\n this.timeSignature = timeSignature;\n\n if (isNumber(value)) {\n this._ticks = value;\n } else if (typeof value === \"string\") {\n this._ticks = this.parseStringPosition(value);\n } else {\n this._ticks = this.convertObjectToTicks(value);\n }\n }\n\n get ticks() {\n return this._ticks;\n }\n\n get bars() {\n return Math.floor(this.totalBeats / this.timeSignature[0]) + 1;\n }\n\n get beats() {\n return (this.totalBeats % this.timeSignature[0]) + 1;\n }\n\n get sixteenths() {\n return Math.floor(this.beatFraction * 4) + 1;\n }\n\n get totalBeats() {\n return Math.floor(this._ticks / TPB);\n }\n\n get beatFraction() {\n return this._ticks / TPB - this.totalBeats;\n }\n\n toString(): string {\n return `${this.bars}:${this.beats}:${this.sixteenths}`;\n }\n\n toObject(): TPosition {\n return {\n bars: this.bars,\n beats: this.beats,\n sixteenths: this.sixteenths,\n };\n }\n\n private parseStringPosition(positionString: TStringPosition): Ticks {\n const parts = positionString.split(\":\");\n\n const [barsStr, beatsStr, sixteenthsStr] = parts;\n const bars = Number(barsStr);\n const beats = Number(beatsStr);\n const sixteenths = Number(sixteenthsStr);\n\n const position: TPosition = { bars, beats, sixteenths };\n\n return this.convertObjectToTicks(position);\n }\n\n private convertObjectToTicks(position: TPosition): Ticks {\n const totalBeats =\n (position.bars - 1) * this.timeSignature[0] + (position.beats - 1);\n const beatFraction = position.sixteenths / 16;\n return (totalBeats + beatFraction) * TPB;\n }\n}\n","import { BPM, NormalRange, Ticks } from \"./types\";\n\n// Ticks per beat\nexport const TPB = 15360;\n\nexport const secondsPerTick = (tempo: BPM) => {\n return 60 / tempo / TPB;\n};\n\nexport const ticksPerSecond = (tempo: BPM) => {\n return (tempo / 60) * TPB;\n};\n\nexport const insertionIndexBy = <T>(\n arr: T[],\n n: T,\n comparatorFn: (a: T, b: T) => number,\n) => {\n const index = arr.findIndex((el) => comparatorFn(n, el) < 0);\n return index === -1 ? arr.length : index;\n};\n\nexport function lerp(t: number, a: number, b: number): number {\n return a + t * (b - a);\n}\n\nexport function unlerp(t: number, a: number, b: number): number {\n return (t - a) / (b - a);\n}\n\n/**\n * Compute swing.\n *\n * This function is an attempt at mirroring the behaviour in the Reason DAW,\n * where events before the 50% eighth mark are \"expanded\" and events past the\n * mark are \"compressed\". That is, this function transforms the underlying\n * transport time grid:\n *\n * No swing |<--50%-->|<--50%-->|<--50%-->| ...\n * 16ths 0 1 2 3 ...\n * 8ths 0 | 1 | ...\n * Notes X======X X===X | |\n * | | | |\n * | \\ | \\\n * 60% swing |<---60%--->|<-40%->|<---60%--->| ...\n * 16ths 0 1 2 3 ...\n * 8ths 0 | 1 | ...\n * Notes X=======X X==X | |\n *\n * If it is challenging to explain this mechanism to end users, you could\n * use the approach taken in the Logic DAW, which has 6 \"swing modes\":\n *\n * 16A: 50%\n * 16B: 54%\n * 16C: 58%\n * 16D: 62%\n * 16E: 66%\n * 16F: 71%\n *\n * For details, see\n * https://www.attackmagazine.com/technique/passing-notes/daw-drum-machine-swing/2/\n *\n * @param time Time of event, in transport ticks (1/4 = 15360 pulses)\n * @param amount Swing amount in range [0.5, 0.75].\n * @returns New time of the event, with swing applied.\n */\nexport function swing(time: Ticks, amount: NormalRange): Ticks {\n if (amount < 0.5 || amount > 0.75) {\n throw new Error(\"Invalid swing amount\");\n }\n\n /*\n Note lengths in ticks:\n\n 1/4 = 15360\n 1/8 = 7680\n 1/16 = 3840\n */\n\n const t8 = time / 7680; // Input time in 8ths\n const t8i = Math.floor(t8); // 8th in which the time sits\n const t = t8 - t8i; // Percentage position in the 8th in which the time sits\n\n /* \n ### We could make this a bit more efficient; we can simplify the \n <=0.5 case to\n\n (t / 0.5) * amount\n\n But the >0.5 case is more difficult:\n\n amount + ((t - 0.5) / 0.5) * (1 - amount)\n\n Using the lerp/unlerp functions here makes it clear what's going on,\n though, so we'll keep them for now.\n */\n let tn = 0;\n if (t <= 0.5) {\n tn = lerp(unlerp(t, 0.0, 0.5), 0.0, amount);\n } else {\n tn = lerp(unlerp(t, 0.5, 1.0), amount, 1.0);\n }\n\n // Map transformed time back into ticks\n return Math.floor((t8i + tn) * 7680);\n}\n","import { ClockTime } from \"./types\";\nimport { insertionIndexBy } from \"./utils\";\n\nexport type TimelineEvent = {\n time: ClockTime;\n};\n\nexport class Timeline<T extends TimelineEvent = TimelineEvent> {\n private _events: Readonly<T>[] = [];\n\n get events(): readonly Readonly<T>[] {\n return this._events;\n }\n\n /**\n * Add a new event to the timeline. It will be inserted according to its\n * `time` property (which must be present; otherwise an exception is thrown).\n *\n * If the time of the new event is exactly equal to the time of an existing\n * event in the timeline, the existing event is considered to occur before\n * the new event.\n */\n add(event: Readonly<T>) {\n const idx = insertionIndexBy(this._events, event, (a, b) => {\n return a.time - b.time;\n });\n this._events.splice(idx, 0, event);\n }\n\n find(start: ClockTime, end: ClockTime): readonly Readonly<T>[] {\n return this._events.filter((event) => {\n return event.time >= start && event.time < end;\n });\n }\n\n /** Return the last event that occurred at or before the specified time. */\n lastEventBefore(time: ClockTime) {\n for (let i = this._events.length - 1; i >= 0; i--) {\n const event = this._events[i];\n if (event.time <= time) {\n return event;\n }\n }\n return undefined;\n }\n\n remove(start: ClockTime, end: ClockTime) {\n this._events = this._events.filter((event) => {\n return !(event.time >= start && event.time < end);\n });\n }\n\n removeAllBefore(time: ClockTime) {\n this._events = this._events.filter((event) => event.time >= time);\n }\n\n clear() {\n this._events = [];\n }\n}\n","import { Timeline, TimelineEvent } from \"./Timeline\";\nimport { ClockTime, Seconds } from \"./types\";\n\n/**\n * Return all events that occur in the interval [start, end).\n */\nexport type EventGenerator<T extends TimelineEvent> = (\n start: ClockTime,\n end: ClockTime,\n) => readonly Readonly<T>[];\n\n/** Consume the speified event. */\nexport type EventConsumer<T extends TimelineEvent> = (\n events: readonly Readonly<T>[],\n) => void;\n\n/**\n * The scheduler is responsible for tracking the scheduling/playback window.\n *\n * Scheduling of events always precedes playback of events. The left hand\n * edge of the scheduling window is the point in time up to where events have\n * been consumed so far. The right hand edge of the window is the point in time\n * up to where events have been scheduled/prepared. The width of the window is\n * always the same (specified when the Scheduler instance is created).\n */\nexport class Scheduler<T extends TimelineEvent = TimelineEvent> {\n private timeline = new Timeline<T>();\n\n private scheduleAheadTime: Seconds;\n private consumedTime: ClockTime;\n\n private generator: EventGenerator<T>;\n private consumer: EventConsumer<T>;\n\n constructor(\n generator: EventGenerator<T>,\n consumer: EventConsumer<T>,\n scheduleAheadTime: Seconds,\n ) {\n this.generator = generator;\n this.consumer = consumer;\n this.scheduleAheadTime = scheduleAheadTime;\n this.consumedTime = -this.scheduleAheadTime;\n }\n\n private _schedule(start: Seconds, end: Seconds) {\n this.generator(start, end).forEach((e) => {\n this.timeline.add(e);\n });\n }\n\n /**\n * Consume events up to the specified time.\n *\n * This will also generate new events up until `time + scheduleAheadTime`.\n */\n runUntil(time: ClockTime) {\n if (time <= this.consumedTime) {\n throw new Error(\"Scheduling time is <= current time\");\n }\n\n /* \n Generate events that occur between the previous and new right-hand edges \n of the scheduling window. I.e., move scheduling time forwards by the \n specified amount.\n */\n this._schedule(\n this.consumedTime + this.scheduleAheadTime,\n time + this.scheduleAheadTime,\n );\n\n /*\n Consume events that occur between the previous and new left-hand edges\n of the scheduling window. I.e., move playback time forwards by the\n specified amount.\n */\n const events = this.timeline.find(this.consumedTime, time);\n this.consumer(events);\n this.consumedTime = time;\n\n /*\n Remove all events that were consumed from the timeline.\n */\n this.timeline.removeAllBefore(this.consumedTime);\n }\n\n /**\n * Jump to the specified destination time.\n */\n jumpTo(time: ClockTime) {\n this.timeline.clear();\n this.consumedTime = time - this.scheduleAheadTime;\n }\n}\n","import { BPM, ClockTime, Ticks } from \"./types\";\nimport { secondsPerTick, ticksPerSecond } from \"./utils\";\n\nexport class Tempo {\n private clockTimeAtLastTempoChange = 0;\n private ticksAtLastTempoChange = 0;\n private _bpm = 120;\n\n get bpm() {\n return this._bpm;\n }\n\n update(clockTime: ClockTime, tempo: BPM) {\n const ticks = this.getTicks(clockTime);\n this.clockTimeAtLastTempoChange = clockTime;\n this.ticksAtLastTempoChange = ticks;\n this._bpm = tempo;\n }\n\n getTicks(clockTime: ClockTime): Ticks {\n const clockDelta = clockTime - this.clockTimeAtLastTempoChange;\n const tickDelta = clockDelta * ticksPerSecond(this.bpm);\n return Math.floor(this.ticksAtLastTempoChange + tickDelta);\n }\n\n getClockTime(ticks: Ticks): ClockTime {\n const tickDelta = Math.floor(ticks - this.ticksAtLastTempoChange);\n const clockDelta = tickDelta * secondsPerTick(this.bpm);\n return this.clockTimeAtLastTempoChange + clockDelta;\n }\n\n reset(clockTime: ClockTime, ticks: Ticks) {\n this.clockTimeAtLastTempoChange = clockTime;\n this.ticksAtLastTempoChange = ticks;\n }\n}\n","import {\n AudioContext,\n AudioBuffer,\n AudioBufferSourceNode,\n} from \"@blibliki/utils/web-audio-api\";\n\nfunction generateUniqueNumber(map: Map<number, () => void>): number {\n let nextId = 1;\n\n // Keep incrementing until we find an unused ID\n while (map.has(nextId)) {\n nextId++;\n }\n\n return nextId;\n}\n\nclass WebAudioWrapper {\n private _audioBuffer: AudioBuffer | null = null;\n private _audioContext: AudioContext | null = null;\n private _sampleDuration: number | null = null;\n\n get audioContext(): AudioContext {\n this._audioContext ??= new AudioContext();\n return this._audioContext;\n }\n\n get audioBuffer(): AudioBuffer {\n this._audioBuffer ??= new AudioBuffer({\n length: 2,\n sampleRate: this.audioContext.sampleRate,\n });\n return this._audioBuffer;\n }\n\n get sampleDuration(): number {\n this._sampleDuration ??= 2 / this.audioContext.sampleRate;\n return this._sampleDuration;\n }\n}\n\nconst webAudioWrapper = new WebAudioWrapper();\n\nconst SCHEDULED_TIMEOUT_FUNCTIONS = new Map<number, () => void>();\nconst SCHEDULED_INTERVAL_FUNCTIONS = new Map<number, () => void>();\n\nenum TimerType {\n interval = \"interval\",\n timeout = \"timeout\",\n}\n\nconst callIntervalFunction = (id: number, type: TimerType) => {\n const functions =\n type === TimerType.interval\n ? SCHEDULED_INTERVAL_FUNCTIONS\n : SCHEDULED_TIMEOUT_FUNCTIONS;\n\n if (functions.has(id)) {\n const func = functions.get(id);\n\n if (func !== undefined) {\n func();\n\n if (type === TimerType.timeout) {\n SCHEDULED_TIMEOUT_FUNCTIONS.delete(id);\n }\n }\n }\n};\n\nconst scheduleFunction = (id: number, delay: number, type: TimerType) => {\n const now = performance.now();\n\n const audioBufferSourceNode = new AudioBufferSourceNode(\n webAudioWrapper.audioContext,\n { buffer: webAudioWrapper.audioBuffer },\n );\n\n audioBufferSourceNode.onended = () => {\n const elapsedTime = performance.now() - now;\n\n if (elapsedTime >= delay) {\n callIntervalFunction(id, type);\n } else {\n scheduleFunction(id, delay - elapsedTime, type);\n }\n\n audioBufferSourceNode.disconnect(webAudioWrapper.audioContext.destination);\n };\n audioBufferSourceNode.connect(webAudioWrapper.audioContext.destination);\n audioBufferSourceNode.start(\n Math.max(\n 0,\n webAudioWrapper.audioContext.currentTime +\n delay / 1000 -\n webAudioWrapper.sampleDuration,\n ),\n );\n};\n\nconst isNode = typeof window === \"undefined\";\n\n// exported methods\n\nconst acClearInterval = (id: number) => {\n SCHEDULED_INTERVAL_FUNCTIONS.delete(id);\n};\n\nconst acClearTimeout = (id: number) => {\n SCHEDULED_TIMEOUT_FUNCTIONS.delete(id);\n};\n\nconst acSetInterval = (func: () => void, delay: number) => {\n const id = generateUniqueNumber(SCHEDULED_INTERVAL_FUNCTIONS);\n\n SCHEDULED_INTERVAL_FUNCTIONS.set(id, () => {\n func();\n\n scheduleFunction(id, delay, TimerType.interval);\n });\n\n scheduleFunction(id, delay, TimerType.interval);\n\n return id;\n};\n\nconst acSetTimeout = (func: () => void, delay: number) => {\n const id = generateUniqueNumber(SCHEDULED_TIMEOUT_FUNCTIONS);\n\n SCHEDULED_TIMEOUT_FUNCTIONS.set(id, func);\n\n scheduleFunction(id, delay, TimerType.timeout);\n\n return id;\n};\n\nconst exportedClearInterval = isNode ? clearInterval : acClearInterval;\nconst exportedClearTimeout = isNode ? clearTimeout : acClearTimeout;\nconst exportedSetInterval = isNode ? setInterval : acSetInterval;\nconst exportedSetTimeout = isNode ? setTimeout : acSetTimeout;\n\nexport {\n exportedClearInterval as clearInterval,\n exportedClearTimeout as clearTimeout,\n exportedSetInterval as setInterval,\n exportedSetTimeout as setTimeout,\n};\n","import { Context } from \"@blibliki/utils\";\nimport { clearInterval, setInterval } from \"./audio-context-timers\";\nimport { ContextTime } from \"./types\";\n\nexport const scheduleAtContextTime = ({\n scheduleAt,\n context,\n callback,\n}: {\n scheduleAt: ContextTime;\n context: Context;\n callback: () => void;\n}) => {\n const now = context.currentTime;\n if (now > scheduleAt) throw Error(\"Could not schedule event at past\");\n\n const ms = (scheduleAt - now) * 1000;\n\n setTimeout(callback, ms);\n};\n\nexport class Timer {\n private timerId: number | undefined = undefined;\n\n private interval: number;\n private callback: () => void;\n\n constructor(callback: () => void, intervalMs: number) {\n this.callback = callback;\n this.interval = intervalMs;\n }\n\n start() {\n this.timerId = setInterval(() => {\n this.callback();\n }, this.interval);\n }\n\n stop() {\n if (this.timerId === undefined) return;\n\n clearInterval(this.timerId);\n this.timerId = undefined;\n }\n\n get isRunning() {\n return this.timerId !== undefined;\n }\n}\n","import { Context } from \"@blibliki/utils\";\nimport { Clock } from \"./Clock\";\nimport { Position } from \"./Position\";\nimport { Scheduler } from \"./Scheduler\";\nimport { Tempo } from \"./Tempo\";\nimport { TimelineEvent } from \"./Timeline\";\nimport { Timer } from \"./Timer\";\nimport {\n BPM,\n ClockTime,\n ContextTime,\n NormalRange,\n Ticks,\n TimeSignature,\n} from \"./types\";\nimport { swing } from \"./utils\";\n\nexport interface TransportEvent extends TimelineEvent {\n ticks: Ticks;\n contextTime: ContextTime;\n}\n\n/**\n * Given a (future) transport time window (in ticks), return all events that should\n * occur within the window. This function is responsible for setting the ticks\n * value for the events returned.\n *\n * IMPORTANT: Subsequent calls to this function may have overlapping time windows.\n * Be careful to not return the same event more than once.\n */\nexport type TransportEventGenerator<T extends TransportEvent> = (\n start: Ticks,\n end: Ticks,\n) => readonly Readonly<T>[];\n\n/**\n * Schedule the specified event with the audio system. The transport class is\n * responsible for setting the `contextTime` of the events.\n */\nexport type TransportEventConsumer<T extends TransportEvent> = (\n event: Readonly<T>,\n) => void;\n\nexport type TransportListener<T extends TransportEvent> = {\n generator: TransportEventGenerator<T>;\n consumer: TransportEventConsumer<T>;\n onJump: (ticks: Ticks) => void;\n onStart: (contextTime: ContextTime) => void;\n onStop: (contextTime: ContextTime) => void;\n silence: (contextTime: ContextTime) => void;\n};\n\n/**\n * Transport callback that gets invoked at (roughly) sixteenth note intervals.\n *\n * Returns the current clock time in seconds.\n *\n * IMPORTANT! Do not rely on this callback for audio precision mechanisms!\n * It is only accurate up to the precision of the event sheduler rate, and may\n * \"jump\" if the scheduling is struggling to keep up.\n */\nexport type TransportClockCallback = (time: ClockTime) => void;\n\nexport enum TransportState {\n playing = \"playing\",\n stopped = \"stopped\",\n paused = \"paused\",\n}\n\n/**\n * This class converts (music) transport time into audio clock time.\n */\nexport class Transport<T extends TransportEvent> {\n private _initialized = false;\n\n private context: Readonly<Context>;\n private clock: Readonly<Clock>;\n private timer: Readonly<Timer>;\n private scheduler: Readonly<Scheduler<T>>;\n\n private _timeSignature: TimeSignature = [4, 4];\n\n private tempo: Readonly<Tempo> = new Tempo();\n private clockTime = 0;\n private _swingAmount: NormalRange = 0.5;\n\n private listener: Readonly<TransportListener<T>>;\n\n private _clockCallbacks: TransportClockCallback[] = [];\n\n constructor(\n context: Readonly<Context>,\n listener: Readonly<TransportListener<T>>,\n ) {\n // ### Make these adapt to performance of app? Or let user set them?\n const SCHEDULE_INTERVAL_MS = 20;\n const SCHEDULE_WINDOW_SIZE_MS = 200;\n\n this.context = context;\n this.listener = listener;\n this.clock = new Clock(this.context, SCHEDULE_WINDOW_SIZE_MS / 1000);\n\n this.scheduler = new Scheduler<T>(\n this.generateEvents,\n this.consumeEvents,\n SCHEDULE_WINDOW_SIZE_MS / 1000,\n );\n\n this.timer = new Timer(() => {\n const time = this.clock.time();\n if (time <= this.clockTime) return;\n\n this.clockTime = time;\n this.scheduler.runUntil(time);\n this._clockCallbacks.forEach((callback) => {\n callback(this.clockTime);\n });\n }, SCHEDULE_INTERVAL_MS / 1000);\n\n this._initialized = true;\n }\n\n addClockCallback(callback: TransportClockCallback) {\n this._clockCallbacks.push(callback);\n }\n\n // ??? Make this more efficient (no need to compute full position, for example)\n addBarCallback(callback: (bar: number) => void) {\n let currentBar = Infinity;\n this.addClockCallback(() => {\n const pos = this.position;\n if (pos.bars !== currentBar) {\n callback(pos.bars);\n currentBar = pos.bars;\n }\n });\n }\n\n get time() {\n return this.clock.time();\n }\n\n /**\n * Return the (approximate) current Transport time, in ticks.\n */\n get position(): Readonly<Position> {\n const clockTime = this.clock.time();\n const ticks = this.tempo.getTicks(clockTime);\n return new Position(ticks, this.timeSignature);\n }\n\n /**\n * Set the current Transport time.\n */\n set position(position: Readonly<Position>) {\n this.jumpTo(position.ticks);\n }\n\n getPositionOfEvent(event: Readonly<TransportEvent>): Readonly<Position> {\n return new Position(event.ticks, this.timeSignature);\n }\n\n /**\n * Start the Transport.\n */\n start() {\n if (!this._initialized) throw new Error(\"Not initialized\");\n if (this.clock.isRunning) return;\n\n const actionAt = this.context.currentTime;\n\n this.listener.onStart(actionAt);\n this.clock.start(actionAt);\n this.timer.start();\n }\n\n /**\n * Stop the Transport.\n */\n stop() {\n if (!this._initialized) throw new Error(\"Not initialized\");\n\n const actionAt = this.context.currentTime;\n\n this.listener.silence(actionAt);\n this.listener.onStop(actionAt);\n this.clock.stop(actionAt);\n this.timer.stop();\n }\n\n /**\n * Reset the Transport to zero.\n */\n reset() {\n if (!this._initialized) throw new Error(\"Not initialized\");\n\n const actionAt = this.context.currentTime;\n this.listener.silence(actionAt);\n this.jumpTo(0);\n }\n\n get bpm(): BPM {\n return this.tempo.bpm;\n }\n\n set bpm(bpm: BPM) {\n this.tempo.update(this.clockTime, bpm);\n }\n\n get timeSignature() {\n return this._timeSignature;\n }\n\n set timeSignature(value: TimeSignature) {\n this._timeSignature = value;\n }\n\n get state() {\n if (this.clock.isRunning) return TransportState.playing;\n else if (this.time > 0) return TransportState.paused;\n else return TransportState.stopped;\n }\n\n get swingAmount() {\n return this._swingAmount;\n }\n\n set swingAmount(amount: NormalRange) {\n this._swingAmount = amount;\n }\n\n private jumpTo(ticks: Ticks) {\n const clockTime = this.tempo.getClockTime(ticks);\n this.tempo.reset(clockTime, ticks);\n this.clock.jumpTo(clockTime);\n this.scheduler.jumpTo(clockTime);\n this.clockTime = clockTime;\n this.listener.onJump(ticks);\n this._clockCallbacks.forEach((callback) => {\n callback(this.clockTime);\n });\n }\n\n private generateEvents = (\n start: ClockTime,\n end: ClockTime,\n ): readonly Readonly<T>[] => {\n // Get transport-time events and return them as clock-time events\n const transportStart = this.tempo.getTicks(start);\n const transportEnd = this.tempo.getTicks(end);\n return this.listener\n .generator(transportStart, transportEnd)\n .map((event) => {\n // Apply swing\n return {\n ...event,\n ticks: swing(event.ticks, this.swingAmount),\n };\n })\n .map((event) => {\n const time = this.tempo.getClockTime(event.ticks);\n const contextTime = this.clock.clockTimeToContextTime(time);\n return {\n ...event,\n time,\n contextTime,\n };\n });\n };\n\n private consumeEvents = (events: readonly Readonly<T>[]) => {\n events.forEach((event) => {\n this.listener.consumer(event);\n });\n };\n}\n"],"mappings":"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","names":["Clock","context","audioClockOffset","now","actionAt","time","clockTime","type","isNumber","TPB","secondsPerTick","tempo","ticksPerSecond","insertionIndexBy","arr","n","comparatorFn","index","el","lerp","t","a","b","unlerp","swing","time","amount","t8","t8i","tn","Position","value","timeSignature","isNumber","TPB","positionString","parts","barsStr","beatsStr","sixteenthsStr","bars","beats","sixteenths","position","totalBeats","beatFraction","Timeline","event","idx","insertionIndexBy","a","b","start","end","time","i","Scheduler","Timeline","generator","consumer","scheduleAheadTime","start","end","e","time","events","Tempo","clockTime","tempo","ticks","tickDelta","ticksPerSecond","clockDelta","secondsPerTick","AudioContext","AudioBuffer","AudioBufferSourceNode","generateUniqueNumber","map","nextId","WebAudioWrapper","webAudioWrapper","SCHEDULED_TIMEOUT_FUNCTIONS","SCHEDULED_INTERVAL_FUNCTIONS","callIntervalFunction","id","type","functions","SCHEDULED_INTERVAL_FUNCTIONS","SCHEDULED_TIMEOUT_FUNCTIONS","func","scheduleFunction","delay","now","audioBufferSourceNode","AudioBufferSourceNode","webAudioWrapper","elapsedTime","isNode","acClearInterval","acSetInterval","func","delay","id","generateUniqueNumber","SCHEDULED_INTERVAL_FUNCTIONS","scheduleFunction","exportedClearInterval","isNode","acClearInterval","exportedSetInterval","isNode","acSetInterval","Timer","callback","intervalMs","exportedSetInterval","exportedClearInterval","TransportState","Transport","Tempo","context","listener","Clock","Scheduler","Timer","time","callback","currentBar","pos","clockTime","ticks","Position","position","event","actionAt","bpm","value","amount","start","end","transportStart","transportEnd","swing","contextTime","events"]}
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@blibliki/transport",
3
- "version": "0.3.10",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
- "source": "src/index.ts",
6
- "main": "dist/index.cjs",
7
- "module": "dist/index.js",
8
- "types": "dist/index.d.ts",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/index.d.ts",
8
+ "import": "./dist/index.js"
9
+ },
10
+ "./package.json": "./package.json"
11
+ },
9
12
  "files": [
10
13
  "README.md",
11
14
  "src",
@@ -15,7 +18,7 @@
15
18
  "@types/audioworklet": "^0.0.91"
16
19
  },
17
20
  "dependencies": {
18
- "@blibliki/utils": "^0.3.10"
21
+ "@blibliki/utils": "^0.4.0"
19
22
  },
20
23
  "scripts": {
21
24
  "build": "tsup",
@@ -1,3 +1,9 @@
1
+ import {
2
+ AudioContext,
3
+ AudioBuffer,
4
+ AudioBufferSourceNode,
5
+ } from "@blibliki/utils/web-audio-api";
6
+
1
7
  function generateUniqueNumber(map: Map<number, () => void>): number {
2
8
  let nextId = 1;
3
9
 
package/dist/index.cjs DELETED
@@ -1,2 +0,0 @@
1
- "use strict";var f=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var O=Object.prototype.hasOwnProperty;var U=(n,t)=>{for(var e in t)f(n,e,{get:t[e],enumerable:!0})},j=(n,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of L(t))!O.call(n,o)&&o!==e&&f(n,o,{get:()=>t[o],enumerable:!(i=D(t,o))||i.enumerable});return n};var z=n=>j(f({},"__esModule",{value:!0}),n);var V={};U(V,{Position:()=>r,Transport:()=>k,TransportState:()=>g});module.exports=z(V);var l=class{context;clockTimeAt;contextTimeAt;audioClockOffset;_isRunning=!1;constructor(t,e){this.context=t,this.audioClockOffset=e,this.clockTimeAt={start:0,stop:0},this.contextTimeAt={start:0,stop:0}}get isRunning(){return this._isRunning}time(){if(!this.isRunning)return this.clockTimeAt.stop;let t=this.context.currentTime;return this.clockTimeAt.start+(t-this.contextTimeAt.start)}start(t){this.contextTimeAt.start=t,this.clockTimeAt.start=this.clockTimeAt.stop,this._isRunning=!0}stop(t){this._isRunning=!1,this.clockTimeAt.stop=this.clockTimeAt.start+(t-this.contextTimeAt.start),this.contextTimeAt.stop=t}jumpTo(t){this.clockTimeAt.stop=t}clockTimeToContextTime(t){let e=this.isRunning?"start":"stop";return this.audioClockOffset+this.contextTimeAt[e]+t-this.clockTimeAt[e]}};var R=require("@blibliki/utils");var a=15360,_=n=>60/n/a,E=n=>n/60*a,A=(n,t,e)=>{let i=n.findIndex(o=>e(t,o)<0);return i===-1?n.length:i};function b(n,t,e){return t+n*(e-t)}function S(n,t,e){return(n-t)/(e-t)}function y(n,t){if(t<.5||t>.75)throw new Error("Invalid swing amount");let e=n/7680,i=Math.floor(e),o=e-i,s=0;return o<=.5?s=b(S(o,0,.5),0,t):s=b(S(o,.5,1),t,1),Math.floor((i+s)*7680)}var r=class{_ticks;timeSignature;constructor(t,e){this.timeSignature=e,(0,R.isNumber)(t)?this._ticks=t:typeof t=="string"?this._ticks=this.parseStringPosition(t):this._ticks=this.convertObjectToTicks(t)}get ticks(){return this._ticks}get bars(){return Math.floor(this.totalBeats/this.timeSignature[0])+1}get beats(){return this.totalBeats%this.timeSignature[0]+1}get sixteenths(){return Math.floor(this.beatFraction*4)+1}get totalBeats(){return Math.floor(this._ticks/a)}get beatFraction(){return this._ticks/a-this.totalBeats}toString(){return`${this.bars}:${this.beats}:${this.sixteenths}`}toObject(){return{bars:this.bars,beats:this.beats,sixteenths:this.sixteenths}}parseStringPosition(t){let e=t.split(":"),[i,o,s]=e,c=Number(i),v=Number(o),M=Number(s),N={bars:c,beats:v,sixteenths:M};return this.convertObjectToTicks(N)}convertObjectToTicks(t){let e=(t.bars-1)*this.timeSignature[0]+(t.beats-1),i=t.sixteenths/16;return(e+i)*a}};var u=class{_events=[];get events(){return this._events}add(t){let e=A(this._events,t,(i,o)=>i.time-o.time);this._events.splice(e,0,t)}find(t,e){return this._events.filter(i=>i.time>=t&&i.time<e)}lastEventBefore(t){for(let e=this._events.length-1;e>=0;e--){let i=this._events[e];if(i.time<=t)return i}}remove(t,e){this._events=this._events.filter(i=>!(i.time>=t&&i.time<e))}removeAllBefore(t){this._events=this._events.filter(e=>e.time>=t)}clear(){this._events=[]}};var T=class{timeline=new u;scheduleAheadTime;consumedTime;generator;consumer;constructor(t,e,i){this.generator=t,this.consumer=e,this.scheduleAheadTime=i,this.consumedTime=-this.scheduleAheadTime}_schedule(t,e){this.generator(t,e).forEach(i=>{this.timeline.add(i)})}runUntil(t){if(t<=this.consumedTime)throw new Error("Scheduling time is <= current time");this._schedule(this.consumedTime+this.scheduleAheadTime,t+this.scheduleAheadTime);let e=this.timeline.find(this.consumedTime,t);this.consumer(e),this.consumedTime=t,this.timeline.removeAllBefore(this.consumedTime)}jumpTo(t){this.timeline.clear(),this.consumedTime=t-this.scheduleAheadTime}};var h=class{clockTimeAtLastTempoChange=0;ticksAtLastTempoChange=0;_bpm=120;get bpm(){return this._bpm}update(t,e){let i=this.getTicks(t);this.clockTimeAtLastTempoChange=t,this.ticksAtLastTempoChange=i,this._bpm=e}getTicks(t){let i=(t-this.clockTimeAtLastTempoChange)*E(this.bpm);return Math.floor(this.ticksAtLastTempoChange+i)}getClockTime(t){let i=Math.floor(t-this.ticksAtLastTempoChange)*_(this.bpm);return this.clockTimeAtLastTempoChange+i}reset(t,e){this.clockTimeAtLastTempoChange=t,this.ticksAtLastTempoChange=e}};function F(n){let t=1;for(;n.has(t);)t++;return t}var x=class{_audioBuffer=null;_audioContext=null;_sampleDuration=null;get audioContext(){return this._audioContext??=new AudioContext,this._audioContext}get audioBuffer(){return this._audioBuffer??=new AudioBuffer({length:2,sampleRate:this.audioContext.sampleRate}),this._audioBuffer}get sampleDuration(){return this._sampleDuration??=2/this.audioContext.sampleRate,this._sampleDuration}},m=new x,w=new Map,p=new Map;var H=(n,t)=>{let e=t==="interval"?p:w;if(e.has(n)){let i=e.get(n);i!==void 0&&(i(),t==="timeout"&&w.delete(n))}},C=(n,t,e)=>{let i=performance.now(),o=new AudioBufferSourceNode(m.audioContext,{buffer:m.audioBuffer});o.onended=()=>{let s=performance.now()-i;s>=t?H(n,e):C(n,t-s,e),o.disconnect(m.audioContext.destination)},o.connect(m.audioContext.destination),o.start(Math.max(0,m.audioContext.currentTime+t/1e3-m.sampleDuration))},I=typeof window>"u",G=n=>{p.delete(n)};var W=(n,t)=>{let e=F(p);return p.set(e,()=>{n(),C(e,t,"interval")}),C(e,t,"interval"),e};var B=I?clearInterval:G;var P=I?setInterval:W;var d=class{timerId=void 0;interval;callback;constructor(t,e){this.callback=t,this.interval=e}start(){this.timerId=P(()=>{this.callback()},this.interval)}stop(){this.timerId!==void 0&&(B(this.timerId),this.timerId=void 0)}get isRunning(){return this.timerId!==void 0}};var g=(i=>(i.playing="playing",i.stopped="stopped",i.paused="paused",i))(g||{}),k=class{_initialized=!1;context;clock;timer;scheduler;_timeSignature=[4,4];tempo=new h;clockTime=0;_swingAmount=.5;listener;_clockCallbacks=[];constructor(t,e){this.context=t,this.listener=e,this.clock=new l(this.context,200/1e3),this.scheduler=new T(this.generateEvents,this.consumeEvents,200/1e3),this.timer=new d(()=>{let s=this.clock.time();s<=this.clockTime||(this.clockTime=s,this.scheduler.runUntil(s),this._clockCallbacks.forEach(c=>{c(this.clockTime)}))},20/1e3),this._initialized=!0}addClockCallback(t){this._clockCallbacks.push(t)}addBarCallback(t){let e=1/0;this.addClockCallback(()=>{let i=this.position;i.bars!==e&&(t(i.bars),e=i.bars)})}get time(){return this.clock.time()}get position(){let t=this.clock.time(),e=this.tempo.getTicks(t);return new r(e,this.timeSignature)}set position(t){this.jumpTo(t.ticks)}getPositionOfEvent(t){return new r(t.ticks,this.timeSignature)}start(){if(!this._initialized)throw new Error("Not initialized");if(this.clock.isRunning)return;let t=this.context.currentTime;this.listener.onStart(t),this.clock.start(t),this.timer.start()}stop(){if(!this._initialized)throw new Error("Not initialized");let t=this.context.currentTime;this.listener.silence(t),this.listener.onStop(t),this.clock.stop(t),this.timer.stop()}reset(){if(!this._initialized)throw new Error("Not initialized");let t=this.context.currentTime;this.listener.silence(t),this.jumpTo(0)}get bpm(){return this.tempo.bpm}set bpm(t){this.tempo.update(this.clockTime,t)}get timeSignature(){return this._timeSignature}set timeSignature(t){this._timeSignature=t}get state(){return this.clock.isRunning?"playing":this.time>0?"paused":"stopped"}get swingAmount(){return this._swingAmount}set swingAmount(t){this._swingAmount=t}jumpTo(t){let e=this.tempo.getClockTime(t);this.tempo.reset(e,t),this.clock.jumpTo(e),this.scheduler.jumpTo(e),this.clockTime=e,this.listener.onJump(t),this._clockCallbacks.forEach(i=>{i(this.clockTime)})}generateEvents=(t,e)=>{let i=this.tempo.getTicks(t),o=this.tempo.getTicks(e);return this.listener.generator(i,o).map(s=>({...s,ticks:y(s.ticks,this.swingAmount)})).map(s=>{let c=this.tempo.getClockTime(s.ticks),v=this.clock.clockTimeToContextTime(c);return{...s,time:c,contextTime:v}})};consumeEvents=t=>{t.forEach(e=>{this.listener.consumer(e)})}};
2
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts","../src/Clock.ts","../src/Position.ts","../src/utils.ts","../src/Timeline.ts","../src/Scheduler.ts","../src/Tempo.ts","../src/audio-context-timers.ts","../src/Timer.ts","../src/Transport.ts"],"sourcesContent":["export { Transport, TransportState } from \"./Transport\";\nexport type { TransportEvent } from \"./Transport\";\nexport { Position } from \"./Position\";\nexport type {\n Seconds,\n Ticks,\n BPM,\n ContextTime,\n ClockTime,\n TimeSignature,\n} from \"./types\";\n","import { Context } from \"@blibliki/utils\";\nimport { ClockTime, ContextTime, Seconds } from \"./types\";\n\nexport class Clock {\n private context: Readonly<Context>;\n private clockTimeAt: { start: ClockTime; stop: ClockTime };\n private contextTimeAt: { start: ContextTime; stop: ContextTime };\n private audioClockOffset: number;\n\n private _isRunning = false;\n\n constructor(context: Readonly<Context>, audioClockOffset: Seconds) {\n this.context = context;\n this.audioClockOffset = audioClockOffset;\n\n this.clockTimeAt = { start: 0, stop: 0 };\n this.contextTimeAt = { start: 0, stop: 0 };\n }\n\n get isRunning() {\n return this._isRunning;\n }\n\n time() {\n if (!this.isRunning) return this.clockTimeAt.stop;\n\n const now = this.context.currentTime;\n return this.clockTimeAt.start + (now - this.contextTimeAt.start);\n }\n\n start(actionAt: ContextTime) {\n this.contextTimeAt.start = actionAt;\n this.clockTimeAt.start = this.clockTimeAt.stop;\n this._isRunning = true;\n }\n\n stop(actionAt: ContextTime) {\n this._isRunning = false;\n this.clockTimeAt.stop =\n this.clockTimeAt.start + (actionAt - this.contextTimeAt.start);\n this.contextTimeAt.stop = actionAt;\n }\n\n jumpTo(time: ClockTime) {\n this.clockTimeAt.stop = time;\n }\n\n clockTimeToContextTime(clockTime: ClockTime): ContextTime {\n const type = this.isRunning ? \"start\" : \"stop\";\n\n return (\n this.audioClockOffset +\n this.contextTimeAt[type] +\n clockTime -\n this.clockTimeAt[type]\n );\n }\n}\n","import { isNumber } from \"@blibliki/utils\";\nimport { Ticks, TimeSignature, TPosition, TStringPosition } from \"./types\";\nimport { TPB } from \"./utils\";\n\nexport const sixteenthPerBeat = (timeSignature: Readonly<TimeSignature>) => {\n const denominator = timeSignature[1];\n return 16 / denominator;\n};\n\n/**\n * Represents a musical position that can be expressed in multiple formats:\n * - Ticks (raw MIDI time units)\n * - Bars:Beats:Sixteenths (string format like \"1:2:8\")\n * - Object format with bars, beats, and sixteenths properties\n */\nexport class Position {\n private readonly _ticks: Ticks;\n private timeSignature: Readonly<TimeSignature>;\n\n /**\n * Creates a new Position instance\n * @param value - Position value in ticks, string format (\"bars:beats:sixteenths\"), or object format\n * @param timeSignature - Time signature as [numerator, denominator] (e.g., [4, 4] for 4/4)\n */\n constructor(\n value: Ticks | TPosition | TStringPosition,\n timeSignature: TimeSignature,\n ) {\n this.timeSignature = timeSignature;\n\n if (isNumber(value)) {\n this._ticks = value;\n } else if (typeof value === \"string\") {\n this._ticks = this.parseStringPosition(value);\n } else {\n this._ticks = this.convertObjectToTicks(value);\n }\n }\n\n get ticks() {\n return this._ticks;\n }\n\n get bars() {\n return Math.floor(this.totalBeats / this.timeSignature[0]) + 1;\n }\n\n get beats() {\n return (this.totalBeats % this.timeSignature[0]) + 1;\n }\n\n get sixteenths() {\n return Math.floor(this.beatFraction * 4) + 1;\n }\n\n get totalBeats() {\n return Math.floor(this._ticks / TPB);\n }\n\n get beatFraction() {\n return this._ticks / TPB - this.totalBeats;\n }\n\n toString(): string {\n return `${this.bars}:${this.beats}:${this.sixteenths}`;\n }\n\n toObject(): TPosition {\n return {\n bars: this.bars,\n beats: this.beats,\n sixteenths: this.sixteenths,\n };\n }\n\n private parseStringPosition(positionString: TStringPosition): Ticks {\n const parts = positionString.split(\":\");\n\n const [barsStr, beatsStr, sixteenthsStr] = parts;\n const bars = Number(barsStr);\n const beats = Number(beatsStr);\n const sixteenths = Number(sixteenthsStr);\n\n const position: TPosition = { bars, beats, sixteenths };\n\n return this.convertObjectToTicks(position);\n }\n\n private convertObjectToTicks(position: TPosition): Ticks {\n const totalBeats =\n (position.bars - 1) * this.timeSignature[0] + (position.beats - 1);\n const beatFraction = position.sixteenths / 16;\n return (totalBeats + beatFraction) * TPB;\n }\n}\n","import { BPM, NormalRange, Ticks } from \"./types\";\n\n// Ticks per beat\nexport const TPB = 15360;\n\nexport const secondsPerTick = (tempo: BPM) => {\n return 60 / tempo / TPB;\n};\n\nexport const ticksPerSecond = (tempo: BPM) => {\n return (tempo / 60) * TPB;\n};\n\nexport const insertionIndexBy = <T>(\n arr: T[],\n n: T,\n comparatorFn: (a: T, b: T) => number,\n) => {\n const index = arr.findIndex((el) => comparatorFn(n, el) < 0);\n return index === -1 ? arr.length : index;\n};\n\nexport function lerp(t: number, a: number, b: number): number {\n return a + t * (b - a);\n}\n\nexport function unlerp(t: number, a: number, b: number): number {\n return (t - a) / (b - a);\n}\n\n/**\n * Compute swing.\n *\n * This function is an attempt at mirroring the behaviour in the Reason DAW,\n * where events before the 50% eighth mark are \"expanded\" and events past the\n * mark are \"compressed\". That is, this function transforms the underlying\n * transport time grid:\n *\n * No swing |<--50%-->|<--50%-->|<--50%-->| ...\n * 16ths 0 1 2 3 ...\n * 8ths 0 | 1 | ...\n * Notes X======X X===X | |\n * | | | |\n * | \\ | \\\n * 60% swing |<---60%--->|<-40%->|<---60%--->| ...\n * 16ths 0 1 2 3 ...\n * 8ths 0 | 1 | ...\n * Notes X=======X X==X | |\n *\n * If it is challenging to explain this mechanism to end users, you could\n * use the approach taken in the Logic DAW, which has 6 \"swing modes\":\n *\n * 16A: 50%\n * 16B: 54%\n * 16C: 58%\n * 16D: 62%\n * 16E: 66%\n * 16F: 71%\n *\n * For details, see\n * https://www.attackmagazine.com/technique/passing-notes/daw-drum-machine-swing/2/\n *\n * @param time Time of event, in transport ticks (1/4 = 15360 pulses)\n * @param amount Swing amount in range [0.5, 0.75].\n * @returns New time of the event, with swing applied.\n */\nexport function swing(time: Ticks, amount: NormalRange): Ticks {\n if (amount < 0.5 || amount > 0.75) {\n throw new Error(\"Invalid swing amount\");\n }\n\n /*\n Note lengths in ticks:\n\n 1/4 = 15360\n 1/8 = 7680\n 1/16 = 3840\n */\n\n const t8 = time / 7680; // Input time in 8ths\n const t8i = Math.floor(t8); // 8th in which the time sits\n const t = t8 - t8i; // Percentage position in the 8th in which the time sits\n\n /* \n ### We could make this a bit more efficient; we can simplify the \n <=0.5 case to\n\n (t / 0.5) * amount\n\n But the >0.5 case is more difficult:\n\n amount + ((t - 0.5) / 0.5) * (1 - amount)\n\n Using the lerp/unlerp functions here makes it clear what's going on,\n though, so we'll keep them for now.\n */\n let tn = 0;\n if (t <= 0.5) {\n tn = lerp(unlerp(t, 0.0, 0.5), 0.0, amount);\n } else {\n tn = lerp(unlerp(t, 0.5, 1.0), amount, 1.0);\n }\n\n // Map transformed time back into ticks\n return Math.floor((t8i + tn) * 7680);\n}\n","import { ClockTime } from \"./types\";\nimport { insertionIndexBy } from \"./utils\";\n\nexport type TimelineEvent = {\n time: ClockTime;\n};\n\nexport class Timeline<T extends TimelineEvent = TimelineEvent> {\n private _events: Readonly<T>[] = [];\n\n get events(): readonly Readonly<T>[] {\n return this._events;\n }\n\n /**\n * Add a new event to the timeline. It will be inserted according to its\n * `time` property (which must be present; otherwise an exception is thrown).\n *\n * If the time of the new event is exactly equal to the time of an existing\n * event in the timeline, the existing event is considered to occur before\n * the new event.\n */\n add(event: Readonly<T>) {\n const idx = insertionIndexBy(this._events, event, (a, b) => {\n return a.time - b.time;\n });\n this._events.splice(idx, 0, event);\n }\n\n find(start: ClockTime, end: ClockTime): readonly Readonly<T>[] {\n return this._events.filter((event) => {\n return event.time >= start && event.time < end;\n });\n }\n\n /** Return the last event that occurred at or before the specified time. */\n lastEventBefore(time: ClockTime) {\n for (let i = this._events.length - 1; i >= 0; i--) {\n const event = this._events[i];\n if (event.time <= time) {\n return event;\n }\n }\n return undefined;\n }\n\n remove(start: ClockTime, end: ClockTime) {\n this._events = this._events.filter((event) => {\n return !(event.time >= start && event.time < end);\n });\n }\n\n removeAllBefore(time: ClockTime) {\n this._events = this._events.filter((event) => event.time >= time);\n }\n\n clear() {\n this._events = [];\n }\n}\n","import { Timeline, TimelineEvent } from \"./Timeline\";\nimport { ClockTime, Seconds } from \"./types\";\n\n/**\n * Return all events that occur in the interval [start, end).\n */\nexport type EventGenerator<T extends TimelineEvent> = (\n start: ClockTime,\n end: ClockTime,\n) => readonly Readonly<T>[];\n\n/** Consume the speified event. */\nexport type EventConsumer<T extends TimelineEvent> = (\n events: readonly Readonly<T>[],\n) => void;\n\n/**\n * The scheduler is responsible for tracking the scheduling/playback window.\n *\n * Scheduling of events always precedes playback of events. The left hand\n * edge of the scheduling window is the point in time up to where events have\n * been consumed so far. The right hand edge of the window is the point in time\n * up to where events have been scheduled/prepared. The width of the window is\n * always the same (specified when the Scheduler instance is created).\n */\nexport class Scheduler<T extends TimelineEvent = TimelineEvent> {\n private timeline = new Timeline<T>();\n\n private scheduleAheadTime: Seconds;\n private consumedTime: ClockTime;\n\n private generator: EventGenerator<T>;\n private consumer: EventConsumer<T>;\n\n constructor(\n generator: EventGenerator<T>,\n consumer: EventConsumer<T>,\n scheduleAheadTime: Seconds,\n ) {\n this.generator = generator;\n this.consumer = consumer;\n this.scheduleAheadTime = scheduleAheadTime;\n this.consumedTime = -this.scheduleAheadTime;\n }\n\n private _schedule(start: Seconds, end: Seconds) {\n this.generator(start, end).forEach((e) => {\n this.timeline.add(e);\n });\n }\n\n /**\n * Consume events up to the specified time.\n *\n * This will also generate new events up until `time + scheduleAheadTime`.\n */\n runUntil(time: ClockTime) {\n if (time <= this.consumedTime) {\n throw new Error(\"Scheduling time is <= current time\");\n }\n\n /* \n Generate events that occur between the previous and new right-hand edges \n of the scheduling window. I.e., move scheduling time forwards by the \n specified amount.\n */\n this._schedule(\n this.consumedTime + this.scheduleAheadTime,\n time + this.scheduleAheadTime,\n );\n\n /*\n Consume events that occur between the previous and new left-hand edges\n of the scheduling window. I.e., move playback time forwards by the\n specified amount.\n */\n const events = this.timeline.find(this.consumedTime, time);\n this.consumer(events);\n this.consumedTime = time;\n\n /*\n Remove all events that were consumed from the timeline.\n */\n this.timeline.removeAllBefore(this.consumedTime);\n }\n\n /**\n * Jump to the specified destination time.\n */\n jumpTo(time: ClockTime) {\n this.timeline.clear();\n this.consumedTime = time - this.scheduleAheadTime;\n }\n}\n","import { BPM, ClockTime, Ticks } from \"./types\";\nimport { secondsPerTick, ticksPerSecond } from \"./utils\";\n\nexport class Tempo {\n private clockTimeAtLastTempoChange = 0;\n private ticksAtLastTempoChange = 0;\n private _bpm = 120;\n\n get bpm() {\n return this._bpm;\n }\n\n update(clockTime: ClockTime, tempo: BPM) {\n const ticks = this.getTicks(clockTime);\n this.clockTimeAtLastTempoChange = clockTime;\n this.ticksAtLastTempoChange = ticks;\n this._bpm = tempo;\n }\n\n getTicks(clockTime: ClockTime): Ticks {\n const clockDelta = clockTime - this.clockTimeAtLastTempoChange;\n const tickDelta = clockDelta * ticksPerSecond(this.bpm);\n return Math.floor(this.ticksAtLastTempoChange + tickDelta);\n }\n\n getClockTime(ticks: Ticks): ClockTime {\n const tickDelta = Math.floor(ticks - this.ticksAtLastTempoChange);\n const clockDelta = tickDelta * secondsPerTick(this.bpm);\n return this.clockTimeAtLastTempoChange + clockDelta;\n }\n\n reset(clockTime: ClockTime, ticks: Ticks) {\n this.clockTimeAtLastTempoChange = clockTime;\n this.ticksAtLastTempoChange = ticks;\n }\n}\n","function generateUniqueNumber(map: Map<number, () => void>): number {\n let nextId = 1;\n\n // Keep incrementing until we find an unused ID\n while (map.has(nextId)) {\n nextId++;\n }\n\n return nextId;\n}\n\nclass WebAudioWrapper {\n private _audioBuffer: AudioBuffer | null = null;\n private _audioContext: AudioContext | null = null;\n private _sampleDuration: number | null = null;\n\n get audioContext(): AudioContext {\n this._audioContext ??= new AudioContext();\n return this._audioContext;\n }\n\n get audioBuffer(): AudioBuffer {\n this._audioBuffer ??= new AudioBuffer({\n length: 2,\n sampleRate: this.audioContext.sampleRate,\n });\n return this._audioBuffer;\n }\n\n get sampleDuration(): number {\n this._sampleDuration ??= 2 / this.audioContext.sampleRate;\n return this._sampleDuration;\n }\n}\n\nconst webAudioWrapper = new WebAudioWrapper();\n\nconst SCHEDULED_TIMEOUT_FUNCTIONS = new Map<number, () => void>();\nconst SCHEDULED_INTERVAL_FUNCTIONS = new Map<number, () => void>();\n\nenum TimerType {\n interval = \"interval\",\n timeout = \"timeout\",\n}\n\nconst callIntervalFunction = (id: number, type: TimerType) => {\n const functions =\n type === TimerType.interval\n ? SCHEDULED_INTERVAL_FUNCTIONS\n : SCHEDULED_TIMEOUT_FUNCTIONS;\n\n if (functions.has(id)) {\n const func = functions.get(id);\n\n if (func !== undefined) {\n func();\n\n if (type === TimerType.timeout) {\n SCHEDULED_TIMEOUT_FUNCTIONS.delete(id);\n }\n }\n }\n};\n\nconst scheduleFunction = (id: number, delay: number, type: TimerType) => {\n const now = performance.now();\n\n const audioBufferSourceNode = new AudioBufferSourceNode(\n webAudioWrapper.audioContext,\n { buffer: webAudioWrapper.audioBuffer },\n );\n\n audioBufferSourceNode.onended = () => {\n const elapsedTime = performance.now() - now;\n\n if (elapsedTime >= delay) {\n callIntervalFunction(id, type);\n } else {\n scheduleFunction(id, delay - elapsedTime, type);\n }\n\n audioBufferSourceNode.disconnect(webAudioWrapper.audioContext.destination);\n };\n audioBufferSourceNode.connect(webAudioWrapper.audioContext.destination);\n audioBufferSourceNode.start(\n Math.max(\n 0,\n webAudioWrapper.audioContext.currentTime +\n delay / 1000 -\n webAudioWrapper.sampleDuration,\n ),\n );\n};\n\nconst isNode = typeof window === \"undefined\";\n\n// exported methods\n\nconst acClearInterval = (id: number) => {\n SCHEDULED_INTERVAL_FUNCTIONS.delete(id);\n};\n\nconst acClearTimeout = (id: number) => {\n SCHEDULED_TIMEOUT_FUNCTIONS.delete(id);\n};\n\nconst acSetInterval = (func: () => void, delay: number) => {\n const id = generateUniqueNumber(SCHEDULED_INTERVAL_FUNCTIONS);\n\n SCHEDULED_INTERVAL_FUNCTIONS.set(id, () => {\n func();\n\n scheduleFunction(id, delay, TimerType.interval);\n });\n\n scheduleFunction(id, delay, TimerType.interval);\n\n return id;\n};\n\nconst acSetTimeout = (func: () => void, delay: number) => {\n const id = generateUniqueNumber(SCHEDULED_TIMEOUT_FUNCTIONS);\n\n SCHEDULED_TIMEOUT_FUNCTIONS.set(id, func);\n\n scheduleFunction(id, delay, TimerType.timeout);\n\n return id;\n};\n\nconst exportedClearInterval = isNode ? clearInterval : acClearInterval;\nconst exportedClearTimeout = isNode ? clearTimeout : acClearTimeout;\nconst exportedSetInterval = isNode ? setInterval : acSetInterval;\nconst exportedSetTimeout = isNode ? setTimeout : acSetTimeout;\n\nexport {\n exportedClearInterval as clearInterval,\n exportedClearTimeout as clearTimeout,\n exportedSetInterval as setInterval,\n exportedSetTimeout as setTimeout,\n};\n","import { Context } from \"@blibliki/utils\";\nimport { clearInterval, setInterval } from \"./audio-context-timers\";\nimport { ContextTime } from \"./types\";\n\nexport const scheduleAtContextTime = ({\n scheduleAt,\n context,\n callback,\n}: {\n scheduleAt: ContextTime;\n context: Context;\n callback: () => void;\n}) => {\n const now = context.currentTime;\n if (now > scheduleAt) throw Error(\"Could not schedule event at past\");\n\n const ms = (scheduleAt - now) * 1000;\n\n setTimeout(callback, ms);\n};\n\nexport class Timer {\n private timerId: number | undefined = undefined;\n\n private interval: number;\n private callback: () => void;\n\n constructor(callback: () => void, intervalMs: number) {\n this.callback = callback;\n this.interval = intervalMs;\n }\n\n start() {\n this.timerId = setInterval(() => {\n this.callback();\n }, this.interval);\n }\n\n stop() {\n if (this.timerId === undefined) return;\n\n clearInterval(this.timerId);\n this.timerId = undefined;\n }\n\n get isRunning() {\n return this.timerId !== undefined;\n }\n}\n","import { Context } from \"@blibliki/utils\";\nimport { Clock } from \"./Clock\";\nimport { Position } from \"./Position\";\nimport { Scheduler } from \"./Scheduler\";\nimport { Tempo } from \"./Tempo\";\nimport { TimelineEvent } from \"./Timeline\";\nimport { Timer } from \"./Timer\";\nimport {\n BPM,\n ClockTime,\n ContextTime,\n NormalRange,\n Ticks,\n TimeSignature,\n} from \"./types\";\nimport { swing } from \"./utils\";\n\nexport interface TransportEvent extends TimelineEvent {\n ticks: Ticks;\n contextTime: ContextTime;\n}\n\n/**\n * Given a (future) transport time window (in ticks), return all events that should\n * occur within the window. This function is responsible for setting the ticks\n * value for the events returned.\n *\n * IMPORTANT: Subsequent calls to this function may have overlapping time windows.\n * Be careful to not return the same event more than once.\n */\nexport type TransportEventGenerator<T extends TransportEvent> = (\n start: Ticks,\n end: Ticks,\n) => readonly Readonly<T>[];\n\n/**\n * Schedule the specified event with the audio system. The transport class is\n * responsible for setting the `contextTime` of the events.\n */\nexport type TransportEventConsumer<T extends TransportEvent> = (\n event: Readonly<T>,\n) => void;\n\nexport type TransportListener<T extends TransportEvent> = {\n generator: TransportEventGenerator<T>;\n consumer: TransportEventConsumer<T>;\n onJump: (ticks: Ticks) => void;\n onStart: (contextTime: ContextTime) => void;\n onStop: (contextTime: ContextTime) => void;\n silence: (contextTime: ContextTime) => void;\n};\n\n/**\n * Transport callback that gets invoked at (roughly) sixteenth note intervals.\n *\n * Returns the current clock time in seconds.\n *\n * IMPORTANT! Do not rely on this callback for audio precision mechanisms!\n * It is only accurate up to the precision of the event sheduler rate, and may\n * \"jump\" if the scheduling is struggling to keep up.\n */\nexport type TransportClockCallback = (time: ClockTime) => void;\n\nexport enum TransportState {\n playing = \"playing\",\n stopped = \"stopped\",\n paused = \"paused\",\n}\n\n/**\n * This class converts (music) transport time into audio clock time.\n */\nexport class Transport<T extends TransportEvent> {\n private _initialized = false;\n\n private context: Readonly<Context>;\n private clock: Readonly<Clock>;\n private timer: Readonly<Timer>;\n private scheduler: Readonly<Scheduler<T>>;\n\n private _timeSignature: TimeSignature = [4, 4];\n\n private tempo: Readonly<Tempo> = new Tempo();\n private clockTime = 0;\n private _swingAmount: NormalRange = 0.5;\n\n private listener: Readonly<TransportListener<T>>;\n\n private _clockCallbacks: TransportClockCallback[] = [];\n\n constructor(\n context: Readonly<Context>,\n listener: Readonly<TransportListener<T>>,\n ) {\n // ### Make these adapt to performance of app? Or let user set them?\n const SCHEDULE_INTERVAL_MS = 20;\n const SCHEDULE_WINDOW_SIZE_MS = 200;\n\n this.context = context;\n this.listener = listener;\n this.clock = new Clock(this.context, SCHEDULE_WINDOW_SIZE_MS / 1000);\n\n this.scheduler = new Scheduler<T>(\n this.generateEvents,\n this.consumeEvents,\n SCHEDULE_WINDOW_SIZE_MS / 1000,\n );\n\n this.timer = new Timer(() => {\n const time = this.clock.time();\n if (time <= this.clockTime) return;\n\n this.clockTime = time;\n this.scheduler.runUntil(time);\n this._clockCallbacks.forEach((callback) => {\n callback(this.clockTime);\n });\n }, SCHEDULE_INTERVAL_MS / 1000);\n\n this._initialized = true;\n }\n\n addClockCallback(callback: TransportClockCallback) {\n this._clockCallbacks.push(callback);\n }\n\n // ??? Make this more efficient (no need to compute full position, for example)\n addBarCallback(callback: (bar: number) => void) {\n let currentBar = Infinity;\n this.addClockCallback(() => {\n const pos = this.position;\n if (pos.bars !== currentBar) {\n callback(pos.bars);\n currentBar = pos.bars;\n }\n });\n }\n\n get time() {\n return this.clock.time();\n }\n\n /**\n * Return the (approximate) current Transport time, in ticks.\n */\n get position(): Readonly<Position> {\n const clockTime = this.clock.time();\n const ticks = this.tempo.getTicks(clockTime);\n return new Position(ticks, this.timeSignature);\n }\n\n /**\n * Set the current Transport time.\n */\n set position(position: Readonly<Position>) {\n this.jumpTo(position.ticks);\n }\n\n getPositionOfEvent(event: Readonly<TransportEvent>): Readonly<Position> {\n return new Position(event.ticks, this.timeSignature);\n }\n\n /**\n * Start the Transport.\n */\n start() {\n if (!this._initialized) throw new Error(\"Not initialized\");\n if (this.clock.isRunning) return;\n\n const actionAt = this.context.currentTime;\n\n this.listener.onStart(actionAt);\n this.clock.start(actionAt);\n this.timer.start();\n }\n\n /**\n * Stop the Transport.\n */\n stop() {\n if (!this._initialized) throw new Error(\"Not initialized\");\n\n const actionAt = this.context.currentTime;\n\n this.listener.silence(actionAt);\n this.listener.onStop(actionAt);\n this.clock.stop(actionAt);\n this.timer.stop();\n }\n\n /**\n * Reset the Transport to zero.\n */\n reset() {\n if (!this._initialized) throw new Error(\"Not initialized\");\n\n const actionAt = this.context.currentTime;\n this.listener.silence(actionAt);\n this.jumpTo(0);\n }\n\n get bpm(): BPM {\n return this.tempo.bpm;\n }\n\n set bpm(bpm: BPM) {\n this.tempo.update(this.clockTime, bpm);\n }\n\n get timeSignature() {\n return this._timeSignature;\n }\n\n set timeSignature(value: TimeSignature) {\n this._timeSignature = value;\n }\n\n get state() {\n if (this.clock.isRunning) return TransportState.playing;\n else if (this.time > 0) return TransportState.paused;\n else return TransportState.stopped;\n }\n\n get swingAmount() {\n return this._swingAmount;\n }\n\n set swingAmount(amount: NormalRange) {\n this._swingAmount = amount;\n }\n\n private jumpTo(ticks: Ticks) {\n const clockTime = this.tempo.getClockTime(ticks);\n this.tempo.reset(clockTime, ticks);\n this.clock.jumpTo(clockTime);\n this.scheduler.jumpTo(clockTime);\n this.clockTime = clockTime;\n this.listener.onJump(ticks);\n this._clockCallbacks.forEach((callback) => {\n callback(this.clockTime);\n });\n }\n\n private generateEvents = (\n start: ClockTime,\n end: ClockTime,\n ): readonly Readonly<T>[] => {\n // Get transport-time events and return them as clock-time events\n const transportStart = this.tempo.getTicks(start);\n const transportEnd = this.tempo.getTicks(end);\n return this.listener\n .generator(transportStart, transportEnd)\n .map((event) => {\n // Apply swing\n return {\n ...event,\n ticks: swing(event.ticks, this.swingAmount),\n };\n })\n .map((event) => {\n const time = this.tempo.getClockTime(event.ticks);\n const contextTime = this.clock.clockTimeToContextTime(time);\n return {\n ...event,\n time,\n contextTime,\n };\n });\n };\n\n private consumeEvents = (events: readonly Readonly<T>[]) => {\n events.forEach((event) => {\n this.listener.consumer(event);\n });\n };\n}\n"],"mappings":"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","names":["index_exports","__export","Position","Transport","TransportState","__toCommonJS","Clock","context","audioClockOffset","now","actionAt","time","clockTime","type","import_utils","TPB","secondsPerTick","tempo","ticksPerSecond","insertionIndexBy","arr","n","comparatorFn","index","el","lerp","t","a","b","unlerp","swing","time","amount","t8","t8i","tn","Position","value","timeSignature","TPB","positionString","parts","barsStr","beatsStr","sixteenthsStr","bars","beats","sixteenths","position","totalBeats","beatFraction","Timeline","event","idx","insertionIndexBy","a","b","start","end","time","i","Scheduler","Timeline","generator","consumer","scheduleAheadTime","start","end","e","time","events","Tempo","clockTime","tempo","ticks","tickDelta","ticksPerSecond","clockDelta","secondsPerTick","generateUniqueNumber","map","nextId","WebAudioWrapper","webAudioWrapper","SCHEDULED_TIMEOUT_FUNCTIONS","SCHEDULED_INTERVAL_FUNCTIONS","callIntervalFunction","id","type","functions","SCHEDULED_INTERVAL_FUNCTIONS","SCHEDULED_TIMEOUT_FUNCTIONS","func","scheduleFunction","delay","now","audioBufferSourceNode","webAudioWrapper","elapsedTime","isNode","acClearInterval","acSetInterval","func","delay","id","generateUniqueNumber","SCHEDULED_INTERVAL_FUNCTIONS","scheduleFunction","exportedClearInterval","isNode","acClearInterval","exportedSetInterval","isNode","acSetInterval","Timer","callback","intervalMs","exportedSetInterval","exportedClearInterval","TransportState","Transport","Tempo","context","listener","Clock","Scheduler","Timer","time","callback","currentBar","pos","clockTime","ticks","Position","position","event","actionAt","bpm","value","amount","start","end","transportStart","transportEnd","swing","contextTime","events"]}
package/dist/index.d.cts DELETED
@@ -1,145 +0,0 @@
1
- import { Context } from '@blibliki/utils';
2
-
3
- type Seconds = number;
4
- type ClockTime = number;
5
- type ContextTime = number;
6
- type Ticks = number;
7
- type BPM = number;
8
- type TimeSignature = [number, TimeSignatureDenominator];
9
- type TimeSignatureDenominator = 2 | 4 | 8 | 16;
10
- type TPosition = {
11
- bars: number;
12
- beats: number;
13
- sixteenths: number;
14
- };
15
- type TStringPosition = `${number}:${number}:${number}`;
16
- /**
17
- * A number that is between [0, 1]
18
- */
19
- type NormalRange = number;
20
-
21
- /**
22
- * Represents a musical position that can be expressed in multiple formats:
23
- * - Ticks (raw MIDI time units)
24
- * - Bars:Beats:Sixteenths (string format like "1:2:8")
25
- * - Object format with bars, beats, and sixteenths properties
26
- */
27
- declare class Position {
28
- private readonly _ticks;
29
- private timeSignature;
30
- /**
31
- * Creates a new Position instance
32
- * @param value - Position value in ticks, string format ("bars:beats:sixteenths"), or object format
33
- * @param timeSignature - Time signature as [numerator, denominator] (e.g., [4, 4] for 4/4)
34
- */
35
- constructor(value: Ticks | TPosition | TStringPosition, timeSignature: TimeSignature);
36
- get ticks(): number;
37
- get bars(): number;
38
- get beats(): number;
39
- get sixteenths(): number;
40
- get totalBeats(): number;
41
- get beatFraction(): number;
42
- toString(): string;
43
- toObject(): TPosition;
44
- private parseStringPosition;
45
- private convertObjectToTicks;
46
- }
47
-
48
- type TimelineEvent = {
49
- time: ClockTime;
50
- };
51
-
52
- interface TransportEvent extends TimelineEvent {
53
- ticks: Ticks;
54
- contextTime: ContextTime;
55
- }
56
- /**
57
- * Given a (future) transport time window (in ticks), return all events that should
58
- * occur within the window. This function is responsible for setting the ticks
59
- * value for the events returned.
60
- *
61
- * IMPORTANT: Subsequent calls to this function may have overlapping time windows.
62
- * Be careful to not return the same event more than once.
63
- */
64
- type TransportEventGenerator<T extends TransportEvent> = (start: Ticks, end: Ticks) => readonly Readonly<T>[];
65
- /**
66
- * Schedule the specified event with the audio system. The transport class is
67
- * responsible for setting the `contextTime` of the events.
68
- */
69
- type TransportEventConsumer<T extends TransportEvent> = (event: Readonly<T>) => void;
70
- type TransportListener<T extends TransportEvent> = {
71
- generator: TransportEventGenerator<T>;
72
- consumer: TransportEventConsumer<T>;
73
- onJump: (ticks: Ticks) => void;
74
- onStart: (contextTime: ContextTime) => void;
75
- onStop: (contextTime: ContextTime) => void;
76
- silence: (contextTime: ContextTime) => void;
77
- };
78
- /**
79
- * Transport callback that gets invoked at (roughly) sixteenth note intervals.
80
- *
81
- * Returns the current clock time in seconds.
82
- *
83
- * IMPORTANT! Do not rely on this callback for audio precision mechanisms!
84
- * It is only accurate up to the precision of the event sheduler rate, and may
85
- * "jump" if the scheduling is struggling to keep up.
86
- */
87
- type TransportClockCallback = (time: ClockTime) => void;
88
- declare enum TransportState {
89
- playing = "playing",
90
- stopped = "stopped",
91
- paused = "paused"
92
- }
93
- /**
94
- * This class converts (music) transport time into audio clock time.
95
- */
96
- declare class Transport<T extends TransportEvent> {
97
- private _initialized;
98
- private context;
99
- private clock;
100
- private timer;
101
- private scheduler;
102
- private _timeSignature;
103
- private tempo;
104
- private clockTime;
105
- private _swingAmount;
106
- private listener;
107
- private _clockCallbacks;
108
- constructor(context: Readonly<Context>, listener: Readonly<TransportListener<T>>);
109
- addClockCallback(callback: TransportClockCallback): void;
110
- addBarCallback(callback: (bar: number) => void): void;
111
- get time(): number;
112
- /**
113
- * Return the (approximate) current Transport time, in ticks.
114
- */
115
- get position(): Readonly<Position>;
116
- /**
117
- * Set the current Transport time.
118
- */
119
- set position(position: Readonly<Position>);
120
- getPositionOfEvent(event: Readonly<TransportEvent>): Readonly<Position>;
121
- /**
122
- * Start the Transport.
123
- */
124
- start(): void;
125
- /**
126
- * Stop the Transport.
127
- */
128
- stop(): void;
129
- /**
130
- * Reset the Transport to zero.
131
- */
132
- reset(): void;
133
- get bpm(): BPM;
134
- set bpm(bpm: BPM);
135
- get timeSignature(): TimeSignature;
136
- set timeSignature(value: TimeSignature);
137
- get state(): TransportState;
138
- get swingAmount(): NormalRange;
139
- set swingAmount(amount: NormalRange);
140
- private jumpTo;
141
- private generateEvents;
142
- private consumeEvents;
143
- }
144
-
145
- export { type BPM, type ClockTime, type ContextTime, Position, type Seconds, type Ticks, type TimeSignature, Transport, type TransportEvent, TransportState };