misairu 5.0.0 → 5.0.1
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/misairu.d.ts +6 -44
- package/dist/misairu.iife.js +1 -1
- package/dist/misairu.iife.js.map +1 -1
- package/dist/misairu.js +1 -1
- package/dist/misairu.js.map +1 -1
- package/dist/misairu.modern.js +1 -1
- package/dist/misairu.modern.js.map +1 -1
- package/dist/misairu.module.js +1 -1
- package/dist/misairu.module.js.map +1 -1
- package/dist/misairu.umd.js +1 -1
- package/dist/misairu.umd.js.map +1 -1
- package/dist/processors/repeat.d.ts +6 -0
- package/dist/types.d.ts +23 -0
- package/dist/utilities/audio.d.ts +11 -0
- package/dist/utilities/source.d.ts +15 -0
- package/package.json +4 -4
- package/src/misairu.ts +39 -122
- package/src/processors/repeat.ts +35 -0
- package/src/types.ts +26 -0
- package/src/utilities/audio.ts +22 -0
- package/src/utilities/source.ts +28 -0
- package/.github/FUNDING.yml +0 -1
- package/.github/workflows/lint.yml +0 -17
package/dist/misairu.d.ts
CHANGED
|
@@ -7,12 +7,6 @@ export declare class Misairu {
|
|
|
7
7
|
* The HTML5 AudioContext used for accurately timing our events
|
|
8
8
|
*/
|
|
9
9
|
private readonly _audioContext;
|
|
10
|
-
/**
|
|
11
|
-
* If the audio source is a HTML5 media element (either a <audio> or <video> tag), this will be a reference to it
|
|
12
|
-
*
|
|
13
|
-
* @default null
|
|
14
|
-
*/
|
|
15
|
-
private _audioElement;
|
|
16
10
|
/**
|
|
17
11
|
* A source node to play our audio from, either a buffered source from a downloaded media file or a media element source
|
|
18
12
|
*
|
|
@@ -55,6 +49,10 @@ export declare class Misairu {
|
|
|
55
49
|
* Volume of the current instance
|
|
56
50
|
*/
|
|
57
51
|
private _volume;
|
|
52
|
+
/**
|
|
53
|
+
* List of (predefined) track processors
|
|
54
|
+
*/
|
|
55
|
+
private _processors;
|
|
58
56
|
get volume(): number;
|
|
59
57
|
set volume(db: number);
|
|
60
58
|
/**
|
|
@@ -71,21 +69,6 @@ export declare class Misairu {
|
|
|
71
69
|
* @internal
|
|
72
70
|
*/
|
|
73
71
|
private getOptimalAudioSource;
|
|
74
|
-
/**
|
|
75
|
-
* Method to fetch the external audio file (if the audio source parameter was a string)
|
|
76
|
-
* and turning it into a `AudioBufferSourceNode`
|
|
77
|
-
*
|
|
78
|
-
* @param audioSource the audio source `Misairu` has been constructed with
|
|
79
|
-
* @internal
|
|
80
|
-
*/
|
|
81
|
-
private fetchAudioSource;
|
|
82
|
-
/**
|
|
83
|
-
* Method to get an `MediaElementAudioSourceNode` from the passed audio source
|
|
84
|
-
*
|
|
85
|
-
* @param audioSource the audio source `Misairu` has been constructed with
|
|
86
|
-
* @internal
|
|
87
|
-
*/
|
|
88
|
-
private attachAudioElementSource;
|
|
89
72
|
/**
|
|
90
73
|
* Mutes the instance audio
|
|
91
74
|
*
|
|
@@ -110,20 +93,6 @@ export declare class Misairu {
|
|
|
110
93
|
* @public
|
|
111
94
|
*/
|
|
112
95
|
unpause(): void;
|
|
113
|
-
/**
|
|
114
|
-
* Clamps the volume to a min/max value to prevent accidental oversetting to way too loud measures
|
|
115
|
-
*
|
|
116
|
-
* @internal
|
|
117
|
-
*/
|
|
118
|
-
private clampGain;
|
|
119
|
-
/**
|
|
120
|
-
* Method to turn the passed decibel values into volume values for the audio playback
|
|
121
|
-
*
|
|
122
|
-
* @param db decibel value
|
|
123
|
-
* @returns volume value
|
|
124
|
-
* @internal
|
|
125
|
-
*/
|
|
126
|
-
private dbToVolume;
|
|
127
96
|
/**
|
|
128
97
|
* Set a cache entry for the given track
|
|
129
98
|
*
|
|
@@ -157,18 +126,11 @@ export declare class Misairu {
|
|
|
157
126
|
*/
|
|
158
127
|
private getActiveTimingKey;
|
|
159
128
|
/**
|
|
160
|
-
* Main method to
|
|
161
|
-
*
|
|
162
|
-
* @internal
|
|
163
|
-
*/
|
|
164
|
-
private compile;
|
|
165
|
-
/**
|
|
166
|
-
* Method to compile tracks whose name starts with `repeat:`
|
|
129
|
+
* Main method to process special sections in the timing configuration
|
|
167
130
|
*
|
|
168
|
-
* @param track track to compile the repeat method for
|
|
169
131
|
* @internal
|
|
170
132
|
*/
|
|
171
|
-
private
|
|
133
|
+
private processTracks;
|
|
172
134
|
/**
|
|
173
135
|
* Start audio playback and event handling
|
|
174
136
|
*
|
package/dist/misairu.iife.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var Misairu=function(){function t(t,e){this._audioContext=void 0,this.
|
|
1
|
+
var Misairu=function(){var t=new RegExp(/repeat:\d:\d:\d/),e=function(){function e(){this.deleteOriginTrack=!0}var n=e.prototype;return n.matches=function(e){return null!==e.match(t)},n.process=function(t,e){if("function"!=typeof e)throw Error('The value of repeat track "'+e+'" is not a function');var n=t.split(":");if(4!=n.length)throw Error('The repeat track "'+t+'" does not supply the valid amount of arguments');var i=parseFloat(n[1]),o=parseFloat(n[2]),r=parseFloat(n[3]),s=i,a={};do{a[s.toString()]=e,s+=o}while(s<r);return["repeat-"+Math.random().toString(36).substring(7),a]},e}();function n(t){return Math.pow(10,t/20)}return function(){function t(t,n){this._audioContext=void 0,this._audioSource=null,this._cache={},this._eventHandler=null,this._gainNode=void 0,this._muted=!1,this._paused=!1,this._startTime=0,this._timings=void 0,this._volume=0,this._processors=[new e],null===n&&console.error("You need to specify a timings object"),this._timings=n,this._audioContext=new AudioContext,this._gainNode=this._audioContext.createGain(),this._gainNode.connect(this._audioContext.destination),this.getOptimalAudioSource(t),this.processTracks()}var i,o=t.prototype;return o.getOptimalAudioSource=function(t){var e=this;"string"==typeof t?function(t,e){try{var n=e.createBufferSource();return Promise.resolve(fetch(t)).then(function(t){return Promise.resolve(t.arrayBuffer()).then(function(t){return Promise.resolve(e.decodeAudioData(t)).then(function(t){return n.buffer=t,n})})})}catch(t){return Promise.reject(t)}}(t,this._audioContext).then(function(t){e._audioSource=t,e._audioSource.connect(e._gainNode),document.dispatchEvent(new Event("misairu.ready"))}):"object"!=typeof t||"AUDIO"!=t.tagName&&"VIDEO"!=t.tagName||(this._audioSource=function(t,e){return e.createMediaElementSource(t)}(t,this._audioContext),this._audioSource.connect(this._gainNode))},o.mute=function(){this._muted||(this._muted=!0,this._gainNode.gain.value=0)},o.unmute=function(){this._muted&&(this._muted=!1,this._gainNode.gain.value=n(this._volume))},o.pause=function(){this._paused||(this._audioContext.suspend(),this._paused=!0)},o.unpause=function(){this._paused&&(this._audioContext.resume(),this._paused=!1)},o.setCacheEntry=function(t,e){this._cache[t]=e},o.getCacheEntry=function(t){return this._cache[t]},o.getAllTracks=function(){return Object.keys(this._timings)},o.getActiveTimingKey=function(t,e){var n=Object.keys(this._timings[t]).filter(function(t){if(e>=parseFloat(t))return!0});return n[n.length-1]},o.processTracks=function(){var t=this;this.getAllTracks().forEach(function(e){t._processors.forEach(function(n){if(n.matches(e)){var i=n.process(e,t._timings[e]);t._timings[i[0]]=i[1],n.deleteOriginTrack&&delete t._timings[e]}})})},o.start=function(){this._startTime=this._audioContext.currentTime,this._audioSource.start(),this.startEventHandling()},o.startEventHandling=function(){var t=this;null==this._eventHandler&&(this._eventHandler=window.requestAnimationFrame(function(){t.handleEvents()}))},o.handleEvents=function(){var t=this,e=this._audioContext.currentTime-this._startTime;this.getAllTracks().forEach(function(n){var i=t.getActiveTimingKey(n,e);null!==i&&t.getCacheEntry(n)!=i&&(t.executeEvent(n,i,e),t.setCacheEntry(n,i))}),this._eventHandler=window.requestAnimationFrame(function(){t.handleEvents()})},o.executeEvent=function(t,e,n){this._timings[t][e](this,e,t,n)},(i=[{key:"volume",get:function(){return this._volume},set:function(t){var e;this._volume=(e=t)<-80?-80:e>5?5:e,this._muted||(this._gainNode.gain.value=n(this._volume))}}])&&function(t,e){for(var n=0;n<e.length;n++){var i=e[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,i.key,i)}}(t.prototype,i),t}()}();
|
|
2
2
|
//# sourceMappingURL=misairu.iife.js.map
|
package/dist/misairu.iife.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"misairu.iife.js","sources":["../src/misairu.ts"],"sourcesContent":["import { EventCache, TimingObject } from './types'\r\n\r\n/**\r\n * Main misairu class\r\n */\r\nexport class Misairu {\r\n /**\r\n * The HTML5 AudioContext used for accurately timing our events\r\n */\r\n private readonly _audioContext: AudioContext\r\n\r\n /**\r\n * If the audio source is a HTML5 media element (either a <audio> or <video> tag), this will be a reference to it\r\n *\r\n * @default null\r\n */\r\n private _audioElement: HTMLMediaElement | null = null\r\n\r\n /**\r\n * A source node to play our audio from, either a buffered source from a downloaded media file or a media element source\r\n *\r\n * @default null\r\n */\r\n private _audioSource: AudioBufferSourceNode | MediaElementAudioSourceNode | null = null\r\n\r\n /**\r\n * A object containing the last executed time key per track to not execute an event on every tick\r\n *\r\n * @default {}\r\n */\r\n private _cache: EventCache = {}\r\n\r\n /**\r\n * Reference to the `requestAnimationFrame` handler\r\n *\r\n * @default null\r\n */\r\n private _eventHandler: number | null = null\r\n\r\n /**\r\n * Gain node from our audio context to control audio volume\r\n */\r\n private readonly _gainNode: GainNode\r\n\r\n /**\r\n * Boolean value describing if this instance is currently muted\r\n */\r\n private _muted = false\r\n\r\n /**\r\n * Boolean value describing if this instance is currently paused\r\n */\r\n private _paused = false\r\n\r\n /**\r\n * The time when event handling was started, based on the audio contexts `currentTime` when `start()` was called\r\n */\r\n private _startTime = 0\r\n\r\n /**\r\n * Object containing all timing tracks and events to be executed\r\n */\r\n private _timings: TimingObject\r\n\r\n /**\r\n * Volume of the current instance\r\n */\r\n private _volume = 0\r\n\r\n get volume(): number {\r\n return this._volume\r\n }\r\n\r\n set volume(db: number) {\r\n this._volume = db\r\n this.clampGain()\r\n\r\n if (!this._muted) {\r\n this._gainNode.gain.value = this.dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * misairu constructor\r\n *\r\n * @param audioSource a string or HTML element to be used as audio source\r\n * @param timings a object containing event timing information\r\n */\r\n constructor(audioSource: string | HTMLMediaElement, timings: TimingObject) {\r\n if (timings === null) console.error('You need to specify a timings object')\r\n this._timings = timings\r\n\r\n this._audioContext = new AudioContext()\r\n\r\n this._gainNode = this._audioContext.createGain()\r\n this._gainNode.connect(this._audioContext.destination)\r\n\r\n this.getOptimalAudioSource(audioSource)\r\n\r\n this.compile()\r\n }\r\n\r\n /**\r\n * Method to figure out the best course of action to take with the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private getOptimalAudioSource(audioSource: string | HTMLMediaElement): void {\r\n if (typeof audioSource == 'string') {\r\n this.fetchAudioSource(audioSource)\r\n } else if (\r\n typeof audioSource == 'object' &&\r\n (audioSource.tagName == 'AUDIO' || audioSource.tagName == 'VIDEO')\r\n ) {\r\n this.attachAudioElementSource(audioSource)\r\n }\r\n }\r\n\r\n /**\r\n * Method to fetch the external audio file (if the audio source parameter was a string)\r\n * and turning it into a `AudioBufferSourceNode`\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private fetchAudioSource(audioSource: string): void {\r\n const source = this._audioContext.createBufferSource()\r\n\r\n fetch(audioSource)\r\n .then((response) => {\r\n return response.arrayBuffer()\r\n })\r\n .then((arrayBuffer) => {\r\n this._audioContext.decodeAudioData(arrayBuffer).then((buffer) => {\r\n source.buffer = buffer\r\n source.connect(this._gainNode)\r\n\r\n document.dispatchEvent(new Event('misairu.ready'))\r\n this._audioSource = source\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Method to get an `MediaElementAudioSourceNode` from the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private attachAudioElementSource(audioSource: HTMLMediaElement): void {\r\n const source = this._audioContext.createMediaElementSource(audioSource)\r\n this._audioElement = audioSource\r\n\r\n source.connect(this._gainNode)\r\n this._audioSource = source\r\n }\r\n\r\n /**\r\n * Mutes the instance audio\r\n *\r\n * @public\r\n */\r\n public mute(): void {\r\n if (!this._muted) {\r\n this._muted = true\r\n this._gainNode.gain.value = 0\r\n }\r\n }\r\n\r\n /**\r\n * Unmutes the instance audio\r\n *\r\n * @public\r\n */\r\n public unmute(): void {\r\n if (this._muted) {\r\n this._muted = false\r\n this._gainNode.gain.value = this.dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * Pauses instance playback\r\n *\r\n * @public\r\n */\r\n public pause(): void {\r\n if (!this._paused) {\r\n this._audioContext.suspend()\r\n this._paused = true\r\n }\r\n }\r\n\r\n /**\r\n * Resumes instance playback\r\n *\r\n * @public\r\n */\r\n public unpause(): void {\r\n if (this._paused) {\r\n this._audioContext.resume()\r\n this._paused = false\r\n }\r\n }\r\n\r\n /**\r\n * Clamps the volume to a min/max value to prevent accidental oversetting to way too loud measures\r\n *\r\n * @internal\r\n */\r\n private clampGain(): void {\r\n if (this._volume < -80) {\r\n this._volume = -80\r\n } else if (this._volume > 5) {\r\n this._volume = 5\r\n }\r\n }\r\n\r\n /**\r\n * Method to turn the passed decibel values into volume values for the audio playback\r\n *\r\n * @param db decibel value\r\n * @returns volume value\r\n * @internal\r\n */\r\n private dbToVolume(db: number): number {\r\n return Math.pow(10, db / 20)\r\n }\r\n\r\n /**\r\n * Set a cache entry for the given track\r\n *\r\n * @param track track to set a cache entry for\r\n * @param entry value of the cache entry\r\n * @internal\r\n */\r\n private setCacheEntry(track: string, entry: string): void {\r\n this._cache[track] = entry\r\n }\r\n\r\n /**\r\n * Get cache entry for the given track\r\n *\r\n * @param track track to get a cache entry for\r\n * @returns a cache entry\r\n * @internal\r\n */\r\n private getCacheEntry(track: string): string {\r\n return this._cache[track]\r\n }\r\n\r\n /**\r\n * Get all tracks from the timing configuration\r\n *\r\n * @returns a list of all track names\r\n * @internal\r\n */\r\n private getAllTracks(): string[] {\r\n return Object.keys(this._timings)\r\n }\r\n\r\n /**\r\n * Returns the current active timing key for a given track\r\n *\r\n * @param track track to get the timing key from\r\n * @param currentTime current playback time\r\n * @returns the current active timing key\r\n * @internal\r\n */\r\n private getActiveTimingKey(track: string, currentTime: number): string {\r\n const timingKeys = Object.keys(this._timings[track])\r\n\r\n const activeTimings = timingKeys.filter((timing) => {\r\n if (currentTime >= parseFloat(timing)) {\r\n return true\r\n }\r\n })\r\n\r\n return activeTimings[activeTimings.length - 1]\r\n }\r\n\r\n /**\r\n * Main method to compile special sections in the timing configuration\r\n *\r\n * @internal\r\n */\r\n private compile(): void {\r\n this.getAllTracks().forEach((track) => {\r\n if (track.startsWith('repeat:')) {\r\n this.compileRepeat(track)\r\n }\r\n })\r\n }\r\n\r\n /**\r\n * Method to compile tracks whose name starts with `repeat:`\r\n *\r\n * @param track track to compile the repeat method for\r\n * @internal\r\n */\r\n private compileRepeat(track: string): void {\r\n if (typeof this._timings[track] != 'function')\r\n throw Error(`The value of repeat track \"${track}\" is not a function`)\r\n\r\n const repeatTrackArgs = track.split(':')\r\n\r\n if (repeatTrackArgs.length != 4)\r\n throw Error(`The repeat track \"${track}\" does not supply the valid amount of arguments`)\r\n\r\n const startTime = parseFloat(repeatTrackArgs[1])\r\n const interval = parseFloat(repeatTrackArgs[2])\r\n const endTime = parseFloat(repeatTrackArgs[3])\r\n\r\n let time = startTime\r\n const tempTrack = {}\r\n\r\n do {\r\n tempTrack[time.toString()] = this._timings[track]\r\n time += interval\r\n } while (time < endTime)\r\n\r\n delete this._timings[track]\r\n\r\n this._timings[`repeat-${Math.random().toString(36).substring(7)}`] = tempTrack\r\n }\r\n\r\n /**\r\n * Start audio playback and event handling\r\n *\r\n * @public\r\n */\r\n public start(): void {\r\n this._startTime = this._audioContext.currentTime\r\n\r\n if (this._audioElement !== null) {\r\n this._audioElement.play()\r\n } else {\r\n ;(this._audioSource as AudioBufferSourceNode).start()\r\n }\r\n\r\n this.startEventHandling()\r\n }\r\n\r\n /**\r\n * Method to start event handler loop\r\n *\r\n * @internal\r\n */\r\n private startEventHandling(): void {\r\n if (this._eventHandler == null) {\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Event handler method, is running in a loop using `requestAnimationFrame`\r\n *\r\n * @internal\r\n */\r\n private handleEvents(): void {\r\n const time = this._audioContext.currentTime - this._startTime\r\n\r\n this.getAllTracks().forEach((track) => {\r\n const timingKey = this.getActiveTimingKey(track, time)\r\n\r\n if (timingKey !== null && !(this.getCacheEntry(track) == timingKey)) {\r\n this.executeEvent(track, timingKey, time)\r\n this.setCacheEntry(track, timingKey)\r\n }\r\n })\r\n\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n\r\n /**\r\n * Method to execute the event for a given timing key on a given track\r\n *\r\n * @param track the track to execute the event on\r\n * @param timingKey the timing key to execute\r\n * @param time current playback time\r\n * @internal\r\n */\r\n private executeEvent(track: string, timingKey: string, time: number): void {\r\n this._timings[track][timingKey](this, timingKey, track, time)\r\n }\r\n}\r\n"],"names":["audioSource","timings","_audioContext","_audioElement","_audioSource","_cache","_eventHandler","_gainNode","_muted","_paused","_startTime","_timings","_volume","console","error","this","AudioContext","createGain","connect","destination","getOptimalAudioSource","compile","fetchAudioSource","tagName","attachAudioElementSource","source","createBufferSource","fetch","then","response","arrayBuffer","_this","decodeAudioData","buffer","document","dispatchEvent","Event","createMediaElementSource","mute","gain","value","unmute","dbToVolume","pause","suspend","unpause","resume","clampGain","db","Math","pow","setCacheEntry","track","entry","getCacheEntry","getAllTracks","Object","keys","getActiveTimingKey","currentTime","activeTimings","filter","timing","parseFloat","length","forEach","startsWith","_this2","compileRepeat","Error","repeatTrackArgs","split","startTime","interval","endTime","time","tempTrack","toString","random","substring","start","play","startEventHandling","window","requestAnimationFrame","_this3","handleEvents","timingKey","_this4","executeEvent"],"mappings":"uBAwFE,WAAYA,EAAwCC,QA/EnCC,0BAOTC,cAAyC,UAOzCC,aAA2E,UAO3EC,OAAqB,QAOrBC,cAA+B,UAKtBC,sBAKTC,QAAS,OAKTC,SAAU,OAKVC,WAAa,OAKbC,qBAKAC,QAAU,EAsBA,OAAZX,GAAkBY,QAAQC,MAAM,wCACpCC,KAAKJ,SAAWV,EAEhBc,KAAKb,cAAgB,IAAIc,aAEzBD,KAAKR,UAAYQ,KAAKb,cAAce,aACpCF,KAAKR,UAAUW,QAAQH,KAAKb,cAAciB,aAE1CJ,KAAKK,sBAAsBpB,GAE3Be,KAAKM,UA9FT,6BAuGUD,sBAAA,SAAsBpB,GACF,iBAAfA,EACTe,KAAKO,iBAAiBtB,GAEA,iBAAfA,GACiB,SAAvBA,EAAYuB,SAA6C,SAAvBvB,EAAYuB,SAE/CR,KAAKS,yBAAyBxB,MAW1BsB,iBAAA,SAAiBtB,cACjByB,EAASV,KAAKb,cAAcwB,qBAElCC,MAAM3B,GACH4B,KAAK,SAACC,GACL,OAAOA,EAASC,gBAEjBF,KAAK,SAACE,GACLC,EAAK7B,cAAc8B,gBAAgBF,GAAaF,KAAK,SAACK,GACpDR,EAAOQ,OAASA,EAChBR,EAAOP,QAAQa,EAAKxB,WAEpB2B,SAASC,cAAc,IAAIC,MAAM,kBACjCL,EAAK3B,aAAeqB,SAWpBD,yBAAA,SAAyBxB,GAC/B,IAAMyB,EAASV,KAAKb,cAAcmC,yBAAyBrC,GAC3De,KAAKZ,cAAgBH,EAErByB,EAAOP,QAAQH,KAAKR,WACpBQ,KAAKX,aAAeqB,KAQfa,KAAA,WACAvB,KAAKP,SACRO,KAAKP,QAAS,EACdO,KAAKR,UAAUgC,KAAKC,MAAQ,MASzBC,OAAA,WACD1B,KAAKP,SACPO,KAAKP,QAAS,EACdO,KAAKR,UAAUgC,KAAKC,MAAQzB,KAAK2B,WAAW3B,KAAKH,aAS9C+B,MAAA,WACA5B,KAAKN,UACRM,KAAKb,cAAc0C,UACnB7B,KAAKN,SAAU,MASZoC,QAAA,WACD9B,KAAKN,UACPM,KAAKb,cAAc4C,SACnB/B,KAAKN,SAAU,MASXsC,UAAA,WACFhC,KAAKH,SAAW,GAClBG,KAAKH,SAAW,GACPG,KAAKH,QAAU,IACxBG,KAAKH,QAAU,MAWX8B,WAAA,SAAWM,GACjB,OAAOC,KAAKC,IAAI,GAAIF,EAAK,OAUnBG,cAAA,SAAcC,EAAeC,GACnCtC,KAAKV,OAAO+C,GAASC,KAUfC,cAAA,SAAcF,GACpB,YAAY/C,OAAO+C,MASbG,aAAA,WACN,OAAOC,OAAOC,KAAK1C,KAAKJ,aAWlB+C,mBAAA,SAAmBN,EAAeO,GACxC,IAEMC,EAFaJ,OAAOC,KAAK1C,KAAKJ,SAASyC,IAEZS,OAAO,SAACC,GACvC,GAAIH,GAAeI,WAAWD,GAC5B,WAIJ,OAAOF,EAAcA,EAAcI,OAAS,MAQtC3C,QAAA,sBACNN,KAAKwC,eAAeU,QAAQ,SAACb,GACvBA,EAAMc,WAAW,YACnBC,EAAKC,cAAchB,QAWjBgB,cAAA,SAAchB,GACpB,GAAmC,wBAAnBzC,SAASyC,GACvB,MAAMiB,oCAAoCjB,yBAE5C,IAAMkB,EAAkBlB,EAAMmB,MAAM,KAEpC,GAA8B,GAA1BD,EAAgBN,OAClB,MAAMK,2BAA2BjB,qDAEnC,IAAMoB,EAAYT,WAAWO,EAAgB,IACvCG,EAAWV,WAAWO,EAAgB,IACtCI,EAAUX,WAAWO,EAAgB,IAEvCK,EAAOH,EACLI,EAAY,GAElB,GACEA,EAAUD,EAAKE,YAAc9D,KAAKJ,SAASyC,GAC3CuB,GAAQF,QACDE,EAAOD,eAEJ/D,SAASyC,GAErBrC,KAAKJ,mBAAmBsC,KAAK6B,SAASD,SAAS,IAAIE,UAAU,IAAQH,KAQhEI,MAAA,WACLjE,KAAKL,WAAaK,KAAKb,cAAcyD,YAEV,OAAvB5C,KAAKZ,cACPY,KAAKZ,cAAc8E,OAEjBlE,KAAKX,aAAuC4E,QAGhDjE,KAAKmE,wBAQCA,mBAAA,sBACoB,MAAtBnE,KAAKT,gBACPS,KAAKT,cAAgB6E,OAAOC,sBAAsB,WAChDC,EAAKC,qBAUHA,aAAA,sBACAX,EAAO5D,KAAKb,cAAcyD,YAAc5C,KAAKL,WAEnDK,KAAKwC,eAAeU,QAAQ,SAACb,GAC3B,IAAMmC,EAAYC,EAAK9B,mBAAmBN,EAAOuB,GAE/B,OAAdY,GAAwBC,EAAKlC,cAAcF,IAAUmC,IACvDC,EAAKC,aAAarC,EAAOmC,EAAWZ,GACpCa,EAAKrC,cAAcC,EAAOmC,MAI9BxE,KAAKT,cAAgB6E,OAAOC,sBAAsB,WAChDI,EAAKF,oBAYDG,aAAA,SAAarC,EAAemC,EAAmBZ,GACrD5D,KAAKJ,SAASyC,GAAOmC,GAAWxE,KAAMwE,EAAWnC,EAAOuB,0BA/T1D,WACE,YAAY/D,aAGd,SAAWoC,GACTjC,KAAKH,QAAUoC,EACfjC,KAAKgC,YAEAhC,KAAKP,SACRO,KAAKR,UAAUgC,KAAKC,MAAQzB,KAAK2B,WAAW3B,KAAKH"}
|
|
1
|
+
{"version":3,"file":"misairu.iife.js","sources":["../src/processors/repeat.ts","../src/utilities/audio.ts","../src/misairu.ts","../src/utilities/source.ts"],"sourcesContent":["import { EventFunction, EventTrack, ITrackProcessor } from \"../types\";\r\n\r\nconst REPEAT_NAME_REGEX = new RegExp(/repeat:\\d:\\d:\\d/)\r\n\r\nexport class RepeatTrackProcessor implements ITrackProcessor {\r\n deleteOriginTrack = true;\r\n\r\n matches(name: string): boolean {\r\n return name.match(REPEAT_NAME_REGEX) !== null\r\n }\r\n\r\n process(name: string, track: EventFunction): [string, EventTrack] {\r\n if (typeof track != 'function')\r\n throw Error(`The value of repeat track \"${track}\" is not a function`)\r\n\r\n const repeatTrackArgs = name.split(':')\r\n\r\n if (repeatTrackArgs.length != 4)\r\n throw Error(`The repeat track \"${name}\" does not supply the valid amount of arguments`)\r\n\r\n const startTime = parseFloat(repeatTrackArgs[1])\r\n const interval = parseFloat(repeatTrackArgs[2])\r\n const endTime = parseFloat(repeatTrackArgs[3])\r\n\r\n let time = startTime\r\n const tempTrack = {}\r\n\r\n do {\r\n tempTrack[time.toString()] = track\r\n time += interval\r\n } while (time < endTime)\r\n\r\n return [`repeat-${Math.random().toString(36).substring(7)}`, tempTrack]\r\n }\r\n}","/**\r\n * Method to turn the passed decibel values into volume values for the audio playback\r\n *\r\n * @param db decibel value\r\n * @returns volume value\r\n */\r\nexport function dbToVolume(db: number): number {\r\n return Math.pow(10, db / 20)\r\n}\r\n\r\n/**\r\n * Clamps the volume to a min/max value to prevent accidental oversetting to way too loud measures\r\n */\r\nexport function clampGain(volume: number): number {\r\n if (volume < -80) {\r\n return -80\r\n } else if (volume > 5) {\r\n return 5\r\n }\r\n\r\n return volume\r\n}","import { RepeatTrackProcessor } from './processors/repeat'\r\nimport { EventCache, ITrackProcessor, TimingObject } from './types'\r\nimport { dbToVolume, clampGain } from './utilities/audio'\r\nimport { fetchAudioSource, attachAudioElementSource } from './utilities/source'\r\n\r\n/**\r\n * Main misairu class\r\n */\r\nexport class Misairu {\r\n /**\r\n * The HTML5 AudioContext used for accurately timing our events\r\n */\r\n private readonly _audioContext: AudioContext\r\n\r\n /**\r\n * A source node to play our audio from, either a buffered source from a downloaded media file or a media element source\r\n *\r\n * @default null\r\n */\r\n private _audioSource: HTMLMediaElement | AudioBufferSourceNode | MediaElementAudioSourceNode | null = null\r\n\r\n /**\r\n * A object containing the last executed time key per track to not execute an event on every tick\r\n *\r\n * @default {}\r\n */\r\n private _cache: EventCache = {}\r\n\r\n /**\r\n * Reference to the `requestAnimationFrame` handler\r\n *\r\n * @default null\r\n */\r\n private _eventHandler: number | null = null\r\n\r\n /**\r\n * Gain node from our audio context to control audio volume\r\n */\r\n private readonly _gainNode: GainNode\r\n\r\n /**\r\n * Boolean value describing if this instance is currently muted\r\n */\r\n private _muted = false\r\n\r\n /**\r\n * Boolean value describing if this instance is currently paused\r\n */\r\n private _paused = false\r\n\r\n /**\r\n * The time when event handling was started, based on the audio contexts `currentTime` when `start()` was called\r\n */\r\n private _startTime = 0\r\n\r\n /**\r\n * Object containing all timing tracks and events to be executed\r\n */\r\n private _timings: TimingObject\r\n\r\n /**\r\n * Volume of the current instance\r\n */\r\n private _volume = 0\r\n\r\n /**\r\n * List of (predefined) track processors\r\n */\r\n private _processors: ITrackProcessor[] = [\r\n new RepeatTrackProcessor()\r\n ]\r\n\r\n get volume(): number {\r\n return this._volume\r\n }\r\n\r\n set volume(db: number) {\r\n this._volume = clampGain(db)\r\n\r\n if (!this._muted) {\r\n this._gainNode.gain.value = dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * misairu constructor\r\n *\r\n * @param audioSource a string or HTML element to be used as audio source\r\n * @param timings a object containing event timing information\r\n */\r\n constructor(audioSource: string | HTMLMediaElement, timings: TimingObject) {\r\n if (timings === null) console.error('You need to specify a timings object')\r\n this._timings = timings\r\n\r\n this._audioContext = new AudioContext()\r\n\r\n this._gainNode = this._audioContext.createGain()\r\n this._gainNode.connect(this._audioContext.destination)\r\n\r\n this.getOptimalAudioSource(audioSource)\r\n\r\n this.processTracks()\r\n }\r\n\r\n /**\r\n * Method to figure out the best course of action to take with the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private getOptimalAudioSource(audioSource: string | HTMLMediaElement): void {\r\n if (typeof audioSource == 'string') {\r\n fetchAudioSource(audioSource, this._audioContext).then((audioSource) => {\r\n this._audioSource = audioSource\r\n this._audioSource.connect(this._gainNode)\r\n\r\n document.dispatchEvent(new Event('misairu.ready'))\r\n })\r\n } else if (\r\n typeof audioSource == 'object' &&\r\n (audioSource.tagName == 'AUDIO' || audioSource.tagName == 'VIDEO')\r\n ) {\r\n this._audioSource = attachAudioElementSource(audioSource, this._audioContext)\r\n this._audioSource.connect(this._gainNode)\r\n }\r\n }\r\n\r\n /**\r\n * Mutes the instance audio\r\n *\r\n * @public\r\n */\r\n public mute(): void {\r\n if (!this._muted) {\r\n this._muted = true\r\n this._gainNode.gain.value = 0\r\n }\r\n }\r\n\r\n /**\r\n * Unmutes the instance audio\r\n *\r\n * @public\r\n */\r\n public unmute(): void {\r\n if (this._muted) {\r\n this._muted = false\r\n this._gainNode.gain.value = dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * Pauses instance playback\r\n *\r\n * @public\r\n */\r\n public pause(): void {\r\n if (!this._paused) {\r\n this._audioContext.suspend()\r\n this._paused = true\r\n }\r\n }\r\n\r\n /**\r\n * Resumes instance playback\r\n *\r\n * @public\r\n */\r\n public unpause(): void {\r\n if (this._paused) {\r\n this._audioContext.resume()\r\n this._paused = false\r\n }\r\n }\r\n\r\n /**\r\n * Set a cache entry for the given track\r\n *\r\n * @param track track to set a cache entry for\r\n * @param entry value of the cache entry\r\n * @internal\r\n */\r\n private setCacheEntry(track: string, entry: string): void {\r\n this._cache[track] = entry\r\n }\r\n\r\n /**\r\n * Get cache entry for the given track\r\n *\r\n * @param track track to get a cache entry for\r\n * @returns a cache entry\r\n * @internal\r\n */\r\n private getCacheEntry(track: string): string {\r\n return this._cache[track]\r\n }\r\n\r\n /**\r\n * Get all tracks from the timing configuration\r\n *\r\n * @returns a list of all track names\r\n * @internal\r\n */\r\n private getAllTracks(): string[] {\r\n return Object.keys(this._timings)\r\n }\r\n\r\n /**\r\n * Returns the current active timing key for a given track\r\n *\r\n * @param track track to get the timing key from\r\n * @param currentTime current playback time\r\n * @returns the current active timing key\r\n * @internal\r\n */\r\n private getActiveTimingKey(track: string, currentTime: number): string {\r\n const timingKeys = Object.keys(this._timings[track])\r\n\r\n const activeTimings = timingKeys.filter((timing) => {\r\n if (currentTime >= parseFloat(timing)) {\r\n return true\r\n }\r\n })\r\n\r\n return activeTimings[activeTimings.length - 1]\r\n }\r\n\r\n /**\r\n * Main method to process special sections in the timing configuration\r\n *\r\n * @internal\r\n */\r\n private processTracks(): void {\r\n this.getAllTracks().forEach((trackName) => {\r\n this._processors.forEach((processor: ITrackProcessor) => {\r\n if (processor.matches(trackName)) {\r\n const [processedTrackName, eventTrack] = processor.process(trackName, this._timings[trackName])\r\n\r\n this._timings[processedTrackName] = eventTrack\r\n\r\n if (processor.deleteOriginTrack) {\r\n delete this._timings[trackName]\r\n }\r\n }\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Start audio playback and event handling\r\n *\r\n * @public\r\n */\r\n public start(): void {\r\n this._startTime = this._audioContext.currentTime\r\n\r\n ;(this._audioSource as AudioBufferSourceNode).start()\r\n\r\n this.startEventHandling()\r\n }\r\n\r\n /**\r\n * Method to start event handler loop\r\n *\r\n * @internal\r\n */\r\n private startEventHandling(): void {\r\n if (this._eventHandler == null) {\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Event handler method, is running in a loop using `requestAnimationFrame`\r\n *\r\n * @internal\r\n */\r\n private handleEvents(): void {\r\n const time = this._audioContext.currentTime - this._startTime\r\n\r\n this.getAllTracks().forEach((track) => {\r\n const timingKey = this.getActiveTimingKey(track, time)\r\n\r\n if (timingKey !== null && !(this.getCacheEntry(track) == timingKey)) {\r\n this.executeEvent(track, timingKey, time)\r\n this.setCacheEntry(track, timingKey)\r\n }\r\n })\r\n\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n\r\n /**\r\n * Method to execute the event for a given timing key on a given track\r\n *\r\n * @param track the track to execute the event on\r\n * @param timingKey the timing key to execute\r\n * @param time current playback time\r\n * @internal\r\n */\r\n private executeEvent(track: string, timingKey: string, time: number): void {\r\n this._timings[track][timingKey](this, timingKey, track, time)\r\n }\r\n}\r\n","/**\r\n * Method to fetch the external audio file (if the audio source parameter was a string)\r\n * and turning it into a `AudioBufferSourceNode`\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\nexport async function fetchAudioSource(audioSource: string, audioContext: AudioContext): Promise<AudioBufferSourceNode> {\r\n const source = audioContext.createBufferSource()\r\n\r\n const response = await fetch(audioSource)\r\n const arrayBuffer = await response.arrayBuffer()\r\n const buffer = await audioContext.decodeAudioData(arrayBuffer)\r\n\r\n source.buffer = buffer\r\n\r\n return source\r\n}\r\n\r\n/**\r\n * Method to get an `MediaElementAudioSourceNode` from the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\nexport function attachAudioElementSource(audioSource: HTMLMediaElement, audioContext: AudioContext): MediaElementAudioSourceNode {\r\n return audioContext.createMediaElementSource(audioSource)\r\n}"],"names":["REPEAT_NAME_REGEX","RegExp","RepeatTrackProcessor","deleteOriginTrack","matches","name","match","process","track","Error","repeatTrackArgs","split","length","startTime","parseFloat","interval","endTime","time","tempTrack","toString","Math","random","substring","dbToVolume","db","pow","audioSource","timings","_audioContext","_audioSource","_cache","_eventHandler","_gainNode","_muted","_paused","_startTime","_timings","_volume","_processors","console","error","this","AudioContext","createGain","connect","destination","getOptimalAudioSource","processTracks","audioContext","source","createBufferSource","fetch","response","arrayBuffer","decodeAudioData","buffer","fetchAudioSource","then","_this","document","dispatchEvent","Event","tagName","createMediaElementSource","attachAudioElementSource","mute","gain","value","unmute","pause","suspend","unpause","resume","setCacheEntry","entry","getCacheEntry","getAllTracks","Object","keys","getActiveTimingKey","currentTime","activeTimings","filter","timing","forEach","trackName","_this2","processor","start","startEventHandling","window","requestAnimationFrame","_this3","handleEvents","timingKey","_this4","executeEvent","volume"],"mappings":"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"}
|
package/dist/misairu.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
module.exports=function(){function t(t,
|
|
1
|
+
var t=new RegExp(/repeat:\d:\d:\d/),e=function(){function e(){this.deleteOriginTrack=!0}var n=e.prototype;return n.matches=function(e){return null!==e.match(t)},n.process=function(t,e){if("function"!=typeof e)throw Error('The value of repeat track "'+e+'" is not a function');var n=t.split(":");if(4!=n.length)throw Error('The repeat track "'+t+'" does not supply the valid amount of arguments');var i=parseFloat(n[1]),o=parseFloat(n[2]),r=parseFloat(n[3]),s=i,a={};do{a[s.toString()]=e,s+=o}while(s<r);return["repeat-"+Math.random().toString(36).substring(7),a]},e}();function n(t){return Math.pow(10,t/20)}module.exports=function(){function t(t,n){this._audioContext=void 0,this._audioSource=null,this._cache={},this._eventHandler=null,this._gainNode=void 0,this._muted=!1,this._paused=!1,this._startTime=0,this._timings=void 0,this._volume=0,this._processors=[new e],null===n&&console.error("You need to specify a timings object"),this._timings=n,this._audioContext=new AudioContext,this._gainNode=this._audioContext.createGain(),this._gainNode.connect(this._audioContext.destination),this.getOptimalAudioSource(t),this.processTracks()}var i,o=t.prototype;return o.getOptimalAudioSource=function(t){var e=this;"string"==typeof t?function(t,e){try{var n=e.createBufferSource();return Promise.resolve(fetch(t)).then(function(t){return Promise.resolve(t.arrayBuffer()).then(function(t){return Promise.resolve(e.decodeAudioData(t)).then(function(t){return n.buffer=t,n})})})}catch(t){return Promise.reject(t)}}(t,this._audioContext).then(function(t){e._audioSource=t,e._audioSource.connect(e._gainNode),document.dispatchEvent(new Event("misairu.ready"))}):"object"!=typeof t||"AUDIO"!=t.tagName&&"VIDEO"!=t.tagName||(this._audioSource=function(t,e){return e.createMediaElementSource(t)}(t,this._audioContext),this._audioSource.connect(this._gainNode))},o.mute=function(){this._muted||(this._muted=!0,this._gainNode.gain.value=0)},o.unmute=function(){this._muted&&(this._muted=!1,this._gainNode.gain.value=n(this._volume))},o.pause=function(){this._paused||(this._audioContext.suspend(),this._paused=!0)},o.unpause=function(){this._paused&&(this._audioContext.resume(),this._paused=!1)},o.setCacheEntry=function(t,e){this._cache[t]=e},o.getCacheEntry=function(t){return this._cache[t]},o.getAllTracks=function(){return Object.keys(this._timings)},o.getActiveTimingKey=function(t,e){var n=Object.keys(this._timings[t]).filter(function(t){if(e>=parseFloat(t))return!0});return n[n.length-1]},o.processTracks=function(){var t=this;this.getAllTracks().forEach(function(e){t._processors.forEach(function(n){if(n.matches(e)){var i=n.process(e,t._timings[e]);t._timings[i[0]]=i[1],n.deleteOriginTrack&&delete t._timings[e]}})})},o.start=function(){this._startTime=this._audioContext.currentTime,this._audioSource.start(),this.startEventHandling()},o.startEventHandling=function(){var t=this;null==this._eventHandler&&(this._eventHandler=window.requestAnimationFrame(function(){t.handleEvents()}))},o.handleEvents=function(){var t=this,e=this._audioContext.currentTime-this._startTime;this.getAllTracks().forEach(function(n){var i=t.getActiveTimingKey(n,e);null!==i&&t.getCacheEntry(n)!=i&&(t.executeEvent(n,i,e),t.setCacheEntry(n,i))}),this._eventHandler=window.requestAnimationFrame(function(){t.handleEvents()})},o.executeEvent=function(t,e,n){this._timings[t][e](this,e,t,n)},(i=[{key:"volume",get:function(){return this._volume},set:function(t){var e;this._volume=(e=t)<-80?-80:e>5?5:e,this._muted||(this._gainNode.gain.value=n(this._volume))}}])&&function(t,e){for(var n=0;n<e.length;n++){var i=e[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,i.key,i)}}(t.prototype,i),t}();
|
|
2
2
|
//# sourceMappingURL=misairu.js.map
|
package/dist/misairu.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"misairu.js","sources":["../src/misairu.ts"],"sourcesContent":["import { EventCache, TimingObject } from './types'\r\n\r\n/**\r\n * Main misairu class\r\n */\r\nexport class Misairu {\r\n /**\r\n * The HTML5 AudioContext used for accurately timing our events\r\n */\r\n private readonly _audioContext: AudioContext\r\n\r\n /**\r\n * If the audio source is a HTML5 media element (either a <audio> or <video> tag), this will be a reference to it\r\n *\r\n * @default null\r\n */\r\n private _audioElement: HTMLMediaElement | null = null\r\n\r\n /**\r\n * A source node to play our audio from, either a buffered source from a downloaded media file or a media element source\r\n *\r\n * @default null\r\n */\r\n private _audioSource: AudioBufferSourceNode | MediaElementAudioSourceNode | null = null\r\n\r\n /**\r\n * A object containing the last executed time key per track to not execute an event on every tick\r\n *\r\n * @default {}\r\n */\r\n private _cache: EventCache = {}\r\n\r\n /**\r\n * Reference to the `requestAnimationFrame` handler\r\n *\r\n * @default null\r\n */\r\n private _eventHandler: number | null = null\r\n\r\n /**\r\n * Gain node from our audio context to control audio volume\r\n */\r\n private readonly _gainNode: GainNode\r\n\r\n /**\r\n * Boolean value describing if this instance is currently muted\r\n */\r\n private _muted = false\r\n\r\n /**\r\n * Boolean value describing if this instance is currently paused\r\n */\r\n private _paused = false\r\n\r\n /**\r\n * The time when event handling was started, based on the audio contexts `currentTime` when `start()` was called\r\n */\r\n private _startTime = 0\r\n\r\n /**\r\n * Object containing all timing tracks and events to be executed\r\n */\r\n private _timings: TimingObject\r\n\r\n /**\r\n * Volume of the current instance\r\n */\r\n private _volume = 0\r\n\r\n get volume(): number {\r\n return this._volume\r\n }\r\n\r\n set volume(db: number) {\r\n this._volume = db\r\n this.clampGain()\r\n\r\n if (!this._muted) {\r\n this._gainNode.gain.value = this.dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * misairu constructor\r\n *\r\n * @param audioSource a string or HTML element to be used as audio source\r\n * @param timings a object containing event timing information\r\n */\r\n constructor(audioSource: string | HTMLMediaElement, timings: TimingObject) {\r\n if (timings === null) console.error('You need to specify a timings object')\r\n this._timings = timings\r\n\r\n this._audioContext = new AudioContext()\r\n\r\n this._gainNode = this._audioContext.createGain()\r\n this._gainNode.connect(this._audioContext.destination)\r\n\r\n this.getOptimalAudioSource(audioSource)\r\n\r\n this.compile()\r\n }\r\n\r\n /**\r\n * Method to figure out the best course of action to take with the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private getOptimalAudioSource(audioSource: string | HTMLMediaElement): void {\r\n if (typeof audioSource == 'string') {\r\n this.fetchAudioSource(audioSource)\r\n } else if (\r\n typeof audioSource == 'object' &&\r\n (audioSource.tagName == 'AUDIO' || audioSource.tagName == 'VIDEO')\r\n ) {\r\n this.attachAudioElementSource(audioSource)\r\n }\r\n }\r\n\r\n /**\r\n * Method to fetch the external audio file (if the audio source parameter was a string)\r\n * and turning it into a `AudioBufferSourceNode`\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private fetchAudioSource(audioSource: string): void {\r\n const source = this._audioContext.createBufferSource()\r\n\r\n fetch(audioSource)\r\n .then((response) => {\r\n return response.arrayBuffer()\r\n })\r\n .then((arrayBuffer) => {\r\n this._audioContext.decodeAudioData(arrayBuffer).then((buffer) => {\r\n source.buffer = buffer\r\n source.connect(this._gainNode)\r\n\r\n document.dispatchEvent(new Event('misairu.ready'))\r\n this._audioSource = source\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Method to get an `MediaElementAudioSourceNode` from the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private attachAudioElementSource(audioSource: HTMLMediaElement): void {\r\n const source = this._audioContext.createMediaElementSource(audioSource)\r\n this._audioElement = audioSource\r\n\r\n source.connect(this._gainNode)\r\n this._audioSource = source\r\n }\r\n\r\n /**\r\n * Mutes the instance audio\r\n *\r\n * @public\r\n */\r\n public mute(): void {\r\n if (!this._muted) {\r\n this._muted = true\r\n this._gainNode.gain.value = 0\r\n }\r\n }\r\n\r\n /**\r\n * Unmutes the instance audio\r\n *\r\n * @public\r\n */\r\n public unmute(): void {\r\n if (this._muted) {\r\n this._muted = false\r\n this._gainNode.gain.value = this.dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * Pauses instance playback\r\n *\r\n * @public\r\n */\r\n public pause(): void {\r\n if (!this._paused) {\r\n this._audioContext.suspend()\r\n this._paused = true\r\n }\r\n }\r\n\r\n /**\r\n * Resumes instance playback\r\n *\r\n * @public\r\n */\r\n public unpause(): void {\r\n if (this._paused) {\r\n this._audioContext.resume()\r\n this._paused = false\r\n }\r\n }\r\n\r\n /**\r\n * Clamps the volume to a min/max value to prevent accidental oversetting to way too loud measures\r\n *\r\n * @internal\r\n */\r\n private clampGain(): void {\r\n if (this._volume < -80) {\r\n this._volume = -80\r\n } else if (this._volume > 5) {\r\n this._volume = 5\r\n }\r\n }\r\n\r\n /**\r\n * Method to turn the passed decibel values into volume values for the audio playback\r\n *\r\n * @param db decibel value\r\n * @returns volume value\r\n * @internal\r\n */\r\n private dbToVolume(db: number): number {\r\n return Math.pow(10, db / 20)\r\n }\r\n\r\n /**\r\n * Set a cache entry for the given track\r\n *\r\n * @param track track to set a cache entry for\r\n * @param entry value of the cache entry\r\n * @internal\r\n */\r\n private setCacheEntry(track: string, entry: string): void {\r\n this._cache[track] = entry\r\n }\r\n\r\n /**\r\n * Get cache entry for the given track\r\n *\r\n * @param track track to get a cache entry for\r\n * @returns a cache entry\r\n * @internal\r\n */\r\n private getCacheEntry(track: string): string {\r\n return this._cache[track]\r\n }\r\n\r\n /**\r\n * Get all tracks from the timing configuration\r\n *\r\n * @returns a list of all track names\r\n * @internal\r\n */\r\n private getAllTracks(): string[] {\r\n return Object.keys(this._timings)\r\n }\r\n\r\n /**\r\n * Returns the current active timing key for a given track\r\n *\r\n * @param track track to get the timing key from\r\n * @param currentTime current playback time\r\n * @returns the current active timing key\r\n * @internal\r\n */\r\n private getActiveTimingKey(track: string, currentTime: number): string {\r\n const timingKeys = Object.keys(this._timings[track])\r\n\r\n const activeTimings = timingKeys.filter((timing) => {\r\n if (currentTime >= parseFloat(timing)) {\r\n return true\r\n }\r\n })\r\n\r\n return activeTimings[activeTimings.length - 1]\r\n }\r\n\r\n /**\r\n * Main method to compile special sections in the timing configuration\r\n *\r\n * @internal\r\n */\r\n private compile(): void {\r\n this.getAllTracks().forEach((track) => {\r\n if (track.startsWith('repeat:')) {\r\n this.compileRepeat(track)\r\n }\r\n })\r\n }\r\n\r\n /**\r\n * Method to compile tracks whose name starts with `repeat:`\r\n *\r\n * @param track track to compile the repeat method for\r\n * @internal\r\n */\r\n private compileRepeat(track: string): void {\r\n if (typeof this._timings[track] != 'function')\r\n throw Error(`The value of repeat track \"${track}\" is not a function`)\r\n\r\n const repeatTrackArgs = track.split(':')\r\n\r\n if (repeatTrackArgs.length != 4)\r\n throw Error(`The repeat track \"${track}\" does not supply the valid amount of arguments`)\r\n\r\n const startTime = parseFloat(repeatTrackArgs[1])\r\n const interval = parseFloat(repeatTrackArgs[2])\r\n const endTime = parseFloat(repeatTrackArgs[3])\r\n\r\n let time = startTime\r\n const tempTrack = {}\r\n\r\n do {\r\n tempTrack[time.toString()] = this._timings[track]\r\n time += interval\r\n } while (time < endTime)\r\n\r\n delete this._timings[track]\r\n\r\n this._timings[`repeat-${Math.random().toString(36).substring(7)}`] = tempTrack\r\n }\r\n\r\n /**\r\n * Start audio playback and event handling\r\n *\r\n * @public\r\n */\r\n public start(): void {\r\n this._startTime = this._audioContext.currentTime\r\n\r\n if (this._audioElement !== null) {\r\n this._audioElement.play()\r\n } else {\r\n ;(this._audioSource as AudioBufferSourceNode).start()\r\n }\r\n\r\n this.startEventHandling()\r\n }\r\n\r\n /**\r\n * Method to start event handler loop\r\n *\r\n * @internal\r\n */\r\n private startEventHandling(): void {\r\n if (this._eventHandler == null) {\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Event handler method, is running in a loop using `requestAnimationFrame`\r\n *\r\n * @internal\r\n */\r\n private handleEvents(): void {\r\n const time = this._audioContext.currentTime - this._startTime\r\n\r\n this.getAllTracks().forEach((track) => {\r\n const timingKey = this.getActiveTimingKey(track, time)\r\n\r\n if (timingKey !== null && !(this.getCacheEntry(track) == timingKey)) {\r\n this.executeEvent(track, timingKey, time)\r\n this.setCacheEntry(track, timingKey)\r\n }\r\n })\r\n\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n\r\n /**\r\n * Method to execute the event for a given timing key on a given track\r\n *\r\n * @param track the track to execute the event on\r\n * @param timingKey the timing key to execute\r\n * @param time current playback time\r\n * @internal\r\n */\r\n private executeEvent(track: string, timingKey: string, time: number): void {\r\n this._timings[track][timingKey](this, timingKey, track, time)\r\n }\r\n}\r\n"],"names":["audioSource","timings","_audioContext","_audioElement","_audioSource","_cache","_eventHandler","_gainNode","_muted","_paused","_startTime","_timings","_volume","console","error","this","AudioContext","createGain","connect","destination","getOptimalAudioSource","compile","fetchAudioSource","tagName","attachAudioElementSource","source","createBufferSource","fetch","then","response","arrayBuffer","_this","decodeAudioData","buffer","document","dispatchEvent","Event","createMediaElementSource","mute","gain","value","unmute","dbToVolume","pause","suspend","unpause","resume","clampGain","db","Math","pow","setCacheEntry","track","entry","getCacheEntry","getAllTracks","Object","keys","getActiveTimingKey","currentTime","activeTimings","filter","timing","parseFloat","length","forEach","startsWith","_this2","compileRepeat","Error","repeatTrackArgs","split","startTime","interval","endTime","time","tempTrack","toString","random","substring","start","play","startEventHandling","window","requestAnimationFrame","_this3","handleEvents","timingKey","_this4","executeEvent"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"misairu.js","sources":["../src/processors/repeat.ts","../src/utilities/audio.ts","../src/misairu.ts","../src/utilities/source.ts"],"sourcesContent":["import { EventFunction, EventTrack, ITrackProcessor } from \"../types\";\r\n\r\nconst REPEAT_NAME_REGEX = new RegExp(/repeat:\\d:\\d:\\d/)\r\n\r\nexport class RepeatTrackProcessor implements ITrackProcessor {\r\n deleteOriginTrack = true;\r\n\r\n matches(name: string): boolean {\r\n return name.match(REPEAT_NAME_REGEX) !== null\r\n }\r\n\r\n process(name: string, track: EventFunction): [string, EventTrack] {\r\n if (typeof track != 'function')\r\n throw Error(`The value of repeat track \"${track}\" is not a function`)\r\n\r\n const repeatTrackArgs = name.split(':')\r\n\r\n if (repeatTrackArgs.length != 4)\r\n throw Error(`The repeat track \"${name}\" does not supply the valid amount of arguments`)\r\n\r\n const startTime = parseFloat(repeatTrackArgs[1])\r\n const interval = parseFloat(repeatTrackArgs[2])\r\n const endTime = parseFloat(repeatTrackArgs[3])\r\n\r\n let time = startTime\r\n const tempTrack = {}\r\n\r\n do {\r\n tempTrack[time.toString()] = track\r\n time += interval\r\n } while (time < endTime)\r\n\r\n return [`repeat-${Math.random().toString(36).substring(7)}`, tempTrack]\r\n }\r\n}","/**\r\n * Method to turn the passed decibel values into volume values for the audio playback\r\n *\r\n * @param db decibel value\r\n * @returns volume value\r\n */\r\nexport function dbToVolume(db: number): number {\r\n return Math.pow(10, db / 20)\r\n}\r\n\r\n/**\r\n * Clamps the volume to a min/max value to prevent accidental oversetting to way too loud measures\r\n */\r\nexport function clampGain(volume: number): number {\r\n if (volume < -80) {\r\n return -80\r\n } else if (volume > 5) {\r\n return 5\r\n }\r\n\r\n return volume\r\n}","import { RepeatTrackProcessor } from './processors/repeat'\r\nimport { EventCache, ITrackProcessor, TimingObject } from './types'\r\nimport { dbToVolume, clampGain } from './utilities/audio'\r\nimport { fetchAudioSource, attachAudioElementSource } from './utilities/source'\r\n\r\n/**\r\n * Main misairu class\r\n */\r\nexport class Misairu {\r\n /**\r\n * The HTML5 AudioContext used for accurately timing our events\r\n */\r\n private readonly _audioContext: AudioContext\r\n\r\n /**\r\n * A source node to play our audio from, either a buffered source from a downloaded media file or a media element source\r\n *\r\n * @default null\r\n */\r\n private _audioSource: HTMLMediaElement | AudioBufferSourceNode | MediaElementAudioSourceNode | null = null\r\n\r\n /**\r\n * A object containing the last executed time key per track to not execute an event on every tick\r\n *\r\n * @default {}\r\n */\r\n private _cache: EventCache = {}\r\n\r\n /**\r\n * Reference to the `requestAnimationFrame` handler\r\n *\r\n * @default null\r\n */\r\n private _eventHandler: number | null = null\r\n\r\n /**\r\n * Gain node from our audio context to control audio volume\r\n */\r\n private readonly _gainNode: GainNode\r\n\r\n /**\r\n * Boolean value describing if this instance is currently muted\r\n */\r\n private _muted = false\r\n\r\n /**\r\n * Boolean value describing if this instance is currently paused\r\n */\r\n private _paused = false\r\n\r\n /**\r\n * The time when event handling was started, based on the audio contexts `currentTime` when `start()` was called\r\n */\r\n private _startTime = 0\r\n\r\n /**\r\n * Object containing all timing tracks and events to be executed\r\n */\r\n private _timings: TimingObject\r\n\r\n /**\r\n * Volume of the current instance\r\n */\r\n private _volume = 0\r\n\r\n /**\r\n * List of (predefined) track processors\r\n */\r\n private _processors: ITrackProcessor[] = [\r\n new RepeatTrackProcessor()\r\n ]\r\n\r\n get volume(): number {\r\n return this._volume\r\n }\r\n\r\n set volume(db: number) {\r\n this._volume = clampGain(db)\r\n\r\n if (!this._muted) {\r\n this._gainNode.gain.value = dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * misairu constructor\r\n *\r\n * @param audioSource a string or HTML element to be used as audio source\r\n * @param timings a object containing event timing information\r\n */\r\n constructor(audioSource: string | HTMLMediaElement, timings: TimingObject) {\r\n if (timings === null) console.error('You need to specify a timings object')\r\n this._timings = timings\r\n\r\n this._audioContext = new AudioContext()\r\n\r\n this._gainNode = this._audioContext.createGain()\r\n this._gainNode.connect(this._audioContext.destination)\r\n\r\n this.getOptimalAudioSource(audioSource)\r\n\r\n this.processTracks()\r\n }\r\n\r\n /**\r\n * Method to figure out the best course of action to take with the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private getOptimalAudioSource(audioSource: string | HTMLMediaElement): void {\r\n if (typeof audioSource == 'string') {\r\n fetchAudioSource(audioSource, this._audioContext).then((audioSource) => {\r\n this._audioSource = audioSource\r\n this._audioSource.connect(this._gainNode)\r\n\r\n document.dispatchEvent(new Event('misairu.ready'))\r\n })\r\n } else if (\r\n typeof audioSource == 'object' &&\r\n (audioSource.tagName == 'AUDIO' || audioSource.tagName == 'VIDEO')\r\n ) {\r\n this._audioSource = attachAudioElementSource(audioSource, this._audioContext)\r\n this._audioSource.connect(this._gainNode)\r\n }\r\n }\r\n\r\n /**\r\n * Mutes the instance audio\r\n *\r\n * @public\r\n */\r\n public mute(): void {\r\n if (!this._muted) {\r\n this._muted = true\r\n this._gainNode.gain.value = 0\r\n }\r\n }\r\n\r\n /**\r\n * Unmutes the instance audio\r\n *\r\n * @public\r\n */\r\n public unmute(): void {\r\n if (this._muted) {\r\n this._muted = false\r\n this._gainNode.gain.value = dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * Pauses instance playback\r\n *\r\n * @public\r\n */\r\n public pause(): void {\r\n if (!this._paused) {\r\n this._audioContext.suspend()\r\n this._paused = true\r\n }\r\n }\r\n\r\n /**\r\n * Resumes instance playback\r\n *\r\n * @public\r\n */\r\n public unpause(): void {\r\n if (this._paused) {\r\n this._audioContext.resume()\r\n this._paused = false\r\n }\r\n }\r\n\r\n /**\r\n * Set a cache entry for the given track\r\n *\r\n * @param track track to set a cache entry for\r\n * @param entry value of the cache entry\r\n * @internal\r\n */\r\n private setCacheEntry(track: string, entry: string): void {\r\n this._cache[track] = entry\r\n }\r\n\r\n /**\r\n * Get cache entry for the given track\r\n *\r\n * @param track track to get a cache entry for\r\n * @returns a cache entry\r\n * @internal\r\n */\r\n private getCacheEntry(track: string): string {\r\n return this._cache[track]\r\n }\r\n\r\n /**\r\n * Get all tracks from the timing configuration\r\n *\r\n * @returns a list of all track names\r\n * @internal\r\n */\r\n private getAllTracks(): string[] {\r\n return Object.keys(this._timings)\r\n }\r\n\r\n /**\r\n * Returns the current active timing key for a given track\r\n *\r\n * @param track track to get the timing key from\r\n * @param currentTime current playback time\r\n * @returns the current active timing key\r\n * @internal\r\n */\r\n private getActiveTimingKey(track: string, currentTime: number): string {\r\n const timingKeys = Object.keys(this._timings[track])\r\n\r\n const activeTimings = timingKeys.filter((timing) => {\r\n if (currentTime >= parseFloat(timing)) {\r\n return true\r\n }\r\n })\r\n\r\n return activeTimings[activeTimings.length - 1]\r\n }\r\n\r\n /**\r\n * Main method to process special sections in the timing configuration\r\n *\r\n * @internal\r\n */\r\n private processTracks(): void {\r\n this.getAllTracks().forEach((trackName) => {\r\n this._processors.forEach((processor: ITrackProcessor) => {\r\n if (processor.matches(trackName)) {\r\n const [processedTrackName, eventTrack] = processor.process(trackName, this._timings[trackName])\r\n\r\n this._timings[processedTrackName] = eventTrack\r\n\r\n if (processor.deleteOriginTrack) {\r\n delete this._timings[trackName]\r\n }\r\n }\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Start audio playback and event handling\r\n *\r\n * @public\r\n */\r\n public start(): void {\r\n this._startTime = this._audioContext.currentTime\r\n\r\n ;(this._audioSource as AudioBufferSourceNode).start()\r\n\r\n this.startEventHandling()\r\n }\r\n\r\n /**\r\n * Method to start event handler loop\r\n *\r\n * @internal\r\n */\r\n private startEventHandling(): void {\r\n if (this._eventHandler == null) {\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Event handler method, is running in a loop using `requestAnimationFrame`\r\n *\r\n * @internal\r\n */\r\n private handleEvents(): void {\r\n const time = this._audioContext.currentTime - this._startTime\r\n\r\n this.getAllTracks().forEach((track) => {\r\n const timingKey = this.getActiveTimingKey(track, time)\r\n\r\n if (timingKey !== null && !(this.getCacheEntry(track) == timingKey)) {\r\n this.executeEvent(track, timingKey, time)\r\n this.setCacheEntry(track, timingKey)\r\n }\r\n })\r\n\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n\r\n /**\r\n * Method to execute the event for a given timing key on a given track\r\n *\r\n * @param track the track to execute the event on\r\n * @param timingKey the timing key to execute\r\n * @param time current playback time\r\n * @internal\r\n */\r\n private executeEvent(track: string, timingKey: string, time: number): void {\r\n this._timings[track][timingKey](this, timingKey, track, time)\r\n }\r\n}\r\n","/**\r\n * Method to fetch the external audio file (if the audio source parameter was a string)\r\n * and turning it into a `AudioBufferSourceNode`\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\nexport async function fetchAudioSource(audioSource: string, audioContext: AudioContext): Promise<AudioBufferSourceNode> {\r\n const source = audioContext.createBufferSource()\r\n\r\n const response = await fetch(audioSource)\r\n const arrayBuffer = await response.arrayBuffer()\r\n const buffer = await audioContext.decodeAudioData(arrayBuffer)\r\n\r\n source.buffer = buffer\r\n\r\n return source\r\n}\r\n\r\n/**\r\n * Method to get an `MediaElementAudioSourceNode` from the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\nexport function attachAudioElementSource(audioSource: HTMLMediaElement, audioContext: AudioContext): MediaElementAudioSourceNode {\r\n return audioContext.createMediaElementSource(audioSource)\r\n}"],"names":["REPEAT_NAME_REGEX","RegExp","RepeatTrackProcessor","deleteOriginTrack","matches","name","match","process","track","Error","repeatTrackArgs","split","length","startTime","parseFloat","interval","endTime","time","tempTrack","toString","Math","random","substring","dbToVolume","db","pow","audioSource","timings","_audioContext","_audioSource","_cache","_eventHandler","_gainNode","_muted","_paused","_startTime","_timings","_volume","_processors","console","error","this","AudioContext","createGain","connect","destination","getOptimalAudioSource","processTracks","audioContext","source","createBufferSource","fetch","response","arrayBuffer","decodeAudioData","buffer","fetchAudioSource","then","_this","document","dispatchEvent","Event","tagName","createMediaElementSource","attachAudioElementSource","mute","gain","value","unmute","pause","suspend","unpause","resume","setCacheEntry","entry","getCacheEntry","getAllTracks","Object","keys","getActiveTimingKey","currentTime","activeTimings","filter","timing","forEach","trackName","_this2","processor","start","startEventHandling","window","requestAnimationFrame","_this3","handleEvents","timingKey","_this4","executeEvent","volume"],"mappings":"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"}
|
package/dist/misairu.modern.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
class t{get volume(){return this._volume}set volume(t){this._volume=t
|
|
1
|
+
const t=new RegExp(/repeat:\d:\d:\d/);class e{constructor(){this.deleteOriginTrack=!0}matches(e){return null!==e.match(t)}process(t,e){if("function"!=typeof e)throw Error(`The value of repeat track "${e}" is not a function`);const i=t.split(":");if(4!=i.length)throw Error(`The repeat track "${t}" does not supply the valid amount of arguments`);const s=parseFloat(i[1]),n=parseFloat(i[2]),a=parseFloat(i[3]);let o=s;const r={};do{r[o.toString()]=e,o+=n}while(o<a);return[`repeat-${Math.random().toString(36).substring(7)}`,r]}}function i(t){return Math.pow(10,t/20)}class s{get volume(){return this._volume}set volume(t){var e;this._volume=(e=t)<-80?-80:e>5?5:e,this._muted||(this._gainNode.gain.value=i(this._volume))}constructor(t,i){this._audioContext=void 0,this._audioSource=null,this._cache={},this._eventHandler=null,this._gainNode=void 0,this._muted=!1,this._paused=!1,this._startTime=0,this._timings=void 0,this._volume=0,this._processors=[new e],null===i&&console.error("You need to specify a timings object"),this._timings=i,this._audioContext=new AudioContext,this._gainNode=this._audioContext.createGain(),this._gainNode.connect(this._audioContext.destination),this.getOptimalAudioSource(t),this.processTracks()}getOptimalAudioSource(t){"string"==typeof t?async function(t,e){const i=e.createBufferSource(),s=await fetch(t),n=await s.arrayBuffer(),a=await e.decodeAudioData(n);return i.buffer=a,i}(t,this._audioContext).then(t=>{this._audioSource=t,this._audioSource.connect(this._gainNode),document.dispatchEvent(new Event("misairu.ready"))}):"object"!=typeof t||"AUDIO"!=t.tagName&&"VIDEO"!=t.tagName||(this._audioSource=function(t,e){return e.createMediaElementSource(t)}(t,this._audioContext),this._audioSource.connect(this._gainNode))}mute(){this._muted||(this._muted=!0,this._gainNode.gain.value=0)}unmute(){this._muted&&(this._muted=!1,this._gainNode.gain.value=i(this._volume))}pause(){this._paused||(this._audioContext.suspend(),this._paused=!0)}unpause(){this._paused&&(this._audioContext.resume(),this._paused=!1)}setCacheEntry(t,e){this._cache[t]=e}getCacheEntry(t){return this._cache[t]}getAllTracks(){return Object.keys(this._timings)}getActiveTimingKey(t,e){const i=Object.keys(this._timings[t]).filter(t=>{if(e>=parseFloat(t))return!0});return i[i.length-1]}processTracks(){this.getAllTracks().forEach(t=>{this._processors.forEach(e=>{if(e.matches(t)){const[i,s]=e.process(t,this._timings[t]);this._timings[i]=s,e.deleteOriginTrack&&delete this._timings[t]}})})}start(){this._startTime=this._audioContext.currentTime,this._audioSource.start(),this.startEventHandling()}startEventHandling(){null==this._eventHandler&&(this._eventHandler=window.requestAnimationFrame(()=>{this.handleEvents()}))}handleEvents(){const t=this._audioContext.currentTime-this._startTime;this.getAllTracks().forEach(e=>{const i=this.getActiveTimingKey(e,t);null!==i&&this.getCacheEntry(e)!=i&&(this.executeEvent(e,i,t),this.setCacheEntry(e,i))}),this._eventHandler=window.requestAnimationFrame(()=>{this.handleEvents()})}executeEvent(t,e,i){this._timings[t][e](this,e,t,i)}}export{s as default};
|
|
2
2
|
//# sourceMappingURL=misairu.modern.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"misairu.modern.js","sources":["../src/misairu.ts"],"sourcesContent":["import { EventCache, TimingObject } from './types'\r\n\r\n/**\r\n * Main misairu class\r\n */\r\nexport class Misairu {\r\n /**\r\n * The HTML5 AudioContext used for accurately timing our events\r\n */\r\n private readonly _audioContext: AudioContext\r\n\r\n /**\r\n * If the audio source is a HTML5 media element (either a <audio> or <video> tag), this will be a reference to it\r\n *\r\n * @default null\r\n */\r\n private _audioElement: HTMLMediaElement | null = null\r\n\r\n /**\r\n * A source node to play our audio from, either a buffered source from a downloaded media file or a media element source\r\n *\r\n * @default null\r\n */\r\n private _audioSource: AudioBufferSourceNode | MediaElementAudioSourceNode | null = null\r\n\r\n /**\r\n * A object containing the last executed time key per track to not execute an event on every tick\r\n *\r\n * @default {}\r\n */\r\n private _cache: EventCache = {}\r\n\r\n /**\r\n * Reference to the `requestAnimationFrame` handler\r\n *\r\n * @default null\r\n */\r\n private _eventHandler: number | null = null\r\n\r\n /**\r\n * Gain node from our audio context to control audio volume\r\n */\r\n private readonly _gainNode: GainNode\r\n\r\n /**\r\n * Boolean value describing if this instance is currently muted\r\n */\r\n private _muted = false\r\n\r\n /**\r\n * Boolean value describing if this instance is currently paused\r\n */\r\n private _paused = false\r\n\r\n /**\r\n * The time when event handling was started, based on the audio contexts `currentTime` when `start()` was called\r\n */\r\n private _startTime = 0\r\n\r\n /**\r\n * Object containing all timing tracks and events to be executed\r\n */\r\n private _timings: TimingObject\r\n\r\n /**\r\n * Volume of the current instance\r\n */\r\n private _volume = 0\r\n\r\n get volume(): number {\r\n return this._volume\r\n }\r\n\r\n set volume(db: number) {\r\n this._volume = db\r\n this.clampGain()\r\n\r\n if (!this._muted) {\r\n this._gainNode.gain.value = this.dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * misairu constructor\r\n *\r\n * @param audioSource a string or HTML element to be used as audio source\r\n * @param timings a object containing event timing information\r\n */\r\n constructor(audioSource: string | HTMLMediaElement, timings: TimingObject) {\r\n if (timings === null) console.error('You need to specify a timings object')\r\n this._timings = timings\r\n\r\n this._audioContext = new AudioContext()\r\n\r\n this._gainNode = this._audioContext.createGain()\r\n this._gainNode.connect(this._audioContext.destination)\r\n\r\n this.getOptimalAudioSource(audioSource)\r\n\r\n this.compile()\r\n }\r\n\r\n /**\r\n * Method to figure out the best course of action to take with the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private getOptimalAudioSource(audioSource: string | HTMLMediaElement): void {\r\n if (typeof audioSource == 'string') {\r\n this.fetchAudioSource(audioSource)\r\n } else if (\r\n typeof audioSource == 'object' &&\r\n (audioSource.tagName == 'AUDIO' || audioSource.tagName == 'VIDEO')\r\n ) {\r\n this.attachAudioElementSource(audioSource)\r\n }\r\n }\r\n\r\n /**\r\n * Method to fetch the external audio file (if the audio source parameter was a string)\r\n * and turning it into a `AudioBufferSourceNode`\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private fetchAudioSource(audioSource: string): void {\r\n const source = this._audioContext.createBufferSource()\r\n\r\n fetch(audioSource)\r\n .then((response) => {\r\n return response.arrayBuffer()\r\n })\r\n .then((arrayBuffer) => {\r\n this._audioContext.decodeAudioData(arrayBuffer).then((buffer) => {\r\n source.buffer = buffer\r\n source.connect(this._gainNode)\r\n\r\n document.dispatchEvent(new Event('misairu.ready'))\r\n this._audioSource = source\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Method to get an `MediaElementAudioSourceNode` from the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private attachAudioElementSource(audioSource: HTMLMediaElement): void {\r\n const source = this._audioContext.createMediaElementSource(audioSource)\r\n this._audioElement = audioSource\r\n\r\n source.connect(this._gainNode)\r\n this._audioSource = source\r\n }\r\n\r\n /**\r\n * Mutes the instance audio\r\n *\r\n * @public\r\n */\r\n public mute(): void {\r\n if (!this._muted) {\r\n this._muted = true\r\n this._gainNode.gain.value = 0\r\n }\r\n }\r\n\r\n /**\r\n * Unmutes the instance audio\r\n *\r\n * @public\r\n */\r\n public unmute(): void {\r\n if (this._muted) {\r\n this._muted = false\r\n this._gainNode.gain.value = this.dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * Pauses instance playback\r\n *\r\n * @public\r\n */\r\n public pause(): void {\r\n if (!this._paused) {\r\n this._audioContext.suspend()\r\n this._paused = true\r\n }\r\n }\r\n\r\n /**\r\n * Resumes instance playback\r\n *\r\n * @public\r\n */\r\n public unpause(): void {\r\n if (this._paused) {\r\n this._audioContext.resume()\r\n this._paused = false\r\n }\r\n }\r\n\r\n /**\r\n * Clamps the volume to a min/max value to prevent accidental oversetting to way too loud measures\r\n *\r\n * @internal\r\n */\r\n private clampGain(): void {\r\n if (this._volume < -80) {\r\n this._volume = -80\r\n } else if (this._volume > 5) {\r\n this._volume = 5\r\n }\r\n }\r\n\r\n /**\r\n * Method to turn the passed decibel values into volume values for the audio playback\r\n *\r\n * @param db decibel value\r\n * @returns volume value\r\n * @internal\r\n */\r\n private dbToVolume(db: number): number {\r\n return Math.pow(10, db / 20)\r\n }\r\n\r\n /**\r\n * Set a cache entry for the given track\r\n *\r\n * @param track track to set a cache entry for\r\n * @param entry value of the cache entry\r\n * @internal\r\n */\r\n private setCacheEntry(track: string, entry: string): void {\r\n this._cache[track] = entry\r\n }\r\n\r\n /**\r\n * Get cache entry for the given track\r\n *\r\n * @param track track to get a cache entry for\r\n * @returns a cache entry\r\n * @internal\r\n */\r\n private getCacheEntry(track: string): string {\r\n return this._cache[track]\r\n }\r\n\r\n /**\r\n * Get all tracks from the timing configuration\r\n *\r\n * @returns a list of all track names\r\n * @internal\r\n */\r\n private getAllTracks(): string[] {\r\n return Object.keys(this._timings)\r\n }\r\n\r\n /**\r\n * Returns the current active timing key for a given track\r\n *\r\n * @param track track to get the timing key from\r\n * @param currentTime current playback time\r\n * @returns the current active timing key\r\n * @internal\r\n */\r\n private getActiveTimingKey(track: string, currentTime: number): string {\r\n const timingKeys = Object.keys(this._timings[track])\r\n\r\n const activeTimings = timingKeys.filter((timing) => {\r\n if (currentTime >= parseFloat(timing)) {\r\n return true\r\n }\r\n })\r\n\r\n return activeTimings[activeTimings.length - 1]\r\n }\r\n\r\n /**\r\n * Main method to compile special sections in the timing configuration\r\n *\r\n * @internal\r\n */\r\n private compile(): void {\r\n this.getAllTracks().forEach((track) => {\r\n if (track.startsWith('repeat:')) {\r\n this.compileRepeat(track)\r\n }\r\n })\r\n }\r\n\r\n /**\r\n * Method to compile tracks whose name starts with `repeat:`\r\n *\r\n * @param track track to compile the repeat method for\r\n * @internal\r\n */\r\n private compileRepeat(track: string): void {\r\n if (typeof this._timings[track] != 'function')\r\n throw Error(`The value of repeat track \"${track}\" is not a function`)\r\n\r\n const repeatTrackArgs = track.split(':')\r\n\r\n if (repeatTrackArgs.length != 4)\r\n throw Error(`The repeat track \"${track}\" does not supply the valid amount of arguments`)\r\n\r\n const startTime = parseFloat(repeatTrackArgs[1])\r\n const interval = parseFloat(repeatTrackArgs[2])\r\n const endTime = parseFloat(repeatTrackArgs[3])\r\n\r\n let time = startTime\r\n const tempTrack = {}\r\n\r\n do {\r\n tempTrack[time.toString()] = this._timings[track]\r\n time += interval\r\n } while (time < endTime)\r\n\r\n delete this._timings[track]\r\n\r\n this._timings[`repeat-${Math.random().toString(36).substring(7)}`] = tempTrack\r\n }\r\n\r\n /**\r\n * Start audio playback and event handling\r\n *\r\n * @public\r\n */\r\n public start(): void {\r\n this._startTime = this._audioContext.currentTime\r\n\r\n if (this._audioElement !== null) {\r\n this._audioElement.play()\r\n } else {\r\n ;(this._audioSource as AudioBufferSourceNode).start()\r\n }\r\n\r\n this.startEventHandling()\r\n }\r\n\r\n /**\r\n * Method to start event handler loop\r\n *\r\n * @internal\r\n */\r\n private startEventHandling(): void {\r\n if (this._eventHandler == null) {\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Event handler method, is running in a loop using `requestAnimationFrame`\r\n *\r\n * @internal\r\n */\r\n private handleEvents(): void {\r\n const time = this._audioContext.currentTime - this._startTime\r\n\r\n this.getAllTracks().forEach((track) => {\r\n const timingKey = this.getActiveTimingKey(track, time)\r\n\r\n if (timingKey !== null && !(this.getCacheEntry(track) == timingKey)) {\r\n this.executeEvent(track, timingKey, time)\r\n this.setCacheEntry(track, timingKey)\r\n }\r\n })\r\n\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n\r\n /**\r\n * Method to execute the event for a given timing key on a given track\r\n *\r\n * @param track the track to execute the event on\r\n * @param timingKey the timing key to execute\r\n * @param time current playback time\r\n * @internal\r\n */\r\n private executeEvent(track: string, timingKey: string, time: number): void {\r\n this._timings[track][timingKey](this, timingKey, track, time)\r\n }\r\n}\r\n"],"names":["Misairu","volume","_volume","db","this","clampGain","_muted","_gainNode","gain","value","dbToVolume","constructor","audioSource","timings","_audioContext","_audioElement","_audioSource","_cache","_eventHandler","_paused","_startTime","_timings","console","error","AudioContext","createGain","connect","destination","getOptimalAudioSource","compile","fetchAudioSource","tagName","attachAudioElementSource","source","createBufferSource","fetch","then","response","arrayBuffer","decodeAudioData","buffer","document","dispatchEvent","Event","createMediaElementSource","mute","unmute","pause","suspend","unpause","resume","Math","pow","setCacheEntry","track","entry","getCacheEntry","getAllTracks","Object","keys","getActiveTimingKey","currentTime","activeTimings","filter","timing","parseFloat","length","forEach","startsWith","compileRepeat","Error","repeatTrackArgs","split","startTime","interval","endTime","time","tempTrack","toString","random","substring","start","play","startEventHandling","window","requestAnimationFrame","handleEvents","timingKey","executeEvent"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"misairu.modern.js","sources":["../src/processors/repeat.ts","../src/utilities/audio.ts","../src/misairu.ts","../src/utilities/source.ts"],"sourcesContent":["import { EventFunction, EventTrack, ITrackProcessor } from \"../types\";\r\n\r\nconst REPEAT_NAME_REGEX = new RegExp(/repeat:\\d:\\d:\\d/)\r\n\r\nexport class RepeatTrackProcessor implements ITrackProcessor {\r\n deleteOriginTrack = true;\r\n\r\n matches(name: string): boolean {\r\n return name.match(REPEAT_NAME_REGEX) !== null\r\n }\r\n\r\n process(name: string, track: EventFunction): [string, EventTrack] {\r\n if (typeof track != 'function')\r\n throw Error(`The value of repeat track \"${track}\" is not a function`)\r\n\r\n const repeatTrackArgs = name.split(':')\r\n\r\n if (repeatTrackArgs.length != 4)\r\n throw Error(`The repeat track \"${name}\" does not supply the valid amount of arguments`)\r\n\r\n const startTime = parseFloat(repeatTrackArgs[1])\r\n const interval = parseFloat(repeatTrackArgs[2])\r\n const endTime = parseFloat(repeatTrackArgs[3])\r\n\r\n let time = startTime\r\n const tempTrack = {}\r\n\r\n do {\r\n tempTrack[time.toString()] = track\r\n time += interval\r\n } while (time < endTime)\r\n\r\n return [`repeat-${Math.random().toString(36).substring(7)}`, tempTrack]\r\n }\r\n}","/**\r\n * Method to turn the passed decibel values into volume values for the audio playback\r\n *\r\n * @param db decibel value\r\n * @returns volume value\r\n */\r\nexport function dbToVolume(db: number): number {\r\n return Math.pow(10, db / 20)\r\n}\r\n\r\n/**\r\n * Clamps the volume to a min/max value to prevent accidental oversetting to way too loud measures\r\n */\r\nexport function clampGain(volume: number): number {\r\n if (volume < -80) {\r\n return -80\r\n } else if (volume > 5) {\r\n return 5\r\n }\r\n\r\n return volume\r\n}","import { RepeatTrackProcessor } from './processors/repeat'\r\nimport { EventCache, ITrackProcessor, TimingObject } from './types'\r\nimport { dbToVolume, clampGain } from './utilities/audio'\r\nimport { fetchAudioSource, attachAudioElementSource } from './utilities/source'\r\n\r\n/**\r\n * Main misairu class\r\n */\r\nexport class Misairu {\r\n /**\r\n * The HTML5 AudioContext used for accurately timing our events\r\n */\r\n private readonly _audioContext: AudioContext\r\n\r\n /**\r\n * A source node to play our audio from, either a buffered source from a downloaded media file or a media element source\r\n *\r\n * @default null\r\n */\r\n private _audioSource: HTMLMediaElement | AudioBufferSourceNode | MediaElementAudioSourceNode | null = null\r\n\r\n /**\r\n * A object containing the last executed time key per track to not execute an event on every tick\r\n *\r\n * @default {}\r\n */\r\n private _cache: EventCache = {}\r\n\r\n /**\r\n * Reference to the `requestAnimationFrame` handler\r\n *\r\n * @default null\r\n */\r\n private _eventHandler: number | null = null\r\n\r\n /**\r\n * Gain node from our audio context to control audio volume\r\n */\r\n private readonly _gainNode: GainNode\r\n\r\n /**\r\n * Boolean value describing if this instance is currently muted\r\n */\r\n private _muted = false\r\n\r\n /**\r\n * Boolean value describing if this instance is currently paused\r\n */\r\n private _paused = false\r\n\r\n /**\r\n * The time when event handling was started, based on the audio contexts `currentTime` when `start()` was called\r\n */\r\n private _startTime = 0\r\n\r\n /**\r\n * Object containing all timing tracks and events to be executed\r\n */\r\n private _timings: TimingObject\r\n\r\n /**\r\n * Volume of the current instance\r\n */\r\n private _volume = 0\r\n\r\n /**\r\n * List of (predefined) track processors\r\n */\r\n private _processors: ITrackProcessor[] = [\r\n new RepeatTrackProcessor()\r\n ]\r\n\r\n get volume(): number {\r\n return this._volume\r\n }\r\n\r\n set volume(db: number) {\r\n this._volume = clampGain(db)\r\n\r\n if (!this._muted) {\r\n this._gainNode.gain.value = dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * misairu constructor\r\n *\r\n * @param audioSource a string or HTML element to be used as audio source\r\n * @param timings a object containing event timing information\r\n */\r\n constructor(audioSource: string | HTMLMediaElement, timings: TimingObject) {\r\n if (timings === null) console.error('You need to specify a timings object')\r\n this._timings = timings\r\n\r\n this._audioContext = new AudioContext()\r\n\r\n this._gainNode = this._audioContext.createGain()\r\n this._gainNode.connect(this._audioContext.destination)\r\n\r\n this.getOptimalAudioSource(audioSource)\r\n\r\n this.processTracks()\r\n }\r\n\r\n /**\r\n * Method to figure out the best course of action to take with the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private getOptimalAudioSource(audioSource: string | HTMLMediaElement): void {\r\n if (typeof audioSource == 'string') {\r\n fetchAudioSource(audioSource, this._audioContext).then((audioSource) => {\r\n this._audioSource = audioSource\r\n this._audioSource.connect(this._gainNode)\r\n\r\n document.dispatchEvent(new Event('misairu.ready'))\r\n })\r\n } else if (\r\n typeof audioSource == 'object' &&\r\n (audioSource.tagName == 'AUDIO' || audioSource.tagName == 'VIDEO')\r\n ) {\r\n this._audioSource = attachAudioElementSource(audioSource, this._audioContext)\r\n this._audioSource.connect(this._gainNode)\r\n }\r\n }\r\n\r\n /**\r\n * Mutes the instance audio\r\n *\r\n * @public\r\n */\r\n public mute(): void {\r\n if (!this._muted) {\r\n this._muted = true\r\n this._gainNode.gain.value = 0\r\n }\r\n }\r\n\r\n /**\r\n * Unmutes the instance audio\r\n *\r\n * @public\r\n */\r\n public unmute(): void {\r\n if (this._muted) {\r\n this._muted = false\r\n this._gainNode.gain.value = dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * Pauses instance playback\r\n *\r\n * @public\r\n */\r\n public pause(): void {\r\n if (!this._paused) {\r\n this._audioContext.suspend()\r\n this._paused = true\r\n }\r\n }\r\n\r\n /**\r\n * Resumes instance playback\r\n *\r\n * @public\r\n */\r\n public unpause(): void {\r\n if (this._paused) {\r\n this._audioContext.resume()\r\n this._paused = false\r\n }\r\n }\r\n\r\n /**\r\n * Set a cache entry for the given track\r\n *\r\n * @param track track to set a cache entry for\r\n * @param entry value of the cache entry\r\n * @internal\r\n */\r\n private setCacheEntry(track: string, entry: string): void {\r\n this._cache[track] = entry\r\n }\r\n\r\n /**\r\n * Get cache entry for the given track\r\n *\r\n * @param track track to get a cache entry for\r\n * @returns a cache entry\r\n * @internal\r\n */\r\n private getCacheEntry(track: string): string {\r\n return this._cache[track]\r\n }\r\n\r\n /**\r\n * Get all tracks from the timing configuration\r\n *\r\n * @returns a list of all track names\r\n * @internal\r\n */\r\n private getAllTracks(): string[] {\r\n return Object.keys(this._timings)\r\n }\r\n\r\n /**\r\n * Returns the current active timing key for a given track\r\n *\r\n * @param track track to get the timing key from\r\n * @param currentTime current playback time\r\n * @returns the current active timing key\r\n * @internal\r\n */\r\n private getActiveTimingKey(track: string, currentTime: number): string {\r\n const timingKeys = Object.keys(this._timings[track])\r\n\r\n const activeTimings = timingKeys.filter((timing) => {\r\n if (currentTime >= parseFloat(timing)) {\r\n return true\r\n }\r\n })\r\n\r\n return activeTimings[activeTimings.length - 1]\r\n }\r\n\r\n /**\r\n * Main method to process special sections in the timing configuration\r\n *\r\n * @internal\r\n */\r\n private processTracks(): void {\r\n this.getAllTracks().forEach((trackName) => {\r\n this._processors.forEach((processor: ITrackProcessor) => {\r\n if (processor.matches(trackName)) {\r\n const [processedTrackName, eventTrack] = processor.process(trackName, this._timings[trackName])\r\n\r\n this._timings[processedTrackName] = eventTrack\r\n\r\n if (processor.deleteOriginTrack) {\r\n delete this._timings[trackName]\r\n }\r\n }\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Start audio playback and event handling\r\n *\r\n * @public\r\n */\r\n public start(): void {\r\n this._startTime = this._audioContext.currentTime\r\n\r\n ;(this._audioSource as AudioBufferSourceNode).start()\r\n\r\n this.startEventHandling()\r\n }\r\n\r\n /**\r\n * Method to start event handler loop\r\n *\r\n * @internal\r\n */\r\n private startEventHandling(): void {\r\n if (this._eventHandler == null) {\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Event handler method, is running in a loop using `requestAnimationFrame`\r\n *\r\n * @internal\r\n */\r\n private handleEvents(): void {\r\n const time = this._audioContext.currentTime - this._startTime\r\n\r\n this.getAllTracks().forEach((track) => {\r\n const timingKey = this.getActiveTimingKey(track, time)\r\n\r\n if (timingKey !== null && !(this.getCacheEntry(track) == timingKey)) {\r\n this.executeEvent(track, timingKey, time)\r\n this.setCacheEntry(track, timingKey)\r\n }\r\n })\r\n\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n\r\n /**\r\n * Method to execute the event for a given timing key on a given track\r\n *\r\n * @param track the track to execute the event on\r\n * @param timingKey the timing key to execute\r\n * @param time current playback time\r\n * @internal\r\n */\r\n private executeEvent(track: string, timingKey: string, time: number): void {\r\n this._timings[track][timingKey](this, timingKey, track, time)\r\n }\r\n}\r\n","/**\r\n * Method to fetch the external audio file (if the audio source parameter was a string)\r\n * and turning it into a `AudioBufferSourceNode`\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\nexport async function fetchAudioSource(audioSource: string, audioContext: AudioContext): Promise<AudioBufferSourceNode> {\r\n const source = audioContext.createBufferSource()\r\n\r\n const response = await fetch(audioSource)\r\n const arrayBuffer = await response.arrayBuffer()\r\n const buffer = await audioContext.decodeAudioData(arrayBuffer)\r\n\r\n source.buffer = buffer\r\n\r\n return source\r\n}\r\n\r\n/**\r\n * Method to get an `MediaElementAudioSourceNode` from the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\nexport function attachAudioElementSource(audioSource: HTMLMediaElement, audioContext: AudioContext): MediaElementAudioSourceNode {\r\n return audioContext.createMediaElementSource(audioSource)\r\n}"],"names":["REPEAT_NAME_REGEX","RegExp","RepeatTrackProcessor","deleteOriginTrack","matches","name","match","process","track","Error","repeatTrackArgs","split","length","startTime","parseFloat","interval","endTime","time","tempTrack","toString","Math","random","substring","dbToVolume","db","pow","Misairu","volume","_volume","this","_muted","_gainNode","gain","value","constructor","audioSource","timings","_audioContext","_audioSource","_cache","_eventHandler","_paused","_startTime","_timings","_processors","console","error","AudioContext","createGain","connect","destination","getOptimalAudioSource","processTracks","audioContext","source","createBufferSource","response","fetch","arrayBuffer","buffer","decodeAudioData","fetchAudioSource","then","document","dispatchEvent","Event","tagName","createMediaElementSource","attachAudioElementSource","mute","unmute","pause","suspend","unpause","resume","setCacheEntry","entry","getCacheEntry","getAllTracks","Object","keys","getActiveTimingKey","currentTime","activeTimings","filter","timing","forEach","trackName","processor","processedTrackName","eventTrack","start","startEventHandling","window","requestAnimationFrame","handleEvents","timingKey","executeEvent"],"mappings":"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"}
|
package/dist/misairu.module.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var t=function(){function t(t,e){this._audioContext=void 0,this.
|
|
1
|
+
var t=new RegExp(/repeat:\d:\d:\d/),e=function(){function e(){this.deleteOriginTrack=!0}var n=e.prototype;return n.matches=function(e){return null!==e.match(t)},n.process=function(t,e){if("function"!=typeof e)throw Error('The value of repeat track "'+e+'" is not a function');var n=t.split(":");if(4!=n.length)throw Error('The repeat track "'+t+'" does not supply the valid amount of arguments');var i=parseFloat(n[1]),o=parseFloat(n[2]),r=parseFloat(n[3]),s=i,a={};do{a[s.toString()]=e,s+=o}while(s<r);return["repeat-"+Math.random().toString(36).substring(7),a]},e}();function n(t){return Math.pow(10,t/20)}var i=function(){function t(t,n){this._audioContext=void 0,this._audioSource=null,this._cache={},this._eventHandler=null,this._gainNode=void 0,this._muted=!1,this._paused=!1,this._startTime=0,this._timings=void 0,this._volume=0,this._processors=[new e],null===n&&console.error("You need to specify a timings object"),this._timings=n,this._audioContext=new AudioContext,this._gainNode=this._audioContext.createGain(),this._gainNode.connect(this._audioContext.destination),this.getOptimalAudioSource(t),this.processTracks()}var i,o=t.prototype;return o.getOptimalAudioSource=function(t){var e=this;"string"==typeof t?function(t,e){try{var n=e.createBufferSource();return Promise.resolve(fetch(t)).then(function(t){return Promise.resolve(t.arrayBuffer()).then(function(t){return Promise.resolve(e.decodeAudioData(t)).then(function(t){return n.buffer=t,n})})})}catch(t){return Promise.reject(t)}}(t,this._audioContext).then(function(t){e._audioSource=t,e._audioSource.connect(e._gainNode),document.dispatchEvent(new Event("misairu.ready"))}):"object"!=typeof t||"AUDIO"!=t.tagName&&"VIDEO"!=t.tagName||(this._audioSource=function(t,e){return e.createMediaElementSource(t)}(t,this._audioContext),this._audioSource.connect(this._gainNode))},o.mute=function(){this._muted||(this._muted=!0,this._gainNode.gain.value=0)},o.unmute=function(){this._muted&&(this._muted=!1,this._gainNode.gain.value=n(this._volume))},o.pause=function(){this._paused||(this._audioContext.suspend(),this._paused=!0)},o.unpause=function(){this._paused&&(this._audioContext.resume(),this._paused=!1)},o.setCacheEntry=function(t,e){this._cache[t]=e},o.getCacheEntry=function(t){return this._cache[t]},o.getAllTracks=function(){return Object.keys(this._timings)},o.getActiveTimingKey=function(t,e){var n=Object.keys(this._timings[t]).filter(function(t){if(e>=parseFloat(t))return!0});return n[n.length-1]},o.processTracks=function(){var t=this;this.getAllTracks().forEach(function(e){t._processors.forEach(function(n){if(n.matches(e)){var i=n.process(e,t._timings[e]);t._timings[i[0]]=i[1],n.deleteOriginTrack&&delete t._timings[e]}})})},o.start=function(){this._startTime=this._audioContext.currentTime,this._audioSource.start(),this.startEventHandling()},o.startEventHandling=function(){var t=this;null==this._eventHandler&&(this._eventHandler=window.requestAnimationFrame(function(){t.handleEvents()}))},o.handleEvents=function(){var t=this,e=this._audioContext.currentTime-this._startTime;this.getAllTracks().forEach(function(n){var i=t.getActiveTimingKey(n,e);null!==i&&t.getCacheEntry(n)!=i&&(t.executeEvent(n,i,e),t.setCacheEntry(n,i))}),this._eventHandler=window.requestAnimationFrame(function(){t.handleEvents()})},o.executeEvent=function(t,e,n){this._timings[t][e](this,e,t,n)},(i=[{key:"volume",get:function(){return this._volume},set:function(t){var e;this._volume=(e=t)<-80?-80:e>5?5:e,this._muted||(this._gainNode.gain.value=n(this._volume))}}])&&function(t,e){for(var n=0;n<e.length;n++){var i=e[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,i.key,i)}}(t.prototype,i),t}();export{i as default};
|
|
2
2
|
//# sourceMappingURL=misairu.module.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"misairu.module.js","sources":["../src/misairu.ts"],"sourcesContent":["import { EventCache, TimingObject } from './types'\r\n\r\n/**\r\n * Main misairu class\r\n */\r\nexport class Misairu {\r\n /**\r\n * The HTML5 AudioContext used for accurately timing our events\r\n */\r\n private readonly _audioContext: AudioContext\r\n\r\n /**\r\n * If the audio source is a HTML5 media element (either a <audio> or <video> tag), this will be a reference to it\r\n *\r\n * @default null\r\n */\r\n private _audioElement: HTMLMediaElement | null = null\r\n\r\n /**\r\n * A source node to play our audio from, either a buffered source from a downloaded media file or a media element source\r\n *\r\n * @default null\r\n */\r\n private _audioSource: AudioBufferSourceNode | MediaElementAudioSourceNode | null = null\r\n\r\n /**\r\n * A object containing the last executed time key per track to not execute an event on every tick\r\n *\r\n * @default {}\r\n */\r\n private _cache: EventCache = {}\r\n\r\n /**\r\n * Reference to the `requestAnimationFrame` handler\r\n *\r\n * @default null\r\n */\r\n private _eventHandler: number | null = null\r\n\r\n /**\r\n * Gain node from our audio context to control audio volume\r\n */\r\n private readonly _gainNode: GainNode\r\n\r\n /**\r\n * Boolean value describing if this instance is currently muted\r\n */\r\n private _muted = false\r\n\r\n /**\r\n * Boolean value describing if this instance is currently paused\r\n */\r\n private _paused = false\r\n\r\n /**\r\n * The time when event handling was started, based on the audio contexts `currentTime` when `start()` was called\r\n */\r\n private _startTime = 0\r\n\r\n /**\r\n * Object containing all timing tracks and events to be executed\r\n */\r\n private _timings: TimingObject\r\n\r\n /**\r\n * Volume of the current instance\r\n */\r\n private _volume = 0\r\n\r\n get volume(): number {\r\n return this._volume\r\n }\r\n\r\n set volume(db: number) {\r\n this._volume = db\r\n this.clampGain()\r\n\r\n if (!this._muted) {\r\n this._gainNode.gain.value = this.dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * misairu constructor\r\n *\r\n * @param audioSource a string or HTML element to be used as audio source\r\n * @param timings a object containing event timing information\r\n */\r\n constructor(audioSource: string | HTMLMediaElement, timings: TimingObject) {\r\n if (timings === null) console.error('You need to specify a timings object')\r\n this._timings = timings\r\n\r\n this._audioContext = new AudioContext()\r\n\r\n this._gainNode = this._audioContext.createGain()\r\n this._gainNode.connect(this._audioContext.destination)\r\n\r\n this.getOptimalAudioSource(audioSource)\r\n\r\n this.compile()\r\n }\r\n\r\n /**\r\n * Method to figure out the best course of action to take with the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private getOptimalAudioSource(audioSource: string | HTMLMediaElement): void {\r\n if (typeof audioSource == 'string') {\r\n this.fetchAudioSource(audioSource)\r\n } else if (\r\n typeof audioSource == 'object' &&\r\n (audioSource.tagName == 'AUDIO' || audioSource.tagName == 'VIDEO')\r\n ) {\r\n this.attachAudioElementSource(audioSource)\r\n }\r\n }\r\n\r\n /**\r\n * Method to fetch the external audio file (if the audio source parameter was a string)\r\n * and turning it into a `AudioBufferSourceNode`\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private fetchAudioSource(audioSource: string): void {\r\n const source = this._audioContext.createBufferSource()\r\n\r\n fetch(audioSource)\r\n .then((response) => {\r\n return response.arrayBuffer()\r\n })\r\n .then((arrayBuffer) => {\r\n this._audioContext.decodeAudioData(arrayBuffer).then((buffer) => {\r\n source.buffer = buffer\r\n source.connect(this._gainNode)\r\n\r\n document.dispatchEvent(new Event('misairu.ready'))\r\n this._audioSource = source\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Method to get an `MediaElementAudioSourceNode` from the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private attachAudioElementSource(audioSource: HTMLMediaElement): void {\r\n const source = this._audioContext.createMediaElementSource(audioSource)\r\n this._audioElement = audioSource\r\n\r\n source.connect(this._gainNode)\r\n this._audioSource = source\r\n }\r\n\r\n /**\r\n * Mutes the instance audio\r\n *\r\n * @public\r\n */\r\n public mute(): void {\r\n if (!this._muted) {\r\n this._muted = true\r\n this._gainNode.gain.value = 0\r\n }\r\n }\r\n\r\n /**\r\n * Unmutes the instance audio\r\n *\r\n * @public\r\n */\r\n public unmute(): void {\r\n if (this._muted) {\r\n this._muted = false\r\n this._gainNode.gain.value = this.dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * Pauses instance playback\r\n *\r\n * @public\r\n */\r\n public pause(): void {\r\n if (!this._paused) {\r\n this._audioContext.suspend()\r\n this._paused = true\r\n }\r\n }\r\n\r\n /**\r\n * Resumes instance playback\r\n *\r\n * @public\r\n */\r\n public unpause(): void {\r\n if (this._paused) {\r\n this._audioContext.resume()\r\n this._paused = false\r\n }\r\n }\r\n\r\n /**\r\n * Clamps the volume to a min/max value to prevent accidental oversetting to way too loud measures\r\n *\r\n * @internal\r\n */\r\n private clampGain(): void {\r\n if (this._volume < -80) {\r\n this._volume = -80\r\n } else if (this._volume > 5) {\r\n this._volume = 5\r\n }\r\n }\r\n\r\n /**\r\n * Method to turn the passed decibel values into volume values for the audio playback\r\n *\r\n * @param db decibel value\r\n * @returns volume value\r\n * @internal\r\n */\r\n private dbToVolume(db: number): number {\r\n return Math.pow(10, db / 20)\r\n }\r\n\r\n /**\r\n * Set a cache entry for the given track\r\n *\r\n * @param track track to set a cache entry for\r\n * @param entry value of the cache entry\r\n * @internal\r\n */\r\n private setCacheEntry(track: string, entry: string): void {\r\n this._cache[track] = entry\r\n }\r\n\r\n /**\r\n * Get cache entry for the given track\r\n *\r\n * @param track track to get a cache entry for\r\n * @returns a cache entry\r\n * @internal\r\n */\r\n private getCacheEntry(track: string): string {\r\n return this._cache[track]\r\n }\r\n\r\n /**\r\n * Get all tracks from the timing configuration\r\n *\r\n * @returns a list of all track names\r\n * @internal\r\n */\r\n private getAllTracks(): string[] {\r\n return Object.keys(this._timings)\r\n }\r\n\r\n /**\r\n * Returns the current active timing key for a given track\r\n *\r\n * @param track track to get the timing key from\r\n * @param currentTime current playback time\r\n * @returns the current active timing key\r\n * @internal\r\n */\r\n private getActiveTimingKey(track: string, currentTime: number): string {\r\n const timingKeys = Object.keys(this._timings[track])\r\n\r\n const activeTimings = timingKeys.filter((timing) => {\r\n if (currentTime >= parseFloat(timing)) {\r\n return true\r\n }\r\n })\r\n\r\n return activeTimings[activeTimings.length - 1]\r\n }\r\n\r\n /**\r\n * Main method to compile special sections in the timing configuration\r\n *\r\n * @internal\r\n */\r\n private compile(): void {\r\n this.getAllTracks().forEach((track) => {\r\n if (track.startsWith('repeat:')) {\r\n this.compileRepeat(track)\r\n }\r\n })\r\n }\r\n\r\n /**\r\n * Method to compile tracks whose name starts with `repeat:`\r\n *\r\n * @param track track to compile the repeat method for\r\n * @internal\r\n */\r\n private compileRepeat(track: string): void {\r\n if (typeof this._timings[track] != 'function')\r\n throw Error(`The value of repeat track \"${track}\" is not a function`)\r\n\r\n const repeatTrackArgs = track.split(':')\r\n\r\n if (repeatTrackArgs.length != 4)\r\n throw Error(`The repeat track \"${track}\" does not supply the valid amount of arguments`)\r\n\r\n const startTime = parseFloat(repeatTrackArgs[1])\r\n const interval = parseFloat(repeatTrackArgs[2])\r\n const endTime = parseFloat(repeatTrackArgs[3])\r\n\r\n let time = startTime\r\n const tempTrack = {}\r\n\r\n do {\r\n tempTrack[time.toString()] = this._timings[track]\r\n time += interval\r\n } while (time < endTime)\r\n\r\n delete this._timings[track]\r\n\r\n this._timings[`repeat-${Math.random().toString(36).substring(7)}`] = tempTrack\r\n }\r\n\r\n /**\r\n * Start audio playback and event handling\r\n *\r\n * @public\r\n */\r\n public start(): void {\r\n this._startTime = this._audioContext.currentTime\r\n\r\n if (this._audioElement !== null) {\r\n this._audioElement.play()\r\n } else {\r\n ;(this._audioSource as AudioBufferSourceNode).start()\r\n }\r\n\r\n this.startEventHandling()\r\n }\r\n\r\n /**\r\n * Method to start event handler loop\r\n *\r\n * @internal\r\n */\r\n private startEventHandling(): void {\r\n if (this._eventHandler == null) {\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Event handler method, is running in a loop using `requestAnimationFrame`\r\n *\r\n * @internal\r\n */\r\n private handleEvents(): void {\r\n const time = this._audioContext.currentTime - this._startTime\r\n\r\n this.getAllTracks().forEach((track) => {\r\n const timingKey = this.getActiveTimingKey(track, time)\r\n\r\n if (timingKey !== null && !(this.getCacheEntry(track) == timingKey)) {\r\n this.executeEvent(track, timingKey, time)\r\n this.setCacheEntry(track, timingKey)\r\n }\r\n })\r\n\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n\r\n /**\r\n * Method to execute the event for a given timing key on a given track\r\n *\r\n * @param track the track to execute the event on\r\n * @param timingKey the timing key to execute\r\n * @param time current playback time\r\n * @internal\r\n */\r\n private executeEvent(track: string, timingKey: string, time: number): void {\r\n this._timings[track][timingKey](this, timingKey, track, time)\r\n }\r\n}\r\n"],"names":["Misairu","audioSource","timings","_audioContext","_audioElement","_audioSource","_cache","_eventHandler","_gainNode","_muted","_paused","_startTime","_timings","_volume","console","error","this","AudioContext","createGain","connect","destination","getOptimalAudioSource","compile","fetchAudioSource","tagName","attachAudioElementSource","source","createBufferSource","fetch","then","response","arrayBuffer","_this","decodeAudioData","buffer","document","dispatchEvent","Event","createMediaElementSource","mute","gain","value","unmute","dbToVolume","pause","suspend","unpause","resume","clampGain","db","Math","pow","setCacheEntry","track","entry","getCacheEntry","getAllTracks","Object","keys","getActiveTimingKey","currentTime","activeTimings","filter","timing","parseFloat","length","forEach","startsWith","_this2","compileRepeat","Error","repeatTrackArgs","split","startTime","interval","endTime","time","tempTrack","toString","random","substring","start","play","startEventHandling","window","requestAnimationFrame","_this3","handleEvents","timingKey","_this4","executeEvent"],"mappings":"AAKaA,IAAAA,aAmFX,WAAYC,EAAwCC,QA/EnCC,0BAOTC,cAAyC,UAOzCC,aAA2E,UAO3EC,OAAqB,QAOrBC,cAA+B,UAKtBC,sBAKTC,QAAS,OAKTC,SAAU,OAKVC,WAAa,OAKbC,qBAKAC,QAAU,EAsBA,OAAZX,GAAkBY,QAAQC,MAAM,wCACpCC,KAAKJ,SAAWV,EAEhBc,KAAKb,cAAgB,IAAIc,aAEzBD,KAAKR,UAAYQ,KAAKb,cAAce,aACpCF,KAAKR,UAAUW,QAAQH,KAAKb,cAAciB,aAE1CJ,KAAKK,sBAAsBpB,GAE3Be,KAAKM,UA9FT,6BAuGUD,sBAAA,SAAsBpB,GACF,iBAAfA,EACTe,KAAKO,iBAAiBtB,GAEA,iBAAfA,GACiB,SAAvBA,EAAYuB,SAA6C,SAAvBvB,EAAYuB,SAE/CR,KAAKS,yBAAyBxB,MAW1BsB,iBAAA,SAAiBtB,cACjByB,EAASV,KAAKb,cAAcwB,qBAElCC,MAAM3B,GACH4B,KAAK,SAACC,GACL,OAAOA,EAASC,gBAEjBF,KAAK,SAACE,GACLC,EAAK7B,cAAc8B,gBAAgBF,GAAaF,KAAK,SAACK,GACpDR,EAAOQ,OAASA,EAChBR,EAAOP,QAAQa,EAAKxB,WAEpB2B,SAASC,cAAc,IAAIC,MAAM,kBACjCL,EAAK3B,aAAeqB,SAWpBD,yBAAA,SAAyBxB,GAC/B,IAAMyB,EAASV,KAAKb,cAAcmC,yBAAyBrC,GAC3De,KAAKZ,cAAgBH,EAErByB,EAAOP,QAAQH,KAAKR,WACpBQ,KAAKX,aAAeqB,KAQfa,KAAA,WACAvB,KAAKP,SACRO,KAAKP,QAAS,EACdO,KAAKR,UAAUgC,KAAKC,MAAQ,MASzBC,OAAA,WACD1B,KAAKP,SACPO,KAAKP,QAAS,EACdO,KAAKR,UAAUgC,KAAKC,MAAQzB,KAAK2B,WAAW3B,KAAKH,aAS9C+B,MAAA,WACA5B,KAAKN,UACRM,KAAKb,cAAc0C,UACnB7B,KAAKN,SAAU,MASZoC,QAAA,WACD9B,KAAKN,UACPM,KAAKb,cAAc4C,SACnB/B,KAAKN,SAAU,MASXsC,UAAA,WACFhC,KAAKH,SAAW,GAClBG,KAAKH,SAAW,GACPG,KAAKH,QAAU,IACxBG,KAAKH,QAAU,MAWX8B,WAAA,SAAWM,GACjB,OAAOC,KAAKC,IAAI,GAAIF,EAAK,OAUnBG,cAAA,SAAcC,EAAeC,GACnCtC,KAAKV,OAAO+C,GAASC,KAUfC,cAAA,SAAcF,GACpB,YAAY/C,OAAO+C,MASbG,aAAA,WACN,OAAOC,OAAOC,KAAK1C,KAAKJ,aAWlB+C,mBAAA,SAAmBN,EAAeO,GACxC,IAEMC,EAFaJ,OAAOC,KAAK1C,KAAKJ,SAASyC,IAEZS,OAAO,SAACC,GACvC,GAAIH,GAAeI,WAAWD,GAC5B,WAIJ,OAAOF,EAAcA,EAAcI,OAAS,MAQtC3C,QAAA,sBACNN,KAAKwC,eAAeU,QAAQ,SAACb,GACvBA,EAAMc,WAAW,YACnBC,EAAKC,cAAchB,QAWjBgB,cAAA,SAAchB,GACpB,GAAmC,wBAAnBzC,SAASyC,GACvB,MAAMiB,oCAAoCjB,yBAE5C,IAAMkB,EAAkBlB,EAAMmB,MAAM,KAEpC,GAA8B,GAA1BD,EAAgBN,OAClB,MAAMK,2BAA2BjB,qDAEnC,IAAMoB,EAAYT,WAAWO,EAAgB,IACvCG,EAAWV,WAAWO,EAAgB,IACtCI,EAAUX,WAAWO,EAAgB,IAEvCK,EAAOH,EACLI,EAAY,GAElB,GACEA,EAAUD,EAAKE,YAAc9D,KAAKJ,SAASyC,GAC3CuB,GAAQF,QACDE,EAAOD,eAEJ/D,SAASyC,GAErBrC,KAAKJ,mBAAmBsC,KAAK6B,SAASD,SAAS,IAAIE,UAAU,IAAQH,KAQhEI,MAAA,WACLjE,KAAKL,WAAaK,KAAKb,cAAcyD,YAEV,OAAvB5C,KAAKZ,cACPY,KAAKZ,cAAc8E,OAEjBlE,KAAKX,aAAuC4E,QAGhDjE,KAAKmE,wBAQCA,mBAAA,sBACoB,MAAtBnE,KAAKT,gBACPS,KAAKT,cAAgB6E,OAAOC,sBAAsB,WAChDC,EAAKC,qBAUHA,aAAA,sBACAX,EAAO5D,KAAKb,cAAcyD,YAAc5C,KAAKL,WAEnDK,KAAKwC,eAAeU,QAAQ,SAACb,GAC3B,IAAMmC,EAAYC,EAAK9B,mBAAmBN,EAAOuB,GAE/B,OAAdY,GAAwBC,EAAKlC,cAAcF,IAAUmC,IACvDC,EAAKC,aAAarC,EAAOmC,EAAWZ,GACpCa,EAAKrC,cAAcC,EAAOmC,MAI9BxE,KAAKT,cAAgB6E,OAAOC,sBAAsB,WAChDI,EAAKF,oBAYDG,aAAA,SAAarC,EAAemC,EAAmBZ,GACrD5D,KAAKJ,SAASyC,GAAOmC,GAAWxE,KAAMwE,EAAWnC,EAAOuB,0BA/T1D,WACE,YAAY/D,aAGd,SAAWoC,GACTjC,KAAKH,QAAUoC,EACfjC,KAAKgC,YAEAhC,KAAKP,SACRO,KAAKR,UAAUgC,KAAKC,MAAQzB,KAAK2B,WAAW3B,KAAKH"}
|
|
1
|
+
{"version":3,"file":"misairu.module.js","sources":["../src/processors/repeat.ts","../src/utilities/audio.ts","../src/misairu.ts","../src/utilities/source.ts"],"sourcesContent":["import { EventFunction, EventTrack, ITrackProcessor } from \"../types\";\r\n\r\nconst REPEAT_NAME_REGEX = new RegExp(/repeat:\\d:\\d:\\d/)\r\n\r\nexport class RepeatTrackProcessor implements ITrackProcessor {\r\n deleteOriginTrack = true;\r\n\r\n matches(name: string): boolean {\r\n return name.match(REPEAT_NAME_REGEX) !== null\r\n }\r\n\r\n process(name: string, track: EventFunction): [string, EventTrack] {\r\n if (typeof track != 'function')\r\n throw Error(`The value of repeat track \"${track}\" is not a function`)\r\n\r\n const repeatTrackArgs = name.split(':')\r\n\r\n if (repeatTrackArgs.length != 4)\r\n throw Error(`The repeat track \"${name}\" does not supply the valid amount of arguments`)\r\n\r\n const startTime = parseFloat(repeatTrackArgs[1])\r\n const interval = parseFloat(repeatTrackArgs[2])\r\n const endTime = parseFloat(repeatTrackArgs[3])\r\n\r\n let time = startTime\r\n const tempTrack = {}\r\n\r\n do {\r\n tempTrack[time.toString()] = track\r\n time += interval\r\n } while (time < endTime)\r\n\r\n return [`repeat-${Math.random().toString(36).substring(7)}`, tempTrack]\r\n }\r\n}","/**\r\n * Method to turn the passed decibel values into volume values for the audio playback\r\n *\r\n * @param db decibel value\r\n * @returns volume value\r\n */\r\nexport function dbToVolume(db: number): number {\r\n return Math.pow(10, db / 20)\r\n}\r\n\r\n/**\r\n * Clamps the volume to a min/max value to prevent accidental oversetting to way too loud measures\r\n */\r\nexport function clampGain(volume: number): number {\r\n if (volume < -80) {\r\n return -80\r\n } else if (volume > 5) {\r\n return 5\r\n }\r\n\r\n return volume\r\n}","import { RepeatTrackProcessor } from './processors/repeat'\r\nimport { EventCache, ITrackProcessor, TimingObject } from './types'\r\nimport { dbToVolume, clampGain } from './utilities/audio'\r\nimport { fetchAudioSource, attachAudioElementSource } from './utilities/source'\r\n\r\n/**\r\n * Main misairu class\r\n */\r\nexport class Misairu {\r\n /**\r\n * The HTML5 AudioContext used for accurately timing our events\r\n */\r\n private readonly _audioContext: AudioContext\r\n\r\n /**\r\n * A source node to play our audio from, either a buffered source from a downloaded media file or a media element source\r\n *\r\n * @default null\r\n */\r\n private _audioSource: HTMLMediaElement | AudioBufferSourceNode | MediaElementAudioSourceNode | null = null\r\n\r\n /**\r\n * A object containing the last executed time key per track to not execute an event on every tick\r\n *\r\n * @default {}\r\n */\r\n private _cache: EventCache = {}\r\n\r\n /**\r\n * Reference to the `requestAnimationFrame` handler\r\n *\r\n * @default null\r\n */\r\n private _eventHandler: number | null = null\r\n\r\n /**\r\n * Gain node from our audio context to control audio volume\r\n */\r\n private readonly _gainNode: GainNode\r\n\r\n /**\r\n * Boolean value describing if this instance is currently muted\r\n */\r\n private _muted = false\r\n\r\n /**\r\n * Boolean value describing if this instance is currently paused\r\n */\r\n private _paused = false\r\n\r\n /**\r\n * The time when event handling was started, based on the audio contexts `currentTime` when `start()` was called\r\n */\r\n private _startTime = 0\r\n\r\n /**\r\n * Object containing all timing tracks and events to be executed\r\n */\r\n private _timings: TimingObject\r\n\r\n /**\r\n * Volume of the current instance\r\n */\r\n private _volume = 0\r\n\r\n /**\r\n * List of (predefined) track processors\r\n */\r\n private _processors: ITrackProcessor[] = [\r\n new RepeatTrackProcessor()\r\n ]\r\n\r\n get volume(): number {\r\n return this._volume\r\n }\r\n\r\n set volume(db: number) {\r\n this._volume = clampGain(db)\r\n\r\n if (!this._muted) {\r\n this._gainNode.gain.value = dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * misairu constructor\r\n *\r\n * @param audioSource a string or HTML element to be used as audio source\r\n * @param timings a object containing event timing information\r\n */\r\n constructor(audioSource: string | HTMLMediaElement, timings: TimingObject) {\r\n if (timings === null) console.error('You need to specify a timings object')\r\n this._timings = timings\r\n\r\n this._audioContext = new AudioContext()\r\n\r\n this._gainNode = this._audioContext.createGain()\r\n this._gainNode.connect(this._audioContext.destination)\r\n\r\n this.getOptimalAudioSource(audioSource)\r\n\r\n this.processTracks()\r\n }\r\n\r\n /**\r\n * Method to figure out the best course of action to take with the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private getOptimalAudioSource(audioSource: string | HTMLMediaElement): void {\r\n if (typeof audioSource == 'string') {\r\n fetchAudioSource(audioSource, this._audioContext).then((audioSource) => {\r\n this._audioSource = audioSource\r\n this._audioSource.connect(this._gainNode)\r\n\r\n document.dispatchEvent(new Event('misairu.ready'))\r\n })\r\n } else if (\r\n typeof audioSource == 'object' &&\r\n (audioSource.tagName == 'AUDIO' || audioSource.tagName == 'VIDEO')\r\n ) {\r\n this._audioSource = attachAudioElementSource(audioSource, this._audioContext)\r\n this._audioSource.connect(this._gainNode)\r\n }\r\n }\r\n\r\n /**\r\n * Mutes the instance audio\r\n *\r\n * @public\r\n */\r\n public mute(): void {\r\n if (!this._muted) {\r\n this._muted = true\r\n this._gainNode.gain.value = 0\r\n }\r\n }\r\n\r\n /**\r\n * Unmutes the instance audio\r\n *\r\n * @public\r\n */\r\n public unmute(): void {\r\n if (this._muted) {\r\n this._muted = false\r\n this._gainNode.gain.value = dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * Pauses instance playback\r\n *\r\n * @public\r\n */\r\n public pause(): void {\r\n if (!this._paused) {\r\n this._audioContext.suspend()\r\n this._paused = true\r\n }\r\n }\r\n\r\n /**\r\n * Resumes instance playback\r\n *\r\n * @public\r\n */\r\n public unpause(): void {\r\n if (this._paused) {\r\n this._audioContext.resume()\r\n this._paused = false\r\n }\r\n }\r\n\r\n /**\r\n * Set a cache entry for the given track\r\n *\r\n * @param track track to set a cache entry for\r\n * @param entry value of the cache entry\r\n * @internal\r\n */\r\n private setCacheEntry(track: string, entry: string): void {\r\n this._cache[track] = entry\r\n }\r\n\r\n /**\r\n * Get cache entry for the given track\r\n *\r\n * @param track track to get a cache entry for\r\n * @returns a cache entry\r\n * @internal\r\n */\r\n private getCacheEntry(track: string): string {\r\n return this._cache[track]\r\n }\r\n\r\n /**\r\n * Get all tracks from the timing configuration\r\n *\r\n * @returns a list of all track names\r\n * @internal\r\n */\r\n private getAllTracks(): string[] {\r\n return Object.keys(this._timings)\r\n }\r\n\r\n /**\r\n * Returns the current active timing key for a given track\r\n *\r\n * @param track track to get the timing key from\r\n * @param currentTime current playback time\r\n * @returns the current active timing key\r\n * @internal\r\n */\r\n private getActiveTimingKey(track: string, currentTime: number): string {\r\n const timingKeys = Object.keys(this._timings[track])\r\n\r\n const activeTimings = timingKeys.filter((timing) => {\r\n if (currentTime >= parseFloat(timing)) {\r\n return true\r\n }\r\n })\r\n\r\n return activeTimings[activeTimings.length - 1]\r\n }\r\n\r\n /**\r\n * Main method to process special sections in the timing configuration\r\n *\r\n * @internal\r\n */\r\n private processTracks(): void {\r\n this.getAllTracks().forEach((trackName) => {\r\n this._processors.forEach((processor: ITrackProcessor) => {\r\n if (processor.matches(trackName)) {\r\n const [processedTrackName, eventTrack] = processor.process(trackName, this._timings[trackName])\r\n\r\n this._timings[processedTrackName] = eventTrack\r\n\r\n if (processor.deleteOriginTrack) {\r\n delete this._timings[trackName]\r\n }\r\n }\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Start audio playback and event handling\r\n *\r\n * @public\r\n */\r\n public start(): void {\r\n this._startTime = this._audioContext.currentTime\r\n\r\n ;(this._audioSource as AudioBufferSourceNode).start()\r\n\r\n this.startEventHandling()\r\n }\r\n\r\n /**\r\n * Method to start event handler loop\r\n *\r\n * @internal\r\n */\r\n private startEventHandling(): void {\r\n if (this._eventHandler == null) {\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Event handler method, is running in a loop using `requestAnimationFrame`\r\n *\r\n * @internal\r\n */\r\n private handleEvents(): void {\r\n const time = this._audioContext.currentTime - this._startTime\r\n\r\n this.getAllTracks().forEach((track) => {\r\n const timingKey = this.getActiveTimingKey(track, time)\r\n\r\n if (timingKey !== null && !(this.getCacheEntry(track) == timingKey)) {\r\n this.executeEvent(track, timingKey, time)\r\n this.setCacheEntry(track, timingKey)\r\n }\r\n })\r\n\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n\r\n /**\r\n * Method to execute the event for a given timing key on a given track\r\n *\r\n * @param track the track to execute the event on\r\n * @param timingKey the timing key to execute\r\n * @param time current playback time\r\n * @internal\r\n */\r\n private executeEvent(track: string, timingKey: string, time: number): void {\r\n this._timings[track][timingKey](this, timingKey, track, time)\r\n }\r\n}\r\n","/**\r\n * Method to fetch the external audio file (if the audio source parameter was a string)\r\n * and turning it into a `AudioBufferSourceNode`\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\nexport async function fetchAudioSource(audioSource: string, audioContext: AudioContext): Promise<AudioBufferSourceNode> {\r\n const source = audioContext.createBufferSource()\r\n\r\n const response = await fetch(audioSource)\r\n const arrayBuffer = await response.arrayBuffer()\r\n const buffer = await audioContext.decodeAudioData(arrayBuffer)\r\n\r\n source.buffer = buffer\r\n\r\n return source\r\n}\r\n\r\n/**\r\n * Method to get an `MediaElementAudioSourceNode` from the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\nexport function attachAudioElementSource(audioSource: HTMLMediaElement, audioContext: AudioContext): MediaElementAudioSourceNode {\r\n return audioContext.createMediaElementSource(audioSource)\r\n}"],"names":["REPEAT_NAME_REGEX","RegExp","RepeatTrackProcessor","deleteOriginTrack","matches","name","match","process","track","Error","repeatTrackArgs","split","length","startTime","parseFloat","interval","endTime","time","tempTrack","toString","Math","random","substring","dbToVolume","db","pow","Misairu","audioSource","timings","_audioContext","_audioSource","_cache","_eventHandler","_gainNode","_muted","_paused","_startTime","_timings","_volume","_processors","console","error","this","AudioContext","createGain","connect","destination","getOptimalAudioSource","processTracks","audioContext","source","createBufferSource","fetch","response","arrayBuffer","decodeAudioData","buffer","fetchAudioSource","then","_this","document","dispatchEvent","Event","tagName","createMediaElementSource","attachAudioElementSource","mute","gain","value","unmute","pause","suspend","unpause","resume","setCacheEntry","entry","getCacheEntry","getAllTracks","Object","keys","getActiveTimingKey","currentTime","activeTimings","filter","timing","forEach","trackName","_this2","processor","start","startEventHandling","window","requestAnimationFrame","_this3","handleEvents","timingKey","_this4","executeEvent","volume"],"mappings":"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"}
|
package/dist/misairu.umd.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t||self).misairu=e()}(this,function(){return function(){function t(t,e){this._audioContext=void 0,this.
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t||self).misairu=e()}(this,function(){var t=new RegExp(/repeat:\d:\d:\d/),e=function(){function e(){this.deleteOriginTrack=!0}var n=e.prototype;return n.matches=function(e){return null!==e.match(t)},n.process=function(t,e){if("function"!=typeof e)throw Error('The value of repeat track "'+e+'" is not a function');var n=t.split(":");if(4!=n.length)throw Error('The repeat track "'+t+'" does not supply the valid amount of arguments');var i=parseFloat(n[1]),o=parseFloat(n[2]),r=parseFloat(n[3]),s=i,u={};do{u[s.toString()]=e,s+=o}while(s<r);return["repeat-"+Math.random().toString(36).substring(7),u]},e}();function n(t){return Math.pow(10,t/20)}return function(){function t(t,n){this._audioContext=void 0,this._audioSource=null,this._cache={},this._eventHandler=null,this._gainNode=void 0,this._muted=!1,this._paused=!1,this._startTime=0,this._timings=void 0,this._volume=0,this._processors=[new e],null===n&&console.error("You need to specify a timings object"),this._timings=n,this._audioContext=new AudioContext,this._gainNode=this._audioContext.createGain(),this._gainNode.connect(this._audioContext.destination),this.getOptimalAudioSource(t),this.processTracks()}var i,o=t.prototype;return o.getOptimalAudioSource=function(t){var e=this;"string"==typeof t?function(t,e){try{var n=e.createBufferSource();return Promise.resolve(fetch(t)).then(function(t){return Promise.resolve(t.arrayBuffer()).then(function(t){return Promise.resolve(e.decodeAudioData(t)).then(function(t){return n.buffer=t,n})})})}catch(t){return Promise.reject(t)}}(t,this._audioContext).then(function(t){e._audioSource=t,e._audioSource.connect(e._gainNode),document.dispatchEvent(new Event("misairu.ready"))}):"object"!=typeof t||"AUDIO"!=t.tagName&&"VIDEO"!=t.tagName||(this._audioSource=function(t,e){return e.createMediaElementSource(t)}(t,this._audioContext),this._audioSource.connect(this._gainNode))},o.mute=function(){this._muted||(this._muted=!0,this._gainNode.gain.value=0)},o.unmute=function(){this._muted&&(this._muted=!1,this._gainNode.gain.value=n(this._volume))},o.pause=function(){this._paused||(this._audioContext.suspend(),this._paused=!0)},o.unpause=function(){this._paused&&(this._audioContext.resume(),this._paused=!1)},o.setCacheEntry=function(t,e){this._cache[t]=e},o.getCacheEntry=function(t){return this._cache[t]},o.getAllTracks=function(){return Object.keys(this._timings)},o.getActiveTimingKey=function(t,e){var n=Object.keys(this._timings[t]).filter(function(t){if(e>=parseFloat(t))return!0});return n[n.length-1]},o.processTracks=function(){var t=this;this.getAllTracks().forEach(function(e){t._processors.forEach(function(n){if(n.matches(e)){var i=n.process(e,t._timings[e]);t._timings[i[0]]=i[1],n.deleteOriginTrack&&delete t._timings[e]}})})},o.start=function(){this._startTime=this._audioContext.currentTime,this._audioSource.start(),this.startEventHandling()},o.startEventHandling=function(){var t=this;null==this._eventHandler&&(this._eventHandler=window.requestAnimationFrame(function(){t.handleEvents()}))},o.handleEvents=function(){var t=this,e=this._audioContext.currentTime-this._startTime;this.getAllTracks().forEach(function(n){var i=t.getActiveTimingKey(n,e);null!==i&&t.getCacheEntry(n)!=i&&(t.executeEvent(n,i,e),t.setCacheEntry(n,i))}),this._eventHandler=window.requestAnimationFrame(function(){t.handleEvents()})},o.executeEvent=function(t,e,n){this._timings[t][e](this,e,t,n)},(i=[{key:"volume",get:function(){return this._volume},set:function(t){var e;this._volume=(e=t)<-80?-80:e>5?5:e,this._muted||(this._gainNode.gain.value=n(this._volume))}}])&&function(t,e){for(var n=0;n<e.length;n++){var i=e[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,i.key,i)}}(t.prototype,i),t}()});
|
|
2
2
|
//# sourceMappingURL=misairu.umd.js.map
|
package/dist/misairu.umd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"misairu.umd.js","sources":["../src/misairu.ts"],"sourcesContent":["import { EventCache, TimingObject } from './types'\r\n\r\n/**\r\n * Main misairu class\r\n */\r\nexport class Misairu {\r\n /**\r\n * The HTML5 AudioContext used for accurately timing our events\r\n */\r\n private readonly _audioContext: AudioContext\r\n\r\n /**\r\n * If the audio source is a HTML5 media element (either a <audio> or <video> tag), this will be a reference to it\r\n *\r\n * @default null\r\n */\r\n private _audioElement: HTMLMediaElement | null = null\r\n\r\n /**\r\n * A source node to play our audio from, either a buffered source from a downloaded media file or a media element source\r\n *\r\n * @default null\r\n */\r\n private _audioSource: AudioBufferSourceNode | MediaElementAudioSourceNode | null = null\r\n\r\n /**\r\n * A object containing the last executed time key per track to not execute an event on every tick\r\n *\r\n * @default {}\r\n */\r\n private _cache: EventCache = {}\r\n\r\n /**\r\n * Reference to the `requestAnimationFrame` handler\r\n *\r\n * @default null\r\n */\r\n private _eventHandler: number | null = null\r\n\r\n /**\r\n * Gain node from our audio context to control audio volume\r\n */\r\n private readonly _gainNode: GainNode\r\n\r\n /**\r\n * Boolean value describing if this instance is currently muted\r\n */\r\n private _muted = false\r\n\r\n /**\r\n * Boolean value describing if this instance is currently paused\r\n */\r\n private _paused = false\r\n\r\n /**\r\n * The time when event handling was started, based on the audio contexts `currentTime` when `start()` was called\r\n */\r\n private _startTime = 0\r\n\r\n /**\r\n * Object containing all timing tracks and events to be executed\r\n */\r\n private _timings: TimingObject\r\n\r\n /**\r\n * Volume of the current instance\r\n */\r\n private _volume = 0\r\n\r\n get volume(): number {\r\n return this._volume\r\n }\r\n\r\n set volume(db: number) {\r\n this._volume = db\r\n this.clampGain()\r\n\r\n if (!this._muted) {\r\n this._gainNode.gain.value = this.dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * misairu constructor\r\n *\r\n * @param audioSource a string or HTML element to be used as audio source\r\n * @param timings a object containing event timing information\r\n */\r\n constructor(audioSource: string | HTMLMediaElement, timings: TimingObject) {\r\n if (timings === null) console.error('You need to specify a timings object')\r\n this._timings = timings\r\n\r\n this._audioContext = new AudioContext()\r\n\r\n this._gainNode = this._audioContext.createGain()\r\n this._gainNode.connect(this._audioContext.destination)\r\n\r\n this.getOptimalAudioSource(audioSource)\r\n\r\n this.compile()\r\n }\r\n\r\n /**\r\n * Method to figure out the best course of action to take with the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private getOptimalAudioSource(audioSource: string | HTMLMediaElement): void {\r\n if (typeof audioSource == 'string') {\r\n this.fetchAudioSource(audioSource)\r\n } else if (\r\n typeof audioSource == 'object' &&\r\n (audioSource.tagName == 'AUDIO' || audioSource.tagName == 'VIDEO')\r\n ) {\r\n this.attachAudioElementSource(audioSource)\r\n }\r\n }\r\n\r\n /**\r\n * Method to fetch the external audio file (if the audio source parameter was a string)\r\n * and turning it into a `AudioBufferSourceNode`\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private fetchAudioSource(audioSource: string): void {\r\n const source = this._audioContext.createBufferSource()\r\n\r\n fetch(audioSource)\r\n .then((response) => {\r\n return response.arrayBuffer()\r\n })\r\n .then((arrayBuffer) => {\r\n this._audioContext.decodeAudioData(arrayBuffer).then((buffer) => {\r\n source.buffer = buffer\r\n source.connect(this._gainNode)\r\n\r\n document.dispatchEvent(new Event('misairu.ready'))\r\n this._audioSource = source\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Method to get an `MediaElementAudioSourceNode` from the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private attachAudioElementSource(audioSource: HTMLMediaElement): void {\r\n const source = this._audioContext.createMediaElementSource(audioSource)\r\n this._audioElement = audioSource\r\n\r\n source.connect(this._gainNode)\r\n this._audioSource = source\r\n }\r\n\r\n /**\r\n * Mutes the instance audio\r\n *\r\n * @public\r\n */\r\n public mute(): void {\r\n if (!this._muted) {\r\n this._muted = true\r\n this._gainNode.gain.value = 0\r\n }\r\n }\r\n\r\n /**\r\n * Unmutes the instance audio\r\n *\r\n * @public\r\n */\r\n public unmute(): void {\r\n if (this._muted) {\r\n this._muted = false\r\n this._gainNode.gain.value = this.dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * Pauses instance playback\r\n *\r\n * @public\r\n */\r\n public pause(): void {\r\n if (!this._paused) {\r\n this._audioContext.suspend()\r\n this._paused = true\r\n }\r\n }\r\n\r\n /**\r\n * Resumes instance playback\r\n *\r\n * @public\r\n */\r\n public unpause(): void {\r\n if (this._paused) {\r\n this._audioContext.resume()\r\n this._paused = false\r\n }\r\n }\r\n\r\n /**\r\n * Clamps the volume to a min/max value to prevent accidental oversetting to way too loud measures\r\n *\r\n * @internal\r\n */\r\n private clampGain(): void {\r\n if (this._volume < -80) {\r\n this._volume = -80\r\n } else if (this._volume > 5) {\r\n this._volume = 5\r\n }\r\n }\r\n\r\n /**\r\n * Method to turn the passed decibel values into volume values for the audio playback\r\n *\r\n * @param db decibel value\r\n * @returns volume value\r\n * @internal\r\n */\r\n private dbToVolume(db: number): number {\r\n return Math.pow(10, db / 20)\r\n }\r\n\r\n /**\r\n * Set a cache entry for the given track\r\n *\r\n * @param track track to set a cache entry for\r\n * @param entry value of the cache entry\r\n * @internal\r\n */\r\n private setCacheEntry(track: string, entry: string): void {\r\n this._cache[track] = entry\r\n }\r\n\r\n /**\r\n * Get cache entry for the given track\r\n *\r\n * @param track track to get a cache entry for\r\n * @returns a cache entry\r\n * @internal\r\n */\r\n private getCacheEntry(track: string): string {\r\n return this._cache[track]\r\n }\r\n\r\n /**\r\n * Get all tracks from the timing configuration\r\n *\r\n * @returns a list of all track names\r\n * @internal\r\n */\r\n private getAllTracks(): string[] {\r\n return Object.keys(this._timings)\r\n }\r\n\r\n /**\r\n * Returns the current active timing key for a given track\r\n *\r\n * @param track track to get the timing key from\r\n * @param currentTime current playback time\r\n * @returns the current active timing key\r\n * @internal\r\n */\r\n private getActiveTimingKey(track: string, currentTime: number): string {\r\n const timingKeys = Object.keys(this._timings[track])\r\n\r\n const activeTimings = timingKeys.filter((timing) => {\r\n if (currentTime >= parseFloat(timing)) {\r\n return true\r\n }\r\n })\r\n\r\n return activeTimings[activeTimings.length - 1]\r\n }\r\n\r\n /**\r\n * Main method to compile special sections in the timing configuration\r\n *\r\n * @internal\r\n */\r\n private compile(): void {\r\n this.getAllTracks().forEach((track) => {\r\n if (track.startsWith('repeat:')) {\r\n this.compileRepeat(track)\r\n }\r\n })\r\n }\r\n\r\n /**\r\n * Method to compile tracks whose name starts with `repeat:`\r\n *\r\n * @param track track to compile the repeat method for\r\n * @internal\r\n */\r\n private compileRepeat(track: string): void {\r\n if (typeof this._timings[track] != 'function')\r\n throw Error(`The value of repeat track \"${track}\" is not a function`)\r\n\r\n const repeatTrackArgs = track.split(':')\r\n\r\n if (repeatTrackArgs.length != 4)\r\n throw Error(`The repeat track \"${track}\" does not supply the valid amount of arguments`)\r\n\r\n const startTime = parseFloat(repeatTrackArgs[1])\r\n const interval = parseFloat(repeatTrackArgs[2])\r\n const endTime = parseFloat(repeatTrackArgs[3])\r\n\r\n let time = startTime\r\n const tempTrack = {}\r\n\r\n do {\r\n tempTrack[time.toString()] = this._timings[track]\r\n time += interval\r\n } while (time < endTime)\r\n\r\n delete this._timings[track]\r\n\r\n this._timings[`repeat-${Math.random().toString(36).substring(7)}`] = tempTrack\r\n }\r\n\r\n /**\r\n * Start audio playback and event handling\r\n *\r\n * @public\r\n */\r\n public start(): void {\r\n this._startTime = this._audioContext.currentTime\r\n\r\n if (this._audioElement !== null) {\r\n this._audioElement.play()\r\n } else {\r\n ;(this._audioSource as AudioBufferSourceNode).start()\r\n }\r\n\r\n this.startEventHandling()\r\n }\r\n\r\n /**\r\n * Method to start event handler loop\r\n *\r\n * @internal\r\n */\r\n private startEventHandling(): void {\r\n if (this._eventHandler == null) {\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Event handler method, is running in a loop using `requestAnimationFrame`\r\n *\r\n * @internal\r\n */\r\n private handleEvents(): void {\r\n const time = this._audioContext.currentTime - this._startTime\r\n\r\n this.getAllTracks().forEach((track) => {\r\n const timingKey = this.getActiveTimingKey(track, time)\r\n\r\n if (timingKey !== null && !(this.getCacheEntry(track) == timingKey)) {\r\n this.executeEvent(track, timingKey, time)\r\n this.setCacheEntry(track, timingKey)\r\n }\r\n })\r\n\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n\r\n /**\r\n * Method to execute the event for a given timing key on a given track\r\n *\r\n * @param track the track to execute the event on\r\n * @param timingKey the timing key to execute\r\n * @param time current playback time\r\n * @internal\r\n */\r\n private executeEvent(track: string, timingKey: string, time: number): void {\r\n this._timings[track][timingKey](this, timingKey, track, time)\r\n }\r\n}\r\n"],"names":["audioSource","timings","_audioContext","_audioElement","_audioSource","_cache","_eventHandler","_gainNode","_muted","_paused","_startTime","_timings","_volume","console","error","this","AudioContext","createGain","connect","destination","getOptimalAudioSource","compile","fetchAudioSource","tagName","attachAudioElementSource","source","createBufferSource","fetch","then","response","arrayBuffer","_this","decodeAudioData","buffer","document","dispatchEvent","Event","createMediaElementSource","mute","gain","value","unmute","dbToVolume","pause","suspend","unpause","resume","clampGain","db","Math","pow","setCacheEntry","track","entry","getCacheEntry","getAllTracks","Object","keys","getActiveTimingKey","currentTime","activeTimings","filter","timing","parseFloat","length","forEach","startsWith","_this2","compileRepeat","Error","repeatTrackArgs","split","startTime","interval","endTime","time","tempTrack","toString","random","substring","start","play","startEventHandling","window","requestAnimationFrame","_this3","handleEvents","timingKey","_this4","executeEvent"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"misairu.umd.js","sources":["../src/processors/repeat.ts","../src/utilities/audio.ts","../src/misairu.ts","../src/utilities/source.ts"],"sourcesContent":["import { EventFunction, EventTrack, ITrackProcessor } from \"../types\";\r\n\r\nconst REPEAT_NAME_REGEX = new RegExp(/repeat:\\d:\\d:\\d/)\r\n\r\nexport class RepeatTrackProcessor implements ITrackProcessor {\r\n deleteOriginTrack = true;\r\n\r\n matches(name: string): boolean {\r\n return name.match(REPEAT_NAME_REGEX) !== null\r\n }\r\n\r\n process(name: string, track: EventFunction): [string, EventTrack] {\r\n if (typeof track != 'function')\r\n throw Error(`The value of repeat track \"${track}\" is not a function`)\r\n\r\n const repeatTrackArgs = name.split(':')\r\n\r\n if (repeatTrackArgs.length != 4)\r\n throw Error(`The repeat track \"${name}\" does not supply the valid amount of arguments`)\r\n\r\n const startTime = parseFloat(repeatTrackArgs[1])\r\n const interval = parseFloat(repeatTrackArgs[2])\r\n const endTime = parseFloat(repeatTrackArgs[3])\r\n\r\n let time = startTime\r\n const tempTrack = {}\r\n\r\n do {\r\n tempTrack[time.toString()] = track\r\n time += interval\r\n } while (time < endTime)\r\n\r\n return [`repeat-${Math.random().toString(36).substring(7)}`, tempTrack]\r\n }\r\n}","/**\r\n * Method to turn the passed decibel values into volume values for the audio playback\r\n *\r\n * @param db decibel value\r\n * @returns volume value\r\n */\r\nexport function dbToVolume(db: number): number {\r\n return Math.pow(10, db / 20)\r\n}\r\n\r\n/**\r\n * Clamps the volume to a min/max value to prevent accidental oversetting to way too loud measures\r\n */\r\nexport function clampGain(volume: number): number {\r\n if (volume < -80) {\r\n return -80\r\n } else if (volume > 5) {\r\n return 5\r\n }\r\n\r\n return volume\r\n}","import { RepeatTrackProcessor } from './processors/repeat'\r\nimport { EventCache, ITrackProcessor, TimingObject } from './types'\r\nimport { dbToVolume, clampGain } from './utilities/audio'\r\nimport { fetchAudioSource, attachAudioElementSource } from './utilities/source'\r\n\r\n/**\r\n * Main misairu class\r\n */\r\nexport class Misairu {\r\n /**\r\n * The HTML5 AudioContext used for accurately timing our events\r\n */\r\n private readonly _audioContext: AudioContext\r\n\r\n /**\r\n * A source node to play our audio from, either a buffered source from a downloaded media file or a media element source\r\n *\r\n * @default null\r\n */\r\n private _audioSource: HTMLMediaElement | AudioBufferSourceNode | MediaElementAudioSourceNode | null = null\r\n\r\n /**\r\n * A object containing the last executed time key per track to not execute an event on every tick\r\n *\r\n * @default {}\r\n */\r\n private _cache: EventCache = {}\r\n\r\n /**\r\n * Reference to the `requestAnimationFrame` handler\r\n *\r\n * @default null\r\n */\r\n private _eventHandler: number | null = null\r\n\r\n /**\r\n * Gain node from our audio context to control audio volume\r\n */\r\n private readonly _gainNode: GainNode\r\n\r\n /**\r\n * Boolean value describing if this instance is currently muted\r\n */\r\n private _muted = false\r\n\r\n /**\r\n * Boolean value describing if this instance is currently paused\r\n */\r\n private _paused = false\r\n\r\n /**\r\n * The time when event handling was started, based on the audio contexts `currentTime` when `start()` was called\r\n */\r\n private _startTime = 0\r\n\r\n /**\r\n * Object containing all timing tracks and events to be executed\r\n */\r\n private _timings: TimingObject\r\n\r\n /**\r\n * Volume of the current instance\r\n */\r\n private _volume = 0\r\n\r\n /**\r\n * List of (predefined) track processors\r\n */\r\n private _processors: ITrackProcessor[] = [\r\n new RepeatTrackProcessor()\r\n ]\r\n\r\n get volume(): number {\r\n return this._volume\r\n }\r\n\r\n set volume(db: number) {\r\n this._volume = clampGain(db)\r\n\r\n if (!this._muted) {\r\n this._gainNode.gain.value = dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * misairu constructor\r\n *\r\n * @param audioSource a string or HTML element to be used as audio source\r\n * @param timings a object containing event timing information\r\n */\r\n constructor(audioSource: string | HTMLMediaElement, timings: TimingObject) {\r\n if (timings === null) console.error('You need to specify a timings object')\r\n this._timings = timings\r\n\r\n this._audioContext = new AudioContext()\r\n\r\n this._gainNode = this._audioContext.createGain()\r\n this._gainNode.connect(this._audioContext.destination)\r\n\r\n this.getOptimalAudioSource(audioSource)\r\n\r\n this.processTracks()\r\n }\r\n\r\n /**\r\n * Method to figure out the best course of action to take with the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\n private getOptimalAudioSource(audioSource: string | HTMLMediaElement): void {\r\n if (typeof audioSource == 'string') {\r\n fetchAudioSource(audioSource, this._audioContext).then((audioSource) => {\r\n this._audioSource = audioSource\r\n this._audioSource.connect(this._gainNode)\r\n\r\n document.dispatchEvent(new Event('misairu.ready'))\r\n })\r\n } else if (\r\n typeof audioSource == 'object' &&\r\n (audioSource.tagName == 'AUDIO' || audioSource.tagName == 'VIDEO')\r\n ) {\r\n this._audioSource = attachAudioElementSource(audioSource, this._audioContext)\r\n this._audioSource.connect(this._gainNode)\r\n }\r\n }\r\n\r\n /**\r\n * Mutes the instance audio\r\n *\r\n * @public\r\n */\r\n public mute(): void {\r\n if (!this._muted) {\r\n this._muted = true\r\n this._gainNode.gain.value = 0\r\n }\r\n }\r\n\r\n /**\r\n * Unmutes the instance audio\r\n *\r\n * @public\r\n */\r\n public unmute(): void {\r\n if (this._muted) {\r\n this._muted = false\r\n this._gainNode.gain.value = dbToVolume(this._volume)\r\n }\r\n }\r\n\r\n /**\r\n * Pauses instance playback\r\n *\r\n * @public\r\n */\r\n public pause(): void {\r\n if (!this._paused) {\r\n this._audioContext.suspend()\r\n this._paused = true\r\n }\r\n }\r\n\r\n /**\r\n * Resumes instance playback\r\n *\r\n * @public\r\n */\r\n public unpause(): void {\r\n if (this._paused) {\r\n this._audioContext.resume()\r\n this._paused = false\r\n }\r\n }\r\n\r\n /**\r\n * Set a cache entry for the given track\r\n *\r\n * @param track track to set a cache entry for\r\n * @param entry value of the cache entry\r\n * @internal\r\n */\r\n private setCacheEntry(track: string, entry: string): void {\r\n this._cache[track] = entry\r\n }\r\n\r\n /**\r\n * Get cache entry for the given track\r\n *\r\n * @param track track to get a cache entry for\r\n * @returns a cache entry\r\n * @internal\r\n */\r\n private getCacheEntry(track: string): string {\r\n return this._cache[track]\r\n }\r\n\r\n /**\r\n * Get all tracks from the timing configuration\r\n *\r\n * @returns a list of all track names\r\n * @internal\r\n */\r\n private getAllTracks(): string[] {\r\n return Object.keys(this._timings)\r\n }\r\n\r\n /**\r\n * Returns the current active timing key for a given track\r\n *\r\n * @param track track to get the timing key from\r\n * @param currentTime current playback time\r\n * @returns the current active timing key\r\n * @internal\r\n */\r\n private getActiveTimingKey(track: string, currentTime: number): string {\r\n const timingKeys = Object.keys(this._timings[track])\r\n\r\n const activeTimings = timingKeys.filter((timing) => {\r\n if (currentTime >= parseFloat(timing)) {\r\n return true\r\n }\r\n })\r\n\r\n return activeTimings[activeTimings.length - 1]\r\n }\r\n\r\n /**\r\n * Main method to process special sections in the timing configuration\r\n *\r\n * @internal\r\n */\r\n private processTracks(): void {\r\n this.getAllTracks().forEach((trackName) => {\r\n this._processors.forEach((processor: ITrackProcessor) => {\r\n if (processor.matches(trackName)) {\r\n const [processedTrackName, eventTrack] = processor.process(trackName, this._timings[trackName])\r\n\r\n this._timings[processedTrackName] = eventTrack\r\n\r\n if (processor.deleteOriginTrack) {\r\n delete this._timings[trackName]\r\n }\r\n }\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Start audio playback and event handling\r\n *\r\n * @public\r\n */\r\n public start(): void {\r\n this._startTime = this._audioContext.currentTime\r\n\r\n ;(this._audioSource as AudioBufferSourceNode).start()\r\n\r\n this.startEventHandling()\r\n }\r\n\r\n /**\r\n * Method to start event handler loop\r\n *\r\n * @internal\r\n */\r\n private startEventHandling(): void {\r\n if (this._eventHandler == null) {\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Event handler method, is running in a loop using `requestAnimationFrame`\r\n *\r\n * @internal\r\n */\r\n private handleEvents(): void {\r\n const time = this._audioContext.currentTime - this._startTime\r\n\r\n this.getAllTracks().forEach((track) => {\r\n const timingKey = this.getActiveTimingKey(track, time)\r\n\r\n if (timingKey !== null && !(this.getCacheEntry(track) == timingKey)) {\r\n this.executeEvent(track, timingKey, time)\r\n this.setCacheEntry(track, timingKey)\r\n }\r\n })\r\n\r\n this._eventHandler = window.requestAnimationFrame(() => {\r\n this.handleEvents()\r\n })\r\n }\r\n\r\n /**\r\n * Method to execute the event for a given timing key on a given track\r\n *\r\n * @param track the track to execute the event on\r\n * @param timingKey the timing key to execute\r\n * @param time current playback time\r\n * @internal\r\n */\r\n private executeEvent(track: string, timingKey: string, time: number): void {\r\n this._timings[track][timingKey](this, timingKey, track, time)\r\n }\r\n}\r\n","/**\r\n * Method to fetch the external audio file (if the audio source parameter was a string)\r\n * and turning it into a `AudioBufferSourceNode`\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\nexport async function fetchAudioSource(audioSource: string, audioContext: AudioContext): Promise<AudioBufferSourceNode> {\r\n const source = audioContext.createBufferSource()\r\n\r\n const response = await fetch(audioSource)\r\n const arrayBuffer = await response.arrayBuffer()\r\n const buffer = await audioContext.decodeAudioData(arrayBuffer)\r\n\r\n source.buffer = buffer\r\n\r\n return source\r\n}\r\n\r\n/**\r\n * Method to get an `MediaElementAudioSourceNode` from the passed audio source\r\n *\r\n * @param audioSource the audio source `Misairu` has been constructed with\r\n * @internal\r\n */\r\nexport function attachAudioElementSource(audioSource: HTMLMediaElement, audioContext: AudioContext): MediaElementAudioSourceNode {\r\n return audioContext.createMediaElementSource(audioSource)\r\n}"],"names":["REPEAT_NAME_REGEX","RegExp","RepeatTrackProcessor","deleteOriginTrack","matches","name","match","process","track","Error","repeatTrackArgs","split","length","startTime","parseFloat","interval","endTime","time","tempTrack","toString","Math","random","substring","dbToVolume","db","pow","audioSource","timings","_audioContext","_audioSource","_cache","_eventHandler","_gainNode","_muted","_paused","_startTime","_timings","_volume","_processors","console","error","this","AudioContext","createGain","connect","destination","getOptimalAudioSource","processTracks","audioContext","source","createBufferSource","fetch","response","arrayBuffer","decodeAudioData","buffer","fetchAudioSource","then","_this","document","dispatchEvent","Event","tagName","createMediaElementSource","attachAudioElementSource","mute","gain","value","unmute","pause","suspend","unpause","resume","setCacheEntry","entry","getCacheEntry","getAllTracks","Object","keys","getActiveTimingKey","currentTime","activeTimings","filter","timing","forEach","trackName","_this2","processor","start","startEventHandling","window","requestAnimationFrame","_this3","handleEvents","timingKey","_this4","executeEvent","volume"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { EventFunction, EventTrack, ITrackProcessor } from "../types";
|
|
2
|
+
export declare class RepeatTrackProcessor implements ITrackProcessor {
|
|
3
|
+
deleteOriginTrack: boolean;
|
|
4
|
+
matches(name: string): boolean;
|
|
5
|
+
process(name: string, track: EventFunction): [string, EventTrack];
|
|
6
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -21,3 +21,26 @@ export declare type EventTrack = {
|
|
|
21
21
|
export declare type TimingObject = {
|
|
22
22
|
[trackName: string]: EventTrack;
|
|
23
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* Interface describing required methods/members for track processors
|
|
26
|
+
*/
|
|
27
|
+
export interface ITrackProcessor {
|
|
28
|
+
/**
|
|
29
|
+
* Member describing if the processed track should be deleted
|
|
30
|
+
*/
|
|
31
|
+
deleteOriginTrack: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Method to check if the track name matches to determine if the processor
|
|
34
|
+
* should process it
|
|
35
|
+
*
|
|
36
|
+
* @param name name oƒ a track
|
|
37
|
+
*/
|
|
38
|
+
matches(name: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Main processing method
|
|
41
|
+
*
|
|
42
|
+
* @param name name of the track to be processed
|
|
43
|
+
* @param track content of the track to be processed
|
|
44
|
+
*/
|
|
45
|
+
process(name: string, track: EventTrack | EventFunction): [string, EventTrack];
|
|
46
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Method to turn the passed decibel values into volume values for the audio playback
|
|
3
|
+
*
|
|
4
|
+
* @param db decibel value
|
|
5
|
+
* @returns volume value
|
|
6
|
+
*/
|
|
7
|
+
export declare function dbToVolume(db: number): number;
|
|
8
|
+
/**
|
|
9
|
+
* Clamps the volume to a min/max value to prevent accidental oversetting to way too loud measures
|
|
10
|
+
*/
|
|
11
|
+
export declare function clampGain(volume: number): number;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Method to fetch the external audio file (if the audio source parameter was a string)
|
|
3
|
+
* and turning it into a `AudioBufferSourceNode`
|
|
4
|
+
*
|
|
5
|
+
* @param audioSource the audio source `Misairu` has been constructed with
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export declare function fetchAudioSource(audioSource: string, audioContext: AudioContext): Promise<AudioBufferSourceNode>;
|
|
9
|
+
/**
|
|
10
|
+
* Method to get an `MediaElementAudioSourceNode` from the passed audio source
|
|
11
|
+
*
|
|
12
|
+
* @param audioSource the audio source `Misairu` has been constructed with
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export declare function attachAudioElementSource(audioSource: HTMLMediaElement, audioContext: AudioContext): MediaElementAudioSourceNode;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "misairu",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.1",
|
|
4
4
|
"description": "Fire events for specific timeframes easily",
|
|
5
5
|
"source": "src/index.ts",
|
|
6
6
|
"main": "dist/misairu.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
22
|
-
"url": "git+https://
|
|
22
|
+
"url": "git+https://codeberg.org/pixeldesu/misairu.git"
|
|
23
23
|
},
|
|
24
24
|
"keywords": [
|
|
25
25
|
"audio",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"author": "Andreas Nedbal <andy@pixelde.su>",
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"bugs": {
|
|
34
|
-
"url": "https://
|
|
34
|
+
"url": "https://codeberg.org/pixeldesu/misairu/issues"
|
|
35
35
|
},
|
|
36
|
-
"homepage": "https://
|
|
36
|
+
"homepage": "https://codeberg.org/pixeldesu/misairu#readme",
|
|
37
37
|
"lint-staged": {
|
|
38
38
|
"*.{js,ts}": "eslint --fix"
|
|
39
39
|
},
|
package/src/misairu.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RepeatTrackProcessor } from './processors/repeat'
|
|
2
|
+
import { EventCache, ITrackProcessor, TimingObject } from './types'
|
|
3
|
+
import { dbToVolume, clampGain } from './utilities/audio'
|
|
4
|
+
import { fetchAudioSource, attachAudioElementSource } from './utilities/source'
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* Main misairu class
|
|
@@ -9,19 +12,12 @@ export class Misairu {
|
|
|
9
12
|
*/
|
|
10
13
|
private readonly _audioContext: AudioContext
|
|
11
14
|
|
|
12
|
-
/**
|
|
13
|
-
* If the audio source is a HTML5 media element (either a <audio> or <video> tag), this will be a reference to it
|
|
14
|
-
*
|
|
15
|
-
* @default null
|
|
16
|
-
*/
|
|
17
|
-
private _audioElement: HTMLMediaElement | null = null
|
|
18
|
-
|
|
19
15
|
/**
|
|
20
16
|
* A source node to play our audio from, either a buffered source from a downloaded media file or a media element source
|
|
21
17
|
*
|
|
22
18
|
* @default null
|
|
23
19
|
*/
|
|
24
|
-
private _audioSource: AudioBufferSourceNode | MediaElementAudioSourceNode | null = null
|
|
20
|
+
private _audioSource: HTMLMediaElement | AudioBufferSourceNode | MediaElementAudioSourceNode | null = null
|
|
25
21
|
|
|
26
22
|
/**
|
|
27
23
|
* A object containing the last executed time key per track to not execute an event on every tick
|
|
@@ -67,16 +63,22 @@ export class Misairu {
|
|
|
67
63
|
*/
|
|
68
64
|
private _volume = 0
|
|
69
65
|
|
|
66
|
+
/**
|
|
67
|
+
* List of (predefined) track processors
|
|
68
|
+
*/
|
|
69
|
+
private _processors: ITrackProcessor[] = [
|
|
70
|
+
new RepeatTrackProcessor()
|
|
71
|
+
]
|
|
72
|
+
|
|
70
73
|
get volume(): number {
|
|
71
74
|
return this._volume
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
set volume(db: number) {
|
|
75
|
-
this._volume = db
|
|
76
|
-
this.clampGain()
|
|
78
|
+
this._volume = clampGain(db)
|
|
77
79
|
|
|
78
80
|
if (!this._muted) {
|
|
79
|
-
this._gainNode.gain.value =
|
|
81
|
+
this._gainNode.gain.value = dbToVolume(this._volume)
|
|
80
82
|
}
|
|
81
83
|
}
|
|
82
84
|
|
|
@@ -97,7 +99,7 @@ export class Misairu {
|
|
|
97
99
|
|
|
98
100
|
this.getOptimalAudioSource(audioSource)
|
|
99
101
|
|
|
100
|
-
this.
|
|
102
|
+
this.processTracks()
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
/**
|
|
@@ -108,54 +110,21 @@ export class Misairu {
|
|
|
108
110
|
*/
|
|
109
111
|
private getOptimalAudioSource(audioSource: string | HTMLMediaElement): void {
|
|
110
112
|
if (typeof audioSource == 'string') {
|
|
111
|
-
this.
|
|
113
|
+
fetchAudioSource(audioSource, this._audioContext).then((audioSource) => {
|
|
114
|
+
this._audioSource = audioSource
|
|
115
|
+
this._audioSource.connect(this._gainNode)
|
|
116
|
+
|
|
117
|
+
document.dispatchEvent(new Event('misairu.ready'))
|
|
118
|
+
})
|
|
112
119
|
} else if (
|
|
113
120
|
typeof audioSource == 'object' &&
|
|
114
121
|
(audioSource.tagName == 'AUDIO' || audioSource.tagName == 'VIDEO')
|
|
115
122
|
) {
|
|
116
|
-
this.attachAudioElementSource(audioSource)
|
|
123
|
+
this._audioSource = attachAudioElementSource(audioSource, this._audioContext)
|
|
124
|
+
this._audioSource.connect(this._gainNode)
|
|
117
125
|
}
|
|
118
126
|
}
|
|
119
127
|
|
|
120
|
-
/**
|
|
121
|
-
* Method to fetch the external audio file (if the audio source parameter was a string)
|
|
122
|
-
* and turning it into a `AudioBufferSourceNode`
|
|
123
|
-
*
|
|
124
|
-
* @param audioSource the audio source `Misairu` has been constructed with
|
|
125
|
-
* @internal
|
|
126
|
-
*/
|
|
127
|
-
private fetchAudioSource(audioSource: string): void {
|
|
128
|
-
const source = this._audioContext.createBufferSource()
|
|
129
|
-
|
|
130
|
-
fetch(audioSource)
|
|
131
|
-
.then((response) => {
|
|
132
|
-
return response.arrayBuffer()
|
|
133
|
-
})
|
|
134
|
-
.then((arrayBuffer) => {
|
|
135
|
-
this._audioContext.decodeAudioData(arrayBuffer).then((buffer) => {
|
|
136
|
-
source.buffer = buffer
|
|
137
|
-
source.connect(this._gainNode)
|
|
138
|
-
|
|
139
|
-
document.dispatchEvent(new Event('misairu.ready'))
|
|
140
|
-
this._audioSource = source
|
|
141
|
-
})
|
|
142
|
-
})
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Method to get an `MediaElementAudioSourceNode` from the passed audio source
|
|
147
|
-
*
|
|
148
|
-
* @param audioSource the audio source `Misairu` has been constructed with
|
|
149
|
-
* @internal
|
|
150
|
-
*/
|
|
151
|
-
private attachAudioElementSource(audioSource: HTMLMediaElement): void {
|
|
152
|
-
const source = this._audioContext.createMediaElementSource(audioSource)
|
|
153
|
-
this._audioElement = audioSource
|
|
154
|
-
|
|
155
|
-
source.connect(this._gainNode)
|
|
156
|
-
this._audioSource = source
|
|
157
|
-
}
|
|
158
|
-
|
|
159
128
|
/**
|
|
160
129
|
* Mutes the instance audio
|
|
161
130
|
*
|
|
@@ -176,7 +145,7 @@ export class Misairu {
|
|
|
176
145
|
public unmute(): void {
|
|
177
146
|
if (this._muted) {
|
|
178
147
|
this._muted = false
|
|
179
|
-
this._gainNode.gain.value =
|
|
148
|
+
this._gainNode.gain.value = dbToVolume(this._volume)
|
|
180
149
|
}
|
|
181
150
|
}
|
|
182
151
|
|
|
@@ -204,30 +173,6 @@ export class Misairu {
|
|
|
204
173
|
}
|
|
205
174
|
}
|
|
206
175
|
|
|
207
|
-
/**
|
|
208
|
-
* Clamps the volume to a min/max value to prevent accidental oversetting to way too loud measures
|
|
209
|
-
*
|
|
210
|
-
* @internal
|
|
211
|
-
*/
|
|
212
|
-
private clampGain(): void {
|
|
213
|
-
if (this._volume < -80) {
|
|
214
|
-
this._volume = -80
|
|
215
|
-
} else if (this._volume > 5) {
|
|
216
|
-
this._volume = 5
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Method to turn the passed decibel values into volume values for the audio playback
|
|
222
|
-
*
|
|
223
|
-
* @param db decibel value
|
|
224
|
-
* @returns volume value
|
|
225
|
-
* @internal
|
|
226
|
-
*/
|
|
227
|
-
private dbToVolume(db: number): number {
|
|
228
|
-
return Math.pow(10, db / 20)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
176
|
/**
|
|
232
177
|
* Set a cache entry for the given track
|
|
233
178
|
*
|
|
@@ -281,50 +226,26 @@ export class Misairu {
|
|
|
281
226
|
}
|
|
282
227
|
|
|
283
228
|
/**
|
|
284
|
-
* Main method to
|
|
229
|
+
* Main method to process special sections in the timing configuration
|
|
285
230
|
*
|
|
286
231
|
* @internal
|
|
287
232
|
*/
|
|
288
|
-
private
|
|
289
|
-
this.getAllTracks().forEach((
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
233
|
+
private processTracks(): void {
|
|
234
|
+
this.getAllTracks().forEach((trackName) => {
|
|
235
|
+
this._processors.forEach((processor: ITrackProcessor) => {
|
|
236
|
+
if (processor.matches(trackName)) {
|
|
237
|
+
const [processedTrackName, eventTrack] = processor.process(trackName, this._timings[trackName])
|
|
238
|
+
|
|
239
|
+
this._timings[processedTrackName] = eventTrack
|
|
240
|
+
|
|
241
|
+
if (processor.deleteOriginTrack) {
|
|
242
|
+
delete this._timings[trackName]
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
})
|
|
293
246
|
})
|
|
294
247
|
}
|
|
295
248
|
|
|
296
|
-
/**
|
|
297
|
-
* Method to compile tracks whose name starts with `repeat:`
|
|
298
|
-
*
|
|
299
|
-
* @param track track to compile the repeat method for
|
|
300
|
-
* @internal
|
|
301
|
-
*/
|
|
302
|
-
private compileRepeat(track: string): void {
|
|
303
|
-
if (typeof this._timings[track] != 'function')
|
|
304
|
-
throw Error(`The value of repeat track "${track}" is not a function`)
|
|
305
|
-
|
|
306
|
-
const repeatTrackArgs = track.split(':')
|
|
307
|
-
|
|
308
|
-
if (repeatTrackArgs.length != 4)
|
|
309
|
-
throw Error(`The repeat track "${track}" does not supply the valid amount of arguments`)
|
|
310
|
-
|
|
311
|
-
const startTime = parseFloat(repeatTrackArgs[1])
|
|
312
|
-
const interval = parseFloat(repeatTrackArgs[2])
|
|
313
|
-
const endTime = parseFloat(repeatTrackArgs[3])
|
|
314
|
-
|
|
315
|
-
let time = startTime
|
|
316
|
-
const tempTrack = {}
|
|
317
|
-
|
|
318
|
-
do {
|
|
319
|
-
tempTrack[time.toString()] = this._timings[track]
|
|
320
|
-
time += interval
|
|
321
|
-
} while (time < endTime)
|
|
322
|
-
|
|
323
|
-
delete this._timings[track]
|
|
324
|
-
|
|
325
|
-
this._timings[`repeat-${Math.random().toString(36).substring(7)}`] = tempTrack
|
|
326
|
-
}
|
|
327
|
-
|
|
328
249
|
/**
|
|
329
250
|
* Start audio playback and event handling
|
|
330
251
|
*
|
|
@@ -333,11 +254,7 @@ export class Misairu {
|
|
|
333
254
|
public start(): void {
|
|
334
255
|
this._startTime = this._audioContext.currentTime
|
|
335
256
|
|
|
336
|
-
|
|
337
|
-
this._audioElement.play()
|
|
338
|
-
} else {
|
|
339
|
-
;(this._audioSource as AudioBufferSourceNode).start()
|
|
340
|
-
}
|
|
257
|
+
;(this._audioSource as AudioBufferSourceNode).start()
|
|
341
258
|
|
|
342
259
|
this.startEventHandling()
|
|
343
260
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { EventFunction, EventTrack, ITrackProcessor } from "../types";
|
|
2
|
+
|
|
3
|
+
const REPEAT_NAME_REGEX = new RegExp(/repeat:\d:\d:\d/)
|
|
4
|
+
|
|
5
|
+
export class RepeatTrackProcessor implements ITrackProcessor {
|
|
6
|
+
deleteOriginTrack = true;
|
|
7
|
+
|
|
8
|
+
matches(name: string): boolean {
|
|
9
|
+
return name.match(REPEAT_NAME_REGEX) !== null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
process(name: string, track: EventFunction): [string, EventTrack] {
|
|
13
|
+
if (typeof track != 'function')
|
|
14
|
+
throw Error(`The value of repeat track "${track}" is not a function`)
|
|
15
|
+
|
|
16
|
+
const repeatTrackArgs = name.split(':')
|
|
17
|
+
|
|
18
|
+
if (repeatTrackArgs.length != 4)
|
|
19
|
+
throw Error(`The repeat track "${name}" does not supply the valid amount of arguments`)
|
|
20
|
+
|
|
21
|
+
const startTime = parseFloat(repeatTrackArgs[1])
|
|
22
|
+
const interval = parseFloat(repeatTrackArgs[2])
|
|
23
|
+
const endTime = parseFloat(repeatTrackArgs[3])
|
|
24
|
+
|
|
25
|
+
let time = startTime
|
|
26
|
+
const tempTrack = {}
|
|
27
|
+
|
|
28
|
+
do {
|
|
29
|
+
tempTrack[time.toString()] = track
|
|
30
|
+
time += interval
|
|
31
|
+
} while (time < endTime)
|
|
32
|
+
|
|
33
|
+
return [`repeat-${Math.random().toString(36).substring(7)}`, tempTrack]
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -30,3 +30,29 @@ export type EventTrack = {
|
|
|
30
30
|
export type TimingObject = {
|
|
31
31
|
[trackName: string]: EventTrack
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Interface describing required methods/members for track processors
|
|
36
|
+
*/
|
|
37
|
+
export interface ITrackProcessor {
|
|
38
|
+
/**
|
|
39
|
+
* Member describing if the processed track should be deleted
|
|
40
|
+
*/
|
|
41
|
+
deleteOriginTrack: boolean
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Method to check if the track name matches to determine if the processor
|
|
45
|
+
* should process it
|
|
46
|
+
*
|
|
47
|
+
* @param name name oƒ a track
|
|
48
|
+
*/
|
|
49
|
+
matches(name: string): boolean
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Main processing method
|
|
53
|
+
*
|
|
54
|
+
* @param name name of the track to be processed
|
|
55
|
+
* @param track content of the track to be processed
|
|
56
|
+
*/
|
|
57
|
+
process(name: string, track: EventTrack | EventFunction): [string, EventTrack]
|
|
58
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Method to turn the passed decibel values into volume values for the audio playback
|
|
3
|
+
*
|
|
4
|
+
* @param db decibel value
|
|
5
|
+
* @returns volume value
|
|
6
|
+
*/
|
|
7
|
+
export function dbToVolume(db: number): number {
|
|
8
|
+
return Math.pow(10, db / 20)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Clamps the volume to a min/max value to prevent accidental oversetting to way too loud measures
|
|
13
|
+
*/
|
|
14
|
+
export function clampGain(volume: number): number {
|
|
15
|
+
if (volume < -80) {
|
|
16
|
+
return -80
|
|
17
|
+
} else if (volume > 5) {
|
|
18
|
+
return 5
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return volume
|
|
22
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Method to fetch the external audio file (if the audio source parameter was a string)
|
|
3
|
+
* and turning it into a `AudioBufferSourceNode`
|
|
4
|
+
*
|
|
5
|
+
* @param audioSource the audio source `Misairu` has been constructed with
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export async function fetchAudioSource(audioSource: string, audioContext: AudioContext): Promise<AudioBufferSourceNode> {
|
|
9
|
+
const source = audioContext.createBufferSource()
|
|
10
|
+
|
|
11
|
+
const response = await fetch(audioSource)
|
|
12
|
+
const arrayBuffer = await response.arrayBuffer()
|
|
13
|
+
const buffer = await audioContext.decodeAudioData(arrayBuffer)
|
|
14
|
+
|
|
15
|
+
source.buffer = buffer
|
|
16
|
+
|
|
17
|
+
return source
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Method to get an `MediaElementAudioSourceNode` from the passed audio source
|
|
22
|
+
*
|
|
23
|
+
* @param audioSource the audio source `Misairu` has been constructed with
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export function attachAudioElementSource(audioSource: HTMLMediaElement, audioContext: AudioContext): MediaElementAudioSourceNode {
|
|
27
|
+
return audioContext.createMediaElementSource(audioSource)
|
|
28
|
+
}
|
package/.github/FUNDING.yml
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
github: pixeldesu
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
name: Lint
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [ main ]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [ main ]
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
lint:
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
steps:
|
|
13
|
-
- uses: actions/checkout@v2
|
|
14
|
-
- name: Install modules
|
|
15
|
-
run: npm install
|
|
16
|
-
- name: Run ESLint
|
|
17
|
-
run: npm run lint
|