THX "Deep Note"

Web Audio API test

HTML

<div class="frame" style="display: none;">
    <p>The Audience is Listening</p>
</div>
<button>Play Audio</button>

CSS

body {
    background: black;
    margin: 0;
}

div.frame {
    position: relative;
    margin: 2vw;
    box-sizing: border-box;
    border: 1vw solid darkblue;
    width: calc(100vw - 4vw);
    height: calc(100vw * 9 / 16);
    background: black;
}

p {
    color: white;
    width: 100%;
    text-align: center;
    position: absolute;
    bottom: calc((100vw * 9 / 16) / 10);
    font-size: 5vw;
}

button {
    margin: 8px;
}

JavaScript

function rand(min, max) {
	return Math.random() * (max - min) + min
}

function mod(a, n) {
    return a - n * Math.floor(a / n)
}

function note(x) {
	const scale = [0, 2, 4, 5, 7, 9, 10] //D major
	return 440 * Math.pow(2, Math.floor(x / 7)) * Math.pow(2, scale[Math.floor(mod(x, 7))] / 12)
}

function playAudio() {
	const notes = [12, 10, 7, 3, 0, -4, -7, -11, -14, -18, -25]

    const ctx = new AudioContext()
    const gain = ctx.createGain()
    const gainCurve = Array(1000).fill().map((x, i) => (100 ** (i/1000) - 1) / (100 - 1) * 0.05)
    gain.gain.setValueCurveAtTime(gainCurve, ctx.currentTime, 15)
    gain.gain.setValueAtTime(0.05, ctx.currentTime+25)
    gain.gain.linearRampToValueAtTime(0.00, ctx.currentTime + 30)

    const pan = ctx.createStereoPanner()
    const panCurve = Array(1000).fill().map((x, i) => -0.7 * Math.cos(Math.PI * 1.5 * (i / 1000)))
    pan.pan.setValueAtTime(-0.7, ctx.currentTime)
    pan.pan.setValueCurveAtTime(panCurve, ctx.currentTime+1, 8)
    gain.connect(pan)

    const filter = ctx.createBiquadFilter()
    filter.type = "lowshelf"
    filter.frequency.setValueAtTime(200, ctx.currentTime)
    filter.gain.setValueAtTime(15, ctx.currentTime)
    pan.connect(filter)
    filter.connect(ctx.destination)

    notes.forEach(n => {
        for (let i = 0; i < 3; ++i) {
            const oc = ctx.createOscillator()
            oc.type = "sawtooth"
			const wobble = Array(50).fill().map(x => (Math.random() * 2 - 1) * 25)
            const wander = [rand(200, 400), rand(200, 400), rand(200, 400)]
            oc.detune.setValueCurveAtTime(wobble, ctx.currentTime+15, 15)
            oc.frequency.setValueCurveAtTime(wander, ctx.currentTime, 10)
            oc.frequency.linearRampToValueAtTime(note(n), ctx.currentTime + 15)

            const pan = ctx.createStereoPanner()
            pan.pan.value = (Math.random() * 2 - 1) * 1.0

            oc.connect(pan)
            pan.connect(gain)
            oc.start(ctx.currentTime)
           ...