misairu 4.1.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/.eslintignore ADDED
@@ -0,0 +1,3 @@
1
+ .eslintrc.js
2
+ dist/
3
+ node_modules/
package/.eslintrc.js ADDED
@@ -0,0 +1,18 @@
1
+ module.exports = {
2
+ root: true,
3
+ parser: '@typescript-eslint/parser',
4
+ plugins: [
5
+ '@typescript-eslint',
6
+ 'prettier'
7
+ ],
8
+ extends: [
9
+ 'eslint:recommended',
10
+ 'plugin:@typescript-eslint/recommended',
11
+ 'prettier'
12
+ ],
13
+ rules: {
14
+ 'prettier/prettier': 'warn',
15
+ '@typescript-eslint/no-var-requires': 'off',
16
+ '@typescript-eslint/no-non-null-assertion': 'off'
17
+ }
18
+ }
@@ -0,0 +1,2 @@
1
+ node_modules/
2
+ dist/
@@ -0,0 +1,8 @@
1
+ {
2
+ "trailingComma": "es5",
3
+ "tabWidth": 2,
4
+ "semi": false,
5
+ "printWidth": 100,
6
+ "singleQuote": true,
7
+ "bracketSpacing": true
8
+ }
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2017 pixeldesu
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1
+ MIT License
2
+
3
+ Copyright (c) 2017 pixeldesu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
package/README.md CHANGED
@@ -1,116 +1,145 @@
1
- # misairu
2
- _ミサイル // MISSILE_
3
-
4
- :rocket: Fire events for specific timeframes easily
5
-
6
- ## Getting it
7
-
8
- **NPM:**
9
-
10
- ```
11
- $ npm install misairu
12
- ```
13
-
14
- **unpkg:**
15
-
16
- ```html
17
- <script src="https://unpkg.com/misairu/misairu.js"></script>
18
- ```
19
-
20
- Or you can go the traditional way, grab `misairu.js` from the repository and put it somewhere in your project with a `<script>` tag!
21
-
22
- ## Usage
23
-
24
- **Text:**
25
-
26
- ```js
27
- const media = document.getElementById('audioplayer')
28
- const text = document.getElementById('text')
29
-
30
- // define timings, tracks and their functions
31
- const timings = {
32
- "default": {
33
- "0": function() {
34
- text.innerHTML = 'New text at start'
35
- }
36
- },
37
- "default:2": {
38
- "10": function() {
39
- document.body.style.backgroundColor = 'lightblue'
40
- text.innerHTML = 'New text and background color after 10 seconds'
41
- }
42
- }
43
- }
44
-
45
- // create misairu instance
46
- const ev = new misairu(media, timings)
47
-
48
- // you only need this if your audiosource is external e.g. a link to an audio file
49
- document.addEventListener('misairu.ready', function(event) {
50
- // start playback once misairu is ready
51
- ev.start()
52
- })
53
- ```
54
-
55
- ## Reference
56
-
57
- ### `new misairu(audioSource, timings)`
58
-
59
- * **`audioSource`:** (required) Link to an audio file or `<audio>`-Tag DOM Element
60
- * **`timings`:** (required) Object where the keys represent the time and the values are functions or function references
61
-
62
- **Note on `audioSource`:**
63
-
64
- If you use a `HTMLMediaElement`, misairu won't fire the `misairu.ready` event, as the content is already given. To be really
65
- sure that everything starts when you want it to, give your media element the attribute `preload="auto"` to have it pre-buffer
66
- ahead of usage.
67
-
68
- ### `misairu.start()`
69
-
70
- This will start playback of the specified audio and execute the events at the given time. If your audio source is external, the audio buffer is
71
- loaded asynchronously and you should listen to the `misairu.ready` event on `document` and execute this function once
72
- the event was emitted.
73
-
74
- ### `misairu.[un]pause()`
75
-
76
- Pauses/Resumes playback and event execution.
77
-
78
- ### `misairu.[un]mute()`
79
-
80
- (Un)mutes audio of the misairu instance.
81
-
82
- ### `misairu.volume = x`
83
-
84
- Sets the volume to `x`, can be a value between -80 and 5.
85
-
86
- ### Anatomy of the `timings` object
87
-
88
- ```js
89
- // timings object, you pass this to the misairu constructor
90
- const timings = {
91
- // track object
92
- "default": {
93
- // timing key - function
94
- "0": function (instance, timingKey, track, time) {
95
- // instance - misairu instance
96
- // timingKey - current timing key ("0")
97
- // track - current track ("default")
98
- // time - current time (accurate time calculated from the start time and audio context time)
99
-
100
- // >> put some code here <<
101
- }
102
- }
103
- }
104
- ```
105
-
106
- All of the parameters passed to a timing function are optional and don't need to be used as they are only passed for convenience, so you can omit them.
107
-
108
- ## Shoutouts
109
-
110
- * [Rocket](https://github.com/rocket/rocket) for basically being the "big brother" of this small project
111
- * [coderobe](https://github.com/coderobe) for [microhues](https://github.com/coderobe/microhues), which helped me understanding the Web Audio APIs
112
- * [ed](https://github.com/9001), as I'm basically building this to have an easy framework to build as creative things as he does
113
-
114
- ## License
115
-
1
+ # misairu
2
+ _ミサイル // MISSILE_
3
+
4
+ :rocket: Fire events for specific timeframes easily
5
+
6
+ ## Getting it
7
+
8
+ **NPM:**
9
+
10
+ ```
11
+ $ npm install misairu
12
+ ```
13
+
14
+ **unpkg:**
15
+
16
+ ```html
17
+ <script src="https://unpkg.com/misairu/dist/misairu.iife.js"></script>
18
+ ```
19
+
20
+ Or you can go the traditional way, grab `misairu.js` from the repository and put it somewhere in your project with a `<script>` tag!
21
+
22
+ ## Usage
23
+
24
+ **Text:**
25
+
26
+ ```js
27
+ const media = document.getElementById('audioplayer')
28
+ const text = document.getElementById('text')
29
+
30
+ // define timings, tracks and their functions
31
+ const timings = {
32
+ "default": {
33
+ "0": function() {
34
+ text.innerHTML = 'New text at start'
35
+ }
36
+ },
37
+ "default:2": {
38
+ "10": function() {
39
+ document.body.style.backgroundColor = 'lightblue'
40
+ text.innerHTML = 'New text and background color after 10 seconds'
41
+ }
42
+ }
43
+ }
44
+
45
+ // create misairu instance
46
+ const ev = new Misairu(media, timings)
47
+
48
+ // you only need this if your audiosource is external e.g. a link to an audio file
49
+ document.addEventListener('misairu.ready', function(event) {
50
+ // start playback once misairu is ready
51
+ ev.start()
52
+ })
53
+ ```
54
+
55
+ ## Reference
56
+
57
+ ### `new Misairu(audioSource, timings)`
58
+
59
+ * **`audioSource`:** (required) Link to an audio file or `<audio>`-Tag DOM Element
60
+ * **`timings`:** (required) Object where the keys represent the time and the values are functions or function references
61
+
62
+ **Note on `audioSource`:**
63
+
64
+ If you use a `HTMLMediaElement`, misairu won't fire the `misairu.ready` event, as the content is already given. To be really
65
+ sure that everything starts when you want it to, give your media element the attribute `preload="auto"` to have it pre-buffer
66
+ ahead of usage.
67
+
68
+ ### `misairu.start()`
69
+
70
+ This will start playback of the specified audio and execute the events at the given time. If your audio source is external, the audio buffer is
71
+ loaded asynchronously and you should listen to the `misairu.ready` event on `document` and execute this function once
72
+ the event was emitted.
73
+
74
+ ### `misairu.[un]pause()`
75
+
76
+ Pauses/Resumes playback and event execution.
77
+
78
+ ### `misairu.[un]mute()`
79
+
80
+ (Un)mutes audio of the misairu instance.
81
+
82
+ ### `misairu.volume = x`
83
+
84
+ Sets the volume to `x`, can be a value between -80 and 5.
85
+
86
+ ### Anatomy of the `timings` object
87
+
88
+ ```js
89
+ // timings object, you pass this to the misairu constructor
90
+ const timings = {
91
+ // track object
92
+ "default": {
93
+ // timing key - function
94
+ "0": function (instance, timingKey, track, time) {
95
+ // instance - misairu instance
96
+ // timingKey - current timing key ("0")
97
+ // track - current track ("default")
98
+ // time - current time (accurate time calculated from the start time and audio context time)
99
+
100
+ // >> put some code here <<
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ All of the parameters passed to a timing function are optional and don't need to be used as they are only passed for convenience, so you can omit them.
107
+
108
+ ### `repeat` tracks
109
+
110
+ It's possible to define repeating actions for a specific timeframe with special track type, following the naming scheme of `repeat:start-time:interval:end-time`.
111
+
112
+ As an example:
113
+
114
+ ```js
115
+ const timings = {
116
+ "repeat:1:2:10": myCoolFunction
117
+ }
118
+ ```
119
+
120
+ On creation of the `misairu` instance, the timing object gets compiled, so the repeat statement will be unfolded into:
121
+
122
+ ```js
123
+ // misairu_instance.timings
124
+ {
125
+ // the original track "repeat:1:2:10" was deleted
126
+ // and replaced with a repeat-randomhash track containing all timed events
127
+ "repeat-xjas34f": {
128
+ "1": myCoolFunction,
129
+ "3": myCoolFunction,
130
+ "5": myCoolFunction,
131
+ "7": myCoolFunction,
132
+ "9": myCoolFunction
133
+ }
134
+ }
135
+ ```
136
+
137
+ ## Shoutouts
138
+
139
+ * [Rocket](https://github.com/rocket/rocket) for basically being the "big brother" of this small project
140
+ * [coderobe](https://github.com/coderobe) for [microhues](https://github.com/coderobe/microhues), which helped me understanding the Web Audio APIs
141
+ * [ed](https://github.com/9001), as I'm basically building this to have an easy framework to build as creative things as he does
142
+
143
+ ## License
144
+
116
145
  misairu is licensed under the MIT license
@@ -0,0 +1,2 @@
1
+ export * from './types';
2
+ export { Misairu as default } from './misairu';
@@ -0,0 +1,161 @@
1
+ import { TimingObject } from './types';
2
+ /**
3
+ * Main misairu class
4
+ */
5
+ export declare class Misairu {
6
+ /**
7
+ * The HTML5 AudioContext used for accurately timing our events
8
+ */
9
+ private readonly _audioContext;
10
+ /**
11
+ * A source node to play our audio from, either a buffered source from a downloaded media file or a media element source
12
+ *
13
+ * @default null
14
+ */
15
+ private _audioSource;
16
+ /**
17
+ * A object containing the last executed time key per track to not execute an event on every tick
18
+ *
19
+ * @default {}
20
+ */
21
+ private _cache;
22
+ /**
23
+ * Reference to the `requestAnimationFrame` handler
24
+ *
25
+ * @default null
26
+ */
27
+ private _eventHandler;
28
+ /**
29
+ * Gain node from our audio context to control audio volume
30
+ */
31
+ private readonly _gainNode;
32
+ /**
33
+ * Boolean value describing if this instance is currently muted
34
+ */
35
+ private _muted;
36
+ /**
37
+ * Boolean value describing if this instance is currently paused
38
+ */
39
+ private _paused;
40
+ /**
41
+ * The time when event handling was started, based on the audio contexts `currentTime` when `start()` was called
42
+ */
43
+ private _startTime;
44
+ /**
45
+ * Object containing all timing tracks and events to be executed
46
+ */
47
+ private _timings;
48
+ /**
49
+ * Volume of the current instance
50
+ */
51
+ private _volume;
52
+ /**
53
+ * List of (predefined) track processors
54
+ */
55
+ private _processors;
56
+ get volume(): number;
57
+ set volume(db: number);
58
+ /**
59
+ * misairu constructor
60
+ *
61
+ * @param audioSource a string or HTML element to be used as audio source
62
+ * @param timings a object containing event timing information
63
+ */
64
+ constructor(audioSource: string | HTMLMediaElement, timings: TimingObject);
65
+ /**
66
+ * Method to figure out the best course of action to take with the passed audio source
67
+ *
68
+ * @param audioSource the audio source `Misairu` has been constructed with
69
+ * @internal
70
+ */
71
+ private getOptimalAudioSource;
72
+ /**
73
+ * Mutes the instance audio
74
+ *
75
+ * @public
76
+ */
77
+ mute(): void;
78
+ /**
79
+ * Unmutes the instance audio
80
+ *
81
+ * @public
82
+ */
83
+ unmute(): void;
84
+ /**
85
+ * Pauses instance playback
86
+ *
87
+ * @public
88
+ */
89
+ pause(): void;
90
+ /**
91
+ * Resumes instance playback
92
+ *
93
+ * @public
94
+ */
95
+ unpause(): void;
96
+ /**
97
+ * Set a cache entry for the given track
98
+ *
99
+ * @param track track to set a cache entry for
100
+ * @param entry value of the cache entry
101
+ * @internal
102
+ */
103
+ private setCacheEntry;
104
+ /**
105
+ * Get cache entry for the given track
106
+ *
107
+ * @param track track to get a cache entry for
108
+ * @returns a cache entry
109
+ * @internal
110
+ */
111
+ private getCacheEntry;
112
+ /**
113
+ * Get all tracks from the timing configuration
114
+ *
115
+ * @returns a list of all track names
116
+ * @internal
117
+ */
118
+ private getAllTracks;
119
+ /**
120
+ * Returns the current active timing key for a given track
121
+ *
122
+ * @param track track to get the timing key from
123
+ * @param currentTime current playback time
124
+ * @returns the current active timing key
125
+ * @internal
126
+ */
127
+ private getActiveTimingKey;
128
+ /**
129
+ * Main method to process special sections in the timing configuration
130
+ *
131
+ * @internal
132
+ */
133
+ private processTracks;
134
+ /**
135
+ * Start audio playback and event handling
136
+ *
137
+ * @public
138
+ */
139
+ start(): void;
140
+ /**
141
+ * Method to start event handler loop
142
+ *
143
+ * @internal
144
+ */
145
+ private startEventHandling;
146
+ /**
147
+ * Event handler method, is running in a loop using `requestAnimationFrame`
148
+ *
149
+ * @internal
150
+ */
151
+ private handleEvents;
152
+ /**
153
+ * Method to execute the event for a given timing key on a given track
154
+ *
155
+ * @param track the track to execute the event on
156
+ * @param timingKey the timing key to execute
157
+ * @param time current playback time
158
+ * @internal
159
+ */
160
+ private executeEvent;
161
+ }
@@ -0,0 +1,2 @@
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
+ //# sourceMappingURL=misairu.iife.js.map
@@ -0,0 +1 @@
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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"}
@@ -0,0 +1,2 @@
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
+ //# sourceMappingURL=misairu.js.map
@@ -0,0 +1 @@
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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"}
@@ -0,0 +1,2 @@
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
+ //# sourceMappingURL=misairu.modern.js.map