PageSourceSearch

https://luigipulcini.com/js/wavesurfer/webaudio.js

js luigipulcini.com collected 2026-10-07 05:46:52 UTC 11,582 bytes, 415 lines download raw bytes

vendor: 4,044 bytes, lines 1-138
1'use strict';
2
3WaveSurfer.WebAudio = {
4    scriptBufferSize: 256,
5    PLAYING_STATE: 0,
6    PAUSED_STATE: 1,
7    FINISHED_STATE: 2,
8
9    supportsWebAudio: function () {
10        return !!(window.AudioContext || window.webkitAudioContext);
11    },
12
13    getAudioContext: function () {
14        if (!WaveSurfer.WebAudio.audioContext) {
15            WaveSurfer.WebAudio.audioContext = new (
16                window.AudioContext || window.webkitAudioContext
17            );
18        }
19        return WaveSurfer.WebAudio.audioContext;
20    },
21
22    getOfflineAudioContext: function (sampleRate) {
23        if (!WaveSurfer.WebAudio.offlineAudioContext) {
24            WaveSurfer.WebAudio.offlineAudioContext = new (
25                window.OfflineAudioContext || window.webkitOfflineAudioContext
26            )(1, 2, sampleRate);
27        }
28        return WaveSurfer.WebAudio.offlineAudioContext;
29    },
30
31    init: function (params) {
32        this.params = params;
33        this.ac = params.audioContext || this.getAudioContext();
34
35        this.lastPlay = this.ac.currentTime;
36        this.startPosition = 0;
37        this.scheduledPause = null;
38
39        this.states = [
40            Object.create(WaveSurfer.WebAudio.state.playing),
41            Object.create(WaveSurfer.WebAudio.state.paused),
42            Object.create(WaveSurfer.WebAudio.state.finished)
43        ];
44
45        this.createVolumeNode();
46        this.createScriptNode();
47        this.createAnalyserNode();
48
49        this.setState(this.PAUSED_STATE);
50        this.setPlaybackRate(this.params.audioRate);
51    },
52
53    disconnectFilters: function () {
54        if (this.filters) {
55            this.filters.forEach(function (filter) {
56                filter && filter.disconnect();
57            });
58            this.filters = null;
59            // Reconnect direct path
60            this.analyser.connect(this.gainNode);
61        }
62    },
63
64    setState: function (state) {
65        if (this.state !== this.states[state]) {
66            this.state = this.states[state];
67            this.state.init.call(this);
68        }
69    },
70
71    // Unpacked filters
72    setFilter: function () {
73        this.setFilters([].slice.call(arguments));
74    },
75
76    /**
77     * @param {Array} filters Packed ilters array
78     */
79    setFilters: function (filters) {
80        // Remove existing filters
81        this.disconnectFilters();
82
83        // Insert filters if filter array not empty
84        if (filters && filters.length) {
85            this.filters = filters;
86
87            // Disconnect direct path before inserting filters
88            this.analyser.disconnect();
89
90            // Connect each filter in turn
91            filters.reduce(function (prev, curr) {
92                prev.connect(curr);
93                return curr;
94            }, this.analyser).connect(this.gainNode);
95        }
96
97    },
98
99    createScriptNode: function () {
100        if (this.ac.createScriptProcessor) {
101            this.scriptNode = this.ac.createScriptProcessor(this.scriptBufferSize);
102        } else {
103            this.scriptNode = this.ac.createJavaScriptNode(this.scriptBufferSize);
104        }
105
106        this.scriptNode.connect(this.ac.destination);
107    },
108
109    addOnAudioProcess: function () {
110        var my = this;
111
112        this.scriptNode.onaudioprocess = function () {
113            var time = my.getCurrentTime();
114
115            if (time >= my.getDuration()) {
116                my.setState(my.FINISHED_STATE);
117                my.fireEvent('pause');
118            } else if (time >= my.scheduledPause) {
119                my.setState(my.PAUSED_STATE);
120                my.fireEvent('pause');
121            } else if (my.state === my.states[my.PLAYING_STATE]) {
122                my.fireEvent('audioprocess', time);
123            }
124        };
125    },
126
127    removeOnAudioProcess: function () {
128        this.scriptNode.onaudioprocess = null;
129    },
130
131    createAnalyserNode: function () {
132        this.analyser = this.ac.createAnalyser();
133        this.analyser.connect(this.gainNode);
134    },
135
136    /**
137     * Create the gain node needed to control the playback volume.
138     */
139    createVolumeNode: function () {
140        // Create gain node using the AudioContext
141        if (this.ac.createGain) {
142            this.gainNode = this.ac.createGain();
143        } else {
144            this.gainNode = this.ac.createGainNode();
145        }
146        // Add the gain node to the graph
147        this.gainNode.connect(this.ac.destination);
148    },
149
150    /**
151     * Set the gain to a new value.
152     *
153     * @param {Number} newGain The new gain, a floating point value
154     * between 0 and 1. 0 being no gain and 1 being maximum gain.
155     */
156    setVolume: function (newGain) {
157        this.gainNode.gain.value = newGain;
158    },
159
160    /**
161     * Get the current gain.
162     *
163     * @returns {Number} The current gain, a floating point value
164     * between 0 and 1. 0 being no gain and 1 being maximum gain.
165     */
166    getVolume: function () {
167        return this.gainNode.gain.value;
168    },
169
170    decodeArrayBuffer: function (arraybuffer, callback, errback) {
171        if (!this.offlineAc) {
172            this.offlineAc = this.getOfflineAudioContext(this.ac ? this.ac.sampleRate : 44100);
173        }
174        this.offlineAc.decodeAudioData(arraybuffer, (function (data) {
175            callback(data);
176        }).bind(this), errback);
177    },
178
179    /**
180     * Compute the max and min value of the waveform when broken into
181     * <length> subranges.
182     * @param {Number} How many subranges to break the waveform into.
183     * @returns {Array} Array of 2*<length> peaks or array of arrays
184     * of peaks consisting of (max, min) values for each subrange.
185     */
186    getPeaks: function (length) {
187        var sampleSize = this.buffer.length / length;
188        var sampleStep = ~~(sampleSize / 10) || 1;
189        var channels = this.buffer.numberOfChannels;
190        var splitPeaks = [];
191        var mergedPeaks = [];
192
193        for (var c = 0; c < channels; c++) {
194            var peaks = splitPeaks[c] = [];
195            var chan = this.buffer.getChannelData(c);
196
197            for (var i = 0; i < length; i++) {
198                var start = ~~(i * sampleSize);
199                var end = ~~(start + sampleSize);
200                var min = chan[0];
201                var max = chan[0];
202
203                for (var j = start; j < end; j += sampleStep) {
204                    var value = chan[j];
205
206                    if (value > max) {
207                        max = value;
208                    }
209
210                    if (value < min) {
211                        min = value;
212                    }
213                }
214
215                peaks[2 * i] = max;
216                peaks[2 * i + 1] = min;
217
218                if (c == 0 || max > mergedPeaks[2 * i]) {
219                    mergedPeaks[2 * i] = max;
220                }
221
222                if (c == 0 || min < mergedPeaks[2 * i + 1]) {
223                    mergedPeaks[2 * i + 1] = min;
224                }
225            }
226        }
227
228        return this.params.splitChannels ? splitPeaks : mergedPeaks;
229    },
230
231    getPlayedPercents: function () {
232        return this.state.getPlayedPercents.call(this);
233    },
234
235    disconnectSource: function () {
236        if (this.source) {
237            this.source.disconnect();
238        }
239    },
240
241    destroy: function () {
242        if (!this.isPaused()) {
243            this.pause();
244        }
245        this.unAll();
246        this.buffer = null;
247        this.disconnectFilters();
248        this.disconnectSource();
249        this.gainNode.disconnect();
250        this.scriptNode.disconnect();
251        this.analyser.disconnect();
252    },
253
254    load: function (buffer) {
255        this.startPosition = 0;
256        this.lastPlay = this.ac.currentTime;
257        this.buffer = buffer;
258        this.createSource();
259    },
260
261    createSource: function () {
262        this.disconnectSource();
263        this.source = this.ac.createBufferSource();
264
265        //adjust for old browsers.
266        this.source.start = this.source.start || this.source.noteGrainOn;
267        this.source.stop = this.source.stop || this.source.noteOff;
268
269        this.source.playbackRate.value = this.playbackRate;
270        this.source.buffer = this.buffer;
271        this.source.connect(this.analyser);
272    },
273
274    isPaused: function () {
275        return this.state !== this.states[this.PLAYING_STATE];
276    },
277
278    getDuration: function () {
279        if (!this.buffer) {
280            return 0;
281        }
282        return this.buffer.duration;
283    },
284
285    seekTo: function (start, end) {
286        this.scheduledPause = null;
287
288        if (start == null) {
289            start = this.getCurrentTime();
290            if (start >= this.getDuration()) {
291                start = 0;
292            }
293        }
294        if (end == null) {
295            end = this.getDuration();
296        }
297
298        this.startPosition = start;
299        this.lastPlay = this.ac.currentTime;
300
301        if (this.state === this.states[this.FINISHED_STATE]) {
302            this.setState(this.PAUSED_STATE);
303        }
304
305        return { start: start, end: end };
306    },
307
308    getPlayedTime: function () {
309        return (this.ac.currentTime - this.lastPlay) * this.playbackRate;
310    },
311
312    /**
313     * Plays the loaded audio region.
314     *
315     * @param {Number} start Start offset in seconds,
316     * relative to the beginning of a clip.
317     * @param {Number}
317 end When to stop
318     * relative to the beginning of a clip.
319     */
320    play: function (start, end) {
321        // need to re-create source on each playback
322        this.createSource();
323
324        var adjustedTime = this.seekTo(start, end);
325
326        start = adjustedTime.start;
327        end = adjustedTime.end;
328
329        this.scheduledPause = end;
330
331        this.source.start(0, start, end - start);
332
333        this.setState(this.PLAYING_STATE);
334
335        this.fireEvent('play');
336    },
337
338    /**
339     * Pauses the loaded audio.
340     */
341    pause: function () {
342        this.scheduledPause = null;
343
344        this.startPosition += this.getPlayedTime();
345        this.source && this.source.stop(0);
346
347        this.setState(this.PAUSED_STATE);
348
349        this.fireEvent('pause');
350    },
351
352    /**
353    *   Returns the current time in seconds relative to the audioclip's duration.
354    */
355    getCurrentTime: function () {
356        return this.state.getCurrentTime.call(this);
357    },
358
359    /**
360     * Set the audio source playback rate.
361     */
362    setPlaybackRate: function (value) {
363        value = value || 1;
364        if (this.isPaused()) {
365            this.playbackRate = value;
366        } else {
367            this.pause();
368            this.playbackRate = value;
369            this.play();
370        }
371    }
372};
373
374WaveSurfer.WebAudio.state = {};
375
376WaveSurfer.WebAudio.state.playing = {
377    init: function () {
378        this.addOnAudioProcess();
379    },
380    getPlayedPercents: function () {
381        var duration = this.getDuration();
382        return (this.getCurrentTime() / duration) || 0;
383    },
384    getCurrentTime: function () {
385        return this.startPosition + this.getPlayedTime();
386    }
387};
388
389WaveSurfer.WebAudio.state.paused = {
390    init: function () {
391        this.removeOnAudioProcess();
392    },
393    getPlayedPercents: function () {
394        var duration = this.getDuration();
395        return (this.getCurrentTime() / duration) || 0;
396    },
397    getCurrentTime: function () {
398        return this.startPosition;
399    }
400};
401
402WaveSurfer.WebAudio.state.finished = {
403    init: function () {
404        this.removeOnAudioProcess();
405        this.fireEvent('finish');
406    },
407    getPlayedPercents: function () {
408        return 1;
409    },
410    getCurrentTime: function () {
411        return this.getDuration();
412    }
413};
414
415WaveSurfer.util.extend(WaveSurfer.WebAudio, WaveSurfer.Observer);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.