summaryrefslogtreecommitdiffstats
path: root/player.mjs
blob: 02feb6c38920270ca729761fa36881045e17034e (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
105
106
107
108
109
110
111
import { Note, toCents } from './scale.mjs';
import GuitarFuzz from './vend/guitar-fuzz.mjs';
import Trombone from './vend/trombone.mjs';

const audioCtx = new AudioContext();
const fuzzWave = new PeriodicWave(audioCtx, {
    real: GuitarFuzz.real,
    imag: GuitarFuzz.imag
});
const tromboneWave = new PeriodicWave(audioCtx, {
    real: Trombone.real,
    imag: Trombone.imag
});

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.globalGain = audioCtx.createGain();
        this.globalGain.connect(audioCtx.destination);
        this.globalGain.gain.setValueAtTime(this.volume, audioCtx.currentTime);

        this.querySelector('.volume').onchange = e => {
            console.debug('vol changed', e, this.volume);
            this.globalGain.gain.setValueAtTime(this.volume, 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() {
        return this.classList.contains('playing');
    }

    // offsets is an array of offsets in cents from A4.
    get offsets() {
        console.debug('offsets getoffsets', this.getOffsets);
        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() {
        for (let i = 0; i < this.offsets.length; i++) {
            if (!this.notes[i]) {
                const note = new OscillatorNode(audioCtx, {
                    frequency: this.aFreq,
                    type: 'custom',
                    periodicWave: tromboneWave,
                });
                note.connect(this.globalGain);
                if (this.isPlaying) {
                    note.start();
                }
                this.notes[i] = note;
            }
            const note = this.notes[i];
            note.detune.setValueAtTime(this.offsets[i], audioCtx.currentTime);
        }
        for (let j = this.notes.length-1; j >= this.offsets.length; j--) {
            const note = this.notes[j];
            if (note && this.isPlaying) {
                note.stop();
            }
            delete this.notes[j];
        }
    }

    start() {
        console.debug('Player#start', this);
        this.notes.forEach(n => n.start());
    }

    stop() {
        console.debug('Player#stop', this);
        this.notes.forEach(n => n.stop());
    }
}