File size: 2,991 Bytes
6f25f68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/**
 * A simple audio pipe that allows you to push audio data to the audio output.
 * Will allow you to sequentially play audio data.
 */
export class AudioPipe {
    constructor() {
        this.initialized = false;
        this.nextAvailableTime = 0;
        this.gain = 1;
    }

    /**
     * Initializes the audio pipe by creating an AudioContext.
     */
    initialize() {
        this.audioContext = new AudioContext();
        this.gainNode = this.audioContext.createGain();
        this.gainNode.connect(this.audioContext.destination);
        this.gainNode.gain.value = this.gain;
        this.initialized = true;
    }

    /**
     * @returns {number} The volume of the audio pipe [0,1].
     */
    get volume() {
        return this.gain;
    }

    /**
     * Sets the volume of the audio pipe.
     *
     * @param {number} value - The volume of the audio pipe [0,1].
     */
    set volume(value) {
        if (this.initialized) {
            // Change volume [0,1] to gain [-1,1]
            this.gainNode.gain.value = 2 * value - 1;
        }
        this.gain = value;
    }

    /**
     * Pushes audio data to the audio output.
     *
     * @param {Float32Array} data - The audio data to play.
     * @param {number} [sampleRate=48000] - The sample rate of the audio data.
     */
    push(data, sampleRate = 48000) {
        if (!this.initialized) {
            this.initialize();
        }
        const audioBuffer = new AudioBuffer({
            length: data.length,
            numberOfChannels: 1,
            sampleRate: sampleRate
        });
        audioBuffer.copyToChannel(data, 0);
        const audioBufferNode = new AudioBufferSourceNode(
            this.audioContext,
            { buffer: audioBuffer }
        );
        audioBufferNode.connect(this.gainNode);
        audioBufferNode.start(this.nextAvailableTime);
        if (this.nextAvailableTime > this.audioContext.currentTime) {
            // There is already at least one scheduled node, so we need to update the next available time
            this.nextAvailableTime += audioBuffer.duration;
        } else {
            this.nextAvailableTime = this.audioContext.currentTime + audioBuffer.duration;
        }
        return audioBufferNode;
    }

    /**
     * Pushes silence to the audio output.
     *
     * @param {number} duration - The duration of the silence in seconds.
     * @param {number} [sampleRate=48000] - The sample rate of the silence.
     */
    pushSilence(duration, sampleRate = 48000) {
        if (!this.initialized) {
            return; // Don't initialize for silence
        }
        const data = new Float32Array(Math.floor(duration * sampleRate));
        this.push(data, sampleRate);
    }

    /**
     * @returns {boolean} Whether the audio pipe is currently playing audio.
     */
    get playing() {
        if (!this.initialized) {
            return false;
        }
        return this.audioContext.currentTime < this.nextAvailableTime;
    }
}