summaryrefslogtreecommitdiffstats
path: root/player.mjs
blob: 6a4e39a8d23fced37bcae72cb6b3e0aef39b8e12 (plain)
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
99
100
101
102
103
104
import Trombone from './vend/trombone.mjs';

export default class extends HTMLElement {
    static name = 'x-player'
    static register() {
        console.debug('Registering Element', this.name, this);
        customElements.define(this.name, this);
    }

    // A4
    aFreq = 440;

    // oscillatornodes
    notes = [];
    getOffsets = () => [];

    constructor() {
        super();
        console.debug('Player#constructor', this);
    }

    connectedCallback() {
        this.audioCtx = new AudioContext();
        this.wave = new PeriodicWave(this.audioCtx, {
            real: Trombone.real,
            imag: Trombone.imag
        });

        this.globalGain = this.audioCtx.createGain();
        this.globalGain.connect(this.audioCtx.destination);
        this.globalGain.gain.setValueAtTime(this.volume, this.audioCtx.currentTime);

        this.querySelector('.volume').addEventListener('input', _ => {
            this.globalGain.gain.setValueAtTime(this.volume, this.audioCtx.currentTime);
        });
    }

    get volume() {
        console.debug('Player#volume', this.querySelector('.volume').value)
        return Number(this.querySelector('.volume').value);
    }

    set volume(volume) {
        console.debug('Player#volume()', this, volume);
        this.querySelector('.volume').value = volume;
    }

    get isPlaying() {
        console.debug('Player#isPlaying', this, this.classList.contains('playing'));
        return this.classList.contains('playing');
    }

    // offsets is an array of offsets in cents from A4.
    get offsets() {
        return this.getOffsets();
    }

    set isPlaying(isPlaying) {
        console.debug('Player#isPlaying', this, isPlaying);
        if (isPlaying) {
            this.classList.add('playing');
            this.start();
        } else {
            this.classList.remove('playing');
            this.stop();
        }
    }

    update() {
        console.debug('Player#update', this.offsets, this.notes);
        for (let i = 0; i < this.offsets.length; i++) {
            if (!this.notes[i]) {
                console.debug('new note for', i)
                const note = new OscillatorNode(this.audioCtx, {
                    frequency: this.aFreq,
                    type: 'custom',
                    periodicWave: this.wave
                });
                note.connect(this.globalGain);
                note.start();
                this.notes[i] = note;
            }
            const note = this.notes[i];
            note.detune.setValueAtTime(this.offsets[i], this.audioCtx.currentTime);
        }
        for (let j = this.notes.length-1; j >= this.offsets.length; j--) {
            const note = this.notes[j];
            note.stop();
        }
        this.notes = this.notes.slice(0, this.offsets.length);
        console.debug(' -- done', this.notes);
    }

    start() {
        console.debug('Player#start', this.notes);
        this.update();
        this.audioCtx.resume();
    }

    stop() {
        console.debug('Player#stop', this);
        this.audioCtx.suspend();
    }
}