Skip to content

Instantly share code, notes, and snippets.

@HelgeSverre
Created October 3, 2024 19:50
Show Gist options
  • Save HelgeSverre/2d65a61ceee9d2fbf5c3ae0d3d0f612d to your computer and use it in GitHub Desktop.
Save HelgeSverre/2d65a61ceee9d2fbf5c3ae0d3d0f612d to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DnB Beat Generator Visualization</title>
<style>
body, html {
margin: 0;
overflow: hidden;
background-color: black;
}
canvas {
position: absolute;
top: 0;
left: 0;
}
button {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
padding: 10px 20px;
font-size: 16px;
background-color: #0074D9;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
button.playing {
background-color: #ff4136;
}
</style>
</head>
<body>
<canvas id="visualizer"></canvas>
<button id="toggleAudio">Start Audio</button>
<script>
class DnBBeatGenerator {
constructor() {
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
this.bpm = 174;
this.sixteenthNote = (60 / this.bpm) / 4;
this.barLength = this.sixteenthNote * 16;
this.currentBar = 0;
this.masterCompressor = this.audioContext.createDynamicsCompressor();
this.masterCompressor.connect(this.audioContext.destination);
this.kick = this.createKick();
this.snare = this.createSnare();
this.hihat = this.createHihat();
this.bassline = this.createBassline();
this.reese = this.createReese();
// Start oscillators
this.kick.oscillator.start();
this.bassline.oscillator.start();
this.reese.oscillator.start();
}
createKick() {
const kick = this.audioContext.createOscillator();
const kickEnv = this.audioContext.createGain();
kick.frequency.value = 50;
kick.connect(kickEnv);
kickEnv.connect(this.masterCompressor);
kickEnv.gain.value = 0;
return { oscillator: kick, envelope: kickEnv };
}
createSnare() {
const snareEnv = this.audioContext.createGain();
snareEnv.connect(this.masterCompressor);
return { envelope: snareEnv };
}
createHihat() {
const hihatEnv = this.audioContext.createGain();
hihatEnv.connect(this.masterCompressor);
return { envelope: hihatEnv };
}
createBassline() {
const bass = this.audioContext.createOscillator();
const bassEnv = this.audioContext.createGain();
const bassFilter = this.audioContext.createBiquadFilter();
bass.type = 'sawtooth';
bass.connect(bassFilter);
bassFilter.connect(bassEnv);
bassEnv.connect(this.masterCompressor);
bassFilter.type = 'lowpass';
bassFilter.frequency.value = 100;
bassEnv.gain.value = 0;
return { oscillator: bass, envelope: bassEnv, filter: bassFilter };
}
createReese() {
const reese = this.audioContext.createOscillator();
const reeseEnv = this.audioContext.createGain();
const reeseFilter = this.audioContext.createBiquadFilter();
reese.type = 'sawtooth';
reese.connect(reeseFilter);
reeseFilter.connect(reeseEnv);
reeseEnv.connect(this.masterCompressor);
reeseFilter.type = 'lowpass';
reeseFilter.frequency.value = 500;
reeseEnv.gain.value = 0;
return { oscillator: reese, envelope: reeseEnv, filter: reeseFilter };
}
scheduleKick(time) {
this.kick.oscillator.frequency.setValueAtTime(50, time);
this.kick.oscillator.frequency.exponentialRampToValueAtTime(0.01, time + 0.1);
this.kick.envelope.gain.setValueAtTime(1, time);
this.kick.envelope.gain.exponentialRampToValueAtTime(0.01, time + 0.2);
}
scheduleSnare(time) {
const bufferSize = this.audioContext.sampleRate * 0.1;
const buffer = this.audioContext.createBuffer(1, bufferSize, this.audioContext.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = (Math.random() * 2 - 1) * Math.pow((1 - i / bufferSize), 2);
}
const snare = this.audioContext.createBufferSource();
snare.buffer = buffer;
snare.connect(this.snare.envelope);
snare.start(time);
this.snare.envelope.gain.setValueAtTime(0.7, time);
this.snare.envelope.gain.exponentialRampToValueAtTime(0.01, time + 0.2);
}
scheduleHihat(time, isOpen) {
const bufferSize = this.audioContext.sampleRate * (isOpen ? 0.1 : 0.05);
const buffer = this.audioContext.createBuffer(1, bufferSize, this.audioContext.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = (Math.random() * 2 - 1) * Math.pow((1 - i / bufferSize), 4);
}
const hihat = this.audioContext.createBufferSource();
hihat.buffer = buffer;
hihat.connect(this.hihat.envelope);
hihat.start(time);
this.hihat.envelope.gain.setValueAtTime(isOpen ? 0.3 : 0.2, time);
this.hihat.envelope.gain.exponentialRampToValueAtTime(0.01, time + (isOpen ? 0.1 : 0.05));
}
scheduleReese(time, note) {
const freq = 55 * Math.pow(2, note / 12);
this.reese.oscillator.frequency.setValueAtTime(freq, time);
this.reese.envelope.gain.setValueAtTime(0.3, time);
this.reese.envelope.gain.exponentialRampToValueAtTime(0.01, time + 0.3);
this.reese.filter.frequency.setValueAtTime(500, time);
this.reese.filter.frequency.exponentialRampToValueAtTime(100, time + 0.2);
}
scheduleBeat(startTime) {
const bassPattern = [0, 0, 7, 7, 3, 3, 10, 10];
const reesePattern = [0, 3, 7, 10];
for (let i = 0; i < 16; i++) {
const time = startTime + i * this.sixteenthNote;
// Kick on every quarter note
if (i % 4 === 0) {
this.scheduleKick(time);
}
// Snare on 2 and 4
if (i === 4 || i === 12) {
this.scheduleSnare(time);
}
// Hi-hats
this.scheduleHihat(time, i % 4 === 2);
// Bassline
if (i % 2 === 0) {
const bassNote = bassPattern[i / 2 % bassPattern.length];
this.scheduleBassline(time, bassNote);
}
// Reese bass (every 8th note on odd bars)
if (this.currentBar % 2 === 1 && i % 2 === 0) {
const reeseNote = reesePattern[i / 2 % reesePattern.length];
this.scheduleReese(time, reeseNote);
}
}
this.currentBar++;
}
start() {
const scheduleAhead = 0.1;
let nextNoteTime = this.audioContext.currentTime;
const scheduler = () => {
while (nextNoteTime < this.audioContext.currentTime + scheduleAhead) {
this.scheduleBeat(nextNoteTime);
nextNoteTime += this.barLength;
}
requestAnimationFrame(scheduler);
};
scheduler();
}
}
// Visualization and UI Logic
const canvas = document.getElementById('visualizer');
const ctx = canvas.getContext('2d');
let isPlaying = false;
let beatGenerator = null;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const maxRadius = Math.min(canvas.width, canvas.height) * 0.4;
function drawTunnel(time) {
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const zoomFactor = Math.sin(time * 0.001) * 0.5 + 1.5;
const skewX = Math.sin(time * 0.0007) * 0.3;
const skewY = Math.cos(time * 0.0005) * 0.3;
for (let radius = maxRadius; radius > 0; radius -= 5) {
ctx.save();
ctx.translate(centerX, centerY);
ctx.scale(zoomFactor, zoomFactor);
ctx.transform(1, skewY, skewX, 1, 0, 0);
ctx.beginPath();
for (let angle = 0; angle < Math.PI * 2; angle += 0.1) {
const noiseFactor = Math.random() * 50;
const pulseFactor = Math.sin(time * 0.01 + radius * 0.05) * 20;
const x = Math.cos(angle) * (radius + noiseFactor + pulseFactor);
const y = Math.sin(angle) * (radius + noiseFactor + pulseFactor);
if (angle === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.closePath();
const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, maxRadius);
const hue = (time * 0.05 + radius) % 360;
gradient.addColorStop(0, `hsla(${hue}, 100%, 50%, 0.7)`);
gradient.addColorStop(1, `hsla(${(hue + 180) % 360}, 100%, 10%, 0.1)`);
ctx.strokeStyle = gradient;
ctx.lineWidth = 2;
ctx.stroke();
ctx.restore();
}
if (Math.random() < 0.05) {
ctx.fillStyle = `rgba(255, 255, 255, ${Math.random() * 0.2})`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
}
function render(time) {
drawTunnel(time);
requestAnimationFrame(render);
}
render(0);
document.getElementById('toggleAudio').addEventListener('click', () => {
if (isPlaying) {
// Stop audio
isPlaying = false;
beatGenerator.audioContext.close();
beatGenerator = null;
document.getElementById('toggleAudio').classList.remove('playing');
document.getElementById('toggleAudio').textContent = 'Start Audio';
} else {
// Start audio
isPlaying = true;
beatGenerator = new DnBBeatGenerator();
beatGenerator.start();
document.getElementById('toggleAudio').classList.add('playing');
document.getElementById('toggleAudio').textContent = 'Stop Audio';
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment