Last active
August 22, 2023 05:12
-
-
Save ckmahoney/82987ad7dbdfa9b4329f6894ac1be336 to your computer and use it in GitHub Desktop.
Infinite music player with Web Audio API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// copy and paste me into your browser console to play music! | |
function createReverbNode(context, decayTime = 3, wetLevel = 0.33) { | |
const reverbNode = context.createConvolver(); | |
// Create an impulse response buffer for the reverb | |
const bufferLength = context.sampleRate * decayTime; | |
const impulseResponse = context.createBuffer(2, bufferLength, context.sampleRate); | |
// Fill the buffer with random noise for a simple reverb effect | |
for (let channel = 0; channel < impulseResponse.numberOfChannels; channel++) { | |
const channelData = impulseResponse.getChannelData(channel); | |
for (let i = 0; i < bufferLength; i++) { | |
channelData[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / bufferLength, 2); | |
} | |
} | |
reverbNode.buffer = impulseResponse; | |
const wetGain = context.createGain(); | |
const dryGain = context.createGain(); | |
wetGain.gain.value = wetLevel; | |
dryGain.gain.value = 1 - wetLevel; | |
reverbNode.connect(wetGain); | |
reverbNode.connect(dryGain); | |
return { | |
connect(x) { | |
reverbNode.connect(x) | |
}, | |
read(x) { | |
x.connect(reverbNode) | |
} | |
}; | |
} | |
function createMix() { | |
let ctx = new AudioContext() | |
let mixer = new AnalyserNode(ctx, { | |
fftSize: 2048, | |
maxDecibels: -25, | |
minDecibels: -60, | |
smoothingTimeConstant: 0.5, | |
}); | |
let decay = 1 + 5 * Math.random() | |
let wet = Math.random()/3 | |
const reverb = createReverbNode(ctx, decay, wet); | |
let xs = ["sine", "sawtooth", "square", "triangle"] | |
let oscs = ([5,2,3,4]).map(n => { | |
let o = new OscillatorNode(ctx) | |
let g = new GainNode(ctx) | |
o.type = xs[(n + (new Date()).getTime()) % (xs.length - 1)] | |
o.gain = g.gain | |
o.connect(g) | |
o.frequency.value = n * 100 | |
g.connect(mixer) | |
return o | |
}) | |
let volume = new GainNode(ctx) | |
volume.connect(ctx.destination) | |
reverb.connect(ctx.destination) | |
reverb.read(mixer) | |
mixer.connect(volume) | |
ts = [] | |
const r = 1 + Math.random() | |
let v | |
return { | |
ctx, | |
knob(type, value) { | |
switch (type) { | |
case "volume": | |
volume.gain.value = value | |
break; | |
case "reverb": | |
reverb.gain.value = value | |
break; | |
} | |
}, | |
start: function start(scale, vol = 0.1) { | |
v = vol | |
oscs.forEach((o, i) => { | |
let n = i + 1 | |
o.start() | |
const rate = Math.random() * 2000 * scale | |
let sustain = 0.05 + (0.8 * Math.random()) | |
o.gain.value = vol | |
let dur = rate * sustain | |
function autoLoop() { | |
let time = new Date() | |
let ampMod = (Math.abs(Math.sin(n * time))/2) + Math.random()/4 | |
o.gain.value = v * ampMod | |
o.frequency.value = r * 30 * n * (1 + (time % 7)) | |
ts.push(setTimeout($ => o.gain.value = 0, ctx.currentTime + dur)) | |
ts.push(setTimeout(autoLoop, rate)) | |
} | |
autoLoop() | |
}) | |
}, | |
play(x = 0.1) { | |
v = x | |
oscs.forEach(o => o.gain.value = v) | |
}, | |
kill() { | |
ts.forEach(id => { | |
try { clearTimeout(id) } catch (e) { /* noop */ } | |
}) | |
oscs.forEach(o => o.stop()) | |
}, | |
hush() { | |
oscs.forEach(o => o.gain.value = 0) | |
} | |
} | |
} | |
// create a mini infiplayer | |
(function go(scale = 1, sampleRate = 12000) { | |
io = createMix() | |
io.start(scale) | |
scale = Math.sin((new Date()).getTime()/100000) * 5 | |
scale = Math.abs(scale) | |
setTimeout($ => { io.kill(); go(scale, 1000 + Math.random() * 10000 * scale) }, sampleRate) | |
})() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment