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 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 compile special sections in the timing configuration
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 compileRepeat;
133
+ private processTracks;
172
134
  /**
173
135
  * Start audio playback and event handling
174
136
  *
@@ -1,2 +1,2 @@
1
- var Misairu=function(){function t(t,e){this._audioContext=void 0,this._audioElement=null,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,null===e&&console.error("You need to specify a timings object"),this._timings=e,this._audioContext=new AudioContext,this._gainNode=this._audioContext.createGain(),this._gainNode.connect(this._audioContext.destination),this.getOptimalAudioSource(t),this.compile()}var e,i=t.prototype;return i.getOptimalAudioSource=function(t){"string"==typeof t?this.fetchAudioSource(t):"object"!=typeof t||"AUDIO"!=t.tagName&&"VIDEO"!=t.tagName||this.attachAudioElementSource(t)},i.fetchAudioSource=function(t){var e=this,i=this._audioContext.createBufferSource();fetch(t).then(function(t){return t.arrayBuffer()}).then(function(t){e._audioContext.decodeAudioData(t).then(function(t){i.buffer=t,i.connect(e._gainNode),document.dispatchEvent(new Event("misairu.ready")),e._audioSource=i})})},i.attachAudioElementSource=function(t){var e=this._audioContext.createMediaElementSource(t);this._audioElement=t,e.connect(this._gainNode),this._audioSource=e},i.mute=function(){this._muted||(this._muted=!0,this._gainNode.gain.value=0)},i.unmute=function(){this._muted&&(this._muted=!1,this._gainNode.gain.value=this.dbToVolume(this._volume))},i.pause=function(){this._paused||(this._audioContext.suspend(),this._paused=!0)},i.unpause=function(){this._paused&&(this._audioContext.resume(),this._paused=!1)},i.clampGain=function(){this._volume<-80?this._volume=-80:this._volume>5&&(this._volume=5)},i.dbToVolume=function(t){return Math.pow(10,t/20)},i.setCacheEntry=function(t,e){this._cache[t]=e},i.getCacheEntry=function(t){return this._cache[t]},i.getAllTracks=function(){return Object.keys(this._timings)},i.getActiveTimingKey=function(t,e){var i=Object.keys(this._timings[t]).filter(function(t){if(e>=parseFloat(t))return!0});return i[i.length-1]},i.compile=function(){var t=this;this.getAllTracks().forEach(function(e){e.startsWith("repeat:")&&t.compileRepeat(e)})},i.compileRepeat=function(t){if("function"!=typeof this._timings[t])throw Error('The value of repeat track "'+t+'" is not a function');var e=t.split(":");if(4!=e.length)throw Error('The repeat track "'+t+'" does not supply the valid amount of arguments');var i=parseFloat(e[1]),n=parseFloat(e[2]),o=parseFloat(e[3]),a=i,u={};do{u[a.toString()]=this._timings[t],a+=n}while(a<o);delete this._timings[t],this._timings["repeat-"+Math.random().toString(36).substring(7)]=u},i.start=function(){this._startTime=this._audioContext.currentTime,null!==this._audioElement?this._audioElement.play():this._audioSource.start(),this.startEventHandling()},i.startEventHandling=function(){var t=this;null==this._eventHandler&&(this._eventHandler=window.requestAnimationFrame(function(){t.handleEvents()}))},i.handleEvents=function(){var t=this,e=this._audioContext.currentTime-this._startTime;this.getAllTracks().forEach(function(i){var n=t.getActiveTimingKey(i,e);null!==n&&t.getCacheEntry(i)!=n&&(t.executeEvent(i,n,e),t.setCacheEntry(i,n))}),this._eventHandler=window.requestAnimationFrame(function(){t.handleEvents()})},i.executeEvent=function(t,e,i){this._timings[t][e](this,e,t,i)},(e=[{key:"volume",get:function(){return this._volume},set:function(t){this._volume=t,this.clampGain(),this._muted||(this._gainNode.gain.value=this.dbToVolume(this._volume))}}])&&function(t,e){for(var i=0;i<e.length;i++){var n=e[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}(t.prototype,e),t}();
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
@@ -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":"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"}
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,e){this._audioContext=void 0,this._audioElement=null,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,null===e&&console.error("You need to specify a timings object"),this._timings=e,this._audioContext=new AudioContext,this._gainNode=this._audioContext.createGain(),this._gainNode.connect(this._audioContext.destination),this.getOptimalAudioSource(t),this.compile()}var e,i=t.prototype;return i.getOptimalAudioSource=function(t){"string"==typeof t?this.fetchAudioSource(t):"object"!=typeof t||"AUDIO"!=t.tagName&&"VIDEO"!=t.tagName||this.attachAudioElementSource(t)},i.fetchAudioSource=function(t){var e=this,i=this._audioContext.createBufferSource();fetch(t).then(function(t){return t.arrayBuffer()}).then(function(t){e._audioContext.decodeAudioData(t).then(function(t){i.buffer=t,i.connect(e._gainNode),document.dispatchEvent(new Event("misairu.ready")),e._audioSource=i})})},i.attachAudioElementSource=function(t){var e=this._audioContext.createMediaElementSource(t);this._audioElement=t,e.connect(this._gainNode),this._audioSource=e},i.mute=function(){this._muted||(this._muted=!0,this._gainNode.gain.value=0)},i.unmute=function(){this._muted&&(this._muted=!1,this._gainNode.gain.value=this.dbToVolume(this._volume))},i.pause=function(){this._paused||(this._audioContext.suspend(),this._paused=!0)},i.unpause=function(){this._paused&&(this._audioContext.resume(),this._paused=!1)},i.clampGain=function(){this._volume<-80?this._volume=-80:this._volume>5&&(this._volume=5)},i.dbToVolume=function(t){return Math.pow(10,t/20)},i.setCacheEntry=function(t,e){this._cache[t]=e},i.getCacheEntry=function(t){return this._cache[t]},i.getAllTracks=function(){return Object.keys(this._timings)},i.getActiveTimingKey=function(t,e){var i=Object.keys(this._timings[t]).filter(function(t){if(e>=parseFloat(t))return!0});return i[i.length-1]},i.compile=function(){var t=this;this.getAllTracks().forEach(function(e){e.startsWith("repeat:")&&t.compileRepeat(e)})},i.compileRepeat=function(t){if("function"!=typeof this._timings[t])throw Error('The value of repeat track "'+t+'" is not a function');var e=t.split(":");if(4!=e.length)throw Error('The repeat track "'+t+'" does not supply the valid amount of arguments');var i=parseFloat(e[1]),n=parseFloat(e[2]),o=parseFloat(e[3]),u=i,a={};do{a[u.toString()]=this._timings[t],u+=n}while(u<o);delete this._timings[t],this._timings["repeat-"+Math.random().toString(36).substring(7)]=a},i.start=function(){this._startTime=this._audioContext.currentTime,null!==this._audioElement?this._audioElement.play():this._audioSource.start(),this.startEventHandling()},i.startEventHandling=function(){var t=this;null==this._eventHandler&&(this._eventHandler=window.requestAnimationFrame(function(){t.handleEvents()}))},i.handleEvents=function(){var t=this,e=this._audioContext.currentTime-this._startTime;this.getAllTracks().forEach(function(i){var n=t.getActiveTimingKey(i,e);null!==n&&t.getCacheEntry(i)!=n&&(t.executeEvent(i,n,e),t.setCacheEntry(i,n))}),this._eventHandler=window.requestAnimationFrame(function(){t.handleEvents()})},i.executeEvent=function(t,e,i){this._timings[t][e](this,e,t,i)},(e=[{key:"volume",get:function(){return this._volume},set:function(t){this._volume=t,this.clampGain(),this._muted||(this._gainNode.gain.value=this.dbToVolume(this._volume))}}])&&function(t,e){for(var i=0;i<e.length;i++){var n=e[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}(t.prototype,e),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
@@ -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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"}
@@ -1,2 +1,2 @@
1
- class t{get volume(){return this._volume}set volume(t){this._volume=t,this.clampGain(),this._muted||(this._gainNode.gain.value=this.dbToVolume(this._volume))}constructor(t,e){this._audioContext=void 0,this._audioElement=null,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,null===e&&console.error("You need to specify a timings object"),this._timings=e,this._audioContext=new AudioContext,this._gainNode=this._audioContext.createGain(),this._gainNode.connect(this._audioContext.destination),this.getOptimalAudioSource(t),this.compile()}getOptimalAudioSource(t){"string"==typeof t?this.fetchAudioSource(t):"object"!=typeof t||"AUDIO"!=t.tagName&&"VIDEO"!=t.tagName||this.attachAudioElementSource(t)}fetchAudioSource(t){const e=this._audioContext.createBufferSource();fetch(t).then(t=>t.arrayBuffer()).then(t=>{this._audioContext.decodeAudioData(t).then(t=>{e.buffer=t,e.connect(this._gainNode),document.dispatchEvent(new Event("misairu.ready")),this._audioSource=e})})}attachAudioElementSource(t){const e=this._audioContext.createMediaElementSource(t);this._audioElement=t,e.connect(this._gainNode),this._audioSource=e}mute(){this._muted||(this._muted=!0,this._gainNode.gain.value=0)}unmute(){this._muted&&(this._muted=!1,this._gainNode.gain.value=this.dbToVolume(this._volume))}pause(){this._paused||(this._audioContext.suspend(),this._paused=!0)}unpause(){this._paused&&(this._audioContext.resume(),this._paused=!1)}clampGain(){this._volume<-80?this._volume=-80:this._volume>5&&(this._volume=5)}dbToVolume(t){return Math.pow(10,t/20)}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]}compile(){this.getAllTracks().forEach(t=>{t.startsWith("repeat:")&&this.compileRepeat(t)})}compileRepeat(t){if("function"!=typeof this._timings[t])throw Error(`The value of repeat track "${t}" is not a function`);const e=t.split(":");if(4!=e.length)throw Error(`The repeat track "${t}" does not supply the valid amount of arguments`);const i=parseFloat(e[1]),s=parseFloat(e[2]),n=parseFloat(e[3]);let o=i;const a={};do{a[o.toString()]=this._timings[t],o+=s}while(o<n);delete this._timings[t],this._timings[`repeat-${Math.random().toString(36).substring(7)}`]=a}start(){this._startTime=this._audioContext.currentTime,null!==this._audioElement?this._audioElement.play():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{t as default};
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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"}
@@ -1,2 +1,2 @@
1
- var t=function(){function t(t,e){this._audioContext=void 0,this._audioElement=null,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,null===e&&console.error("You need to specify a timings object"),this._timings=e,this._audioContext=new AudioContext,this._gainNode=this._audioContext.createGain(),this._gainNode.connect(this._audioContext.destination),this.getOptimalAudioSource(t),this.compile()}var e,i=t.prototype;return i.getOptimalAudioSource=function(t){"string"==typeof t?this.fetchAudioSource(t):"object"!=typeof t||"AUDIO"!=t.tagName&&"VIDEO"!=t.tagName||this.attachAudioElementSource(t)},i.fetchAudioSource=function(t){var e=this,i=this._audioContext.createBufferSource();fetch(t).then(function(t){return t.arrayBuffer()}).then(function(t){e._audioContext.decodeAudioData(t).then(function(t){i.buffer=t,i.connect(e._gainNode),document.dispatchEvent(new Event("misairu.ready")),e._audioSource=i})})},i.attachAudioElementSource=function(t){var e=this._audioContext.createMediaElementSource(t);this._audioElement=t,e.connect(this._gainNode),this._audioSource=e},i.mute=function(){this._muted||(this._muted=!0,this._gainNode.gain.value=0)},i.unmute=function(){this._muted&&(this._muted=!1,this._gainNode.gain.value=this.dbToVolume(this._volume))},i.pause=function(){this._paused||(this._audioContext.suspend(),this._paused=!0)},i.unpause=function(){this._paused&&(this._audioContext.resume(),this._paused=!1)},i.clampGain=function(){this._volume<-80?this._volume=-80:this._volume>5&&(this._volume=5)},i.dbToVolume=function(t){return Math.pow(10,t/20)},i.setCacheEntry=function(t,e){this._cache[t]=e},i.getCacheEntry=function(t){return this._cache[t]},i.getAllTracks=function(){return Object.keys(this._timings)},i.getActiveTimingKey=function(t,e){var i=Object.keys(this._timings[t]).filter(function(t){if(e>=parseFloat(t))return!0});return i[i.length-1]},i.compile=function(){var t=this;this.getAllTracks().forEach(function(e){e.startsWith("repeat:")&&t.compileRepeat(e)})},i.compileRepeat=function(t){if("function"!=typeof this._timings[t])throw Error('The value of repeat track "'+t+'" is not a function');var e=t.split(":");if(4!=e.length)throw Error('The repeat track "'+t+'" does not supply the valid amount of arguments');var i=parseFloat(e[1]),n=parseFloat(e[2]),o=parseFloat(e[3]),a=i,u={};do{u[a.toString()]=this._timings[t],a+=n}while(a<o);delete this._timings[t],this._timings["repeat-"+Math.random().toString(36).substring(7)]=u},i.start=function(){this._startTime=this._audioContext.currentTime,null!==this._audioElement?this._audioElement.play():this._audioSource.start(),this.startEventHandling()},i.startEventHandling=function(){var t=this;null==this._eventHandler&&(this._eventHandler=window.requestAnimationFrame(function(){t.handleEvents()}))},i.handleEvents=function(){var t=this,e=this._audioContext.currentTime-this._startTime;this.getAllTracks().forEach(function(i){var n=t.getActiveTimingKey(i,e);null!==n&&t.getCacheEntry(i)!=n&&(t.executeEvent(i,n,e),t.setCacheEntry(i,n))}),this._eventHandler=window.requestAnimationFrame(function(){t.handleEvents()})},i.executeEvent=function(t,e,i){this._timings[t][e](this,e,t,i)},(e=[{key:"volume",get:function(){return this._volume},set:function(t){this._volume=t,this.clampGain(),this._muted||(this._gainNode.gain.value=this.dbToVolume(this._volume))}}])&&function(t,e){for(var i=0;i<e.length;i++){var n=e[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}(t.prototype,e),t}();export{t as default};
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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"}
@@ -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._audioElement=null,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,null===e&&console.error("You need to specify a timings object"),this._timings=e,this._audioContext=new AudioContext,this._gainNode=this._audioContext.createGain(),this._gainNode.connect(this._audioContext.destination),this.getOptimalAudioSource(t),this.compile()}var e,i=t.prototype;return i.getOptimalAudioSource=function(t){"string"==typeof t?this.fetchAudioSource(t):"object"!=typeof t||"AUDIO"!=t.tagName&&"VIDEO"!=t.tagName||this.attachAudioElementSource(t)},i.fetchAudioSource=function(t){var e=this,i=this._audioContext.createBufferSource();fetch(t).then(function(t){return t.arrayBuffer()}).then(function(t){e._audioContext.decodeAudioData(t).then(function(t){i.buffer=t,i.connect(e._gainNode),document.dispatchEvent(new Event("misairu.ready")),e._audioSource=i})})},i.attachAudioElementSource=function(t){var e=this._audioContext.createMediaElementSource(t);this._audioElement=t,e.connect(this._gainNode),this._audioSource=e},i.mute=function(){this._muted||(this._muted=!0,this._gainNode.gain.value=0)},i.unmute=function(){this._muted&&(this._muted=!1,this._gainNode.gain.value=this.dbToVolume(this._volume))},i.pause=function(){this._paused||(this._audioContext.suspend(),this._paused=!0)},i.unpause=function(){this._paused&&(this._audioContext.resume(),this._paused=!1)},i.clampGain=function(){this._volume<-80?this._volume=-80:this._volume>5&&(this._volume=5)},i.dbToVolume=function(t){return Math.pow(10,t/20)},i.setCacheEntry=function(t,e){this._cache[t]=e},i.getCacheEntry=function(t){return this._cache[t]},i.getAllTracks=function(){return Object.keys(this._timings)},i.getActiveTimingKey=function(t,e){var i=Object.keys(this._timings[t]).filter(function(t){if(e>=parseFloat(t))return!0});return i[i.length-1]},i.compile=function(){var t=this;this.getAllTracks().forEach(function(e){e.startsWith("repeat:")&&t.compileRepeat(e)})},i.compileRepeat=function(t){if("function"!=typeof this._timings[t])throw Error('The value of repeat track "'+t+'" is not a function');var e=t.split(":");if(4!=e.length)throw Error('The repeat track "'+t+'" does not supply the valid amount of arguments');var i=parseFloat(e[1]),n=parseFloat(e[2]),o=parseFloat(e[3]),u=i,a={};do{a[u.toString()]=this._timings[t],u+=n}while(u<o);delete this._timings[t],this._timings["repeat-"+Math.random().toString(36).substring(7)]=a},i.start=function(){this._startTime=this._audioContext.currentTime,null!==this._audioElement?this._audioElement.play():this._audioSource.start(),this.startEventHandling()},i.startEventHandling=function(){var t=this;null==this._eventHandler&&(this._eventHandler=window.requestAnimationFrame(function(){t.handleEvents()}))},i.handleEvents=function(){var t=this,e=this._audioContext.currentTime-this._startTime;this.getAllTracks().forEach(function(i){var n=t.getActiveTimingKey(i,e);null!==n&&t.getCacheEntry(i)!=n&&(t.executeEvent(i,n,e),t.setCacheEntry(i,n))}),this._eventHandler=window.requestAnimationFrame(function(){t.handleEvents()})},i.executeEvent=function(t,e,i){this._timings[t][e](this,e,t,i)},(e=[{key:"volume",get:function(){return this._volume},set:function(t){this._volume=t,this.clampGain(),this._muted||(this._gainNode.gain.value=this.dbToVolume(this._volume))}}])&&function(t,e){for(var i=0;i<e.length;i++){var n=e[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}(t.prototype,e),t}()});
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
@@ -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.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://github.com/pixeldesu/misairu.git"
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://github.com/pixeldesu/misairu/issues"
34
+ "url": "https://codeberg.org/pixeldesu/misairu/issues"
35
35
  },
36
- "homepage": "https://github.com/pixeldesu/misairu#readme",
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 { EventCache, TimingObject } from './types'
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 = this.dbToVolume(this._volume)
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.compile()
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.fetchAudioSource(audioSource)
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 = this.dbToVolume(this._volume)
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 compile special sections in the timing configuration
229
+ * Main method to process special sections in the timing configuration
285
230
  *
286
231
  * @internal
287
232
  */
288
- private compile(): void {
289
- this.getAllTracks().forEach((track) => {
290
- if (track.startsWith('repeat:')) {
291
- this.compileRepeat(track)
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
- if (this._audioElement !== null) {
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
+ }
@@ -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