<!DOCTYPE html>
<html>
<head>
<meta charset=“UTF-8”/>
<title>3D Music sequencer</title>
<style>
body {
background-color: #1C1C1C;
color: white;
}

h1 {
text-align: center;
font-family: Arial, sans-serif;
}

.container {
margin: 0 auto;
max-width: 800px;
padding: 20px;
}

#canvas {
width: 800px;
height: 600px;
margin: 0 auto;
display: block;
border: 1px solid white;
}

button {
background-color: #4CAF50;
border: none;
color: white;
padding: 12px 24px;
text-align: center;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
}

button:hover {
background-color: #3e8e41;
}

#tempo {
width: 50px;
}

.control-panel {
text-align: center;
}

input[type=“range”] {
margin: 0 10px;
width: 200px;
}
</style>
</head>
<body>
<div class=“container”>
<h1>3D Music sequencer</h1>
<canvas id=“canvas”></canvas>
<div class=“control-panel”>
<label for=“tempo”>Tempo:</label>
<input type=“number” id=“tempo” name=“tempo” value=“120”>
<button id=“play”>Play</button>
<button id=“stop”>Stop</button>
</div>
<div class=“control-panel”>
<span>Volume:</span>
<input type=“range” id=“volume” min=“0” max=“1” step=“0.1” value=“1”>
<span>Panning:</span>
<input type=“range” id=“panning” min=“-1” max=“1” step=“0.1” value=“0”>
</div>
</div>
<script src=“script.js”></script>
</body>
</html>

script.js:
// Define variables
const canvas = document.getElementById(“canvas”);
const ctx = canvas.getContext(“2d”);
const tempoInput = document.getElementById(“tempo”);
const playButton = document.getElementById(“play”);
const stopButton = document.getElementById(“stop”);
const volumeSlider = document.getElementById(“volume”);
const panningSlider = document.getElementById(“panning”);

// Create audio context and nodes
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const volumeNode = audioCtx.createGain();
const panningNode = audioCtx.createStereoPanner();

// Set initial values for nodes
volumeNode.gain.value = volumeSlider.value;
panningNode.pan.value = panningSlider.value;

// Connect nodes to audio context
volumeNode.connect(audioCtx.destination);
panningNode.connect(volumeNode);

// Define function to load audio file
function loadAudio(url) {
return fetch(url)
.then(response => response.arrayBuffer())
.then(buffer => audioCtx.decodeAudioData(buffer));
}

// Load audio files
const kickPromise = loadAudio(“kick.wav”);
const snarePromise = loadAudio(“snare.wav”);
const hiHatPromise = loadAudio(“hihat.wav”);

// Define function to play sounds
function playSound(audioBuffer, time, panningValue = 0) {
const sourceNode = audioCtx.createBufferSource();
sourceNode.buffer = audioBuffer;
sourceNode.connect(panningNode);
sourceNode.start(time);
}

// Define function to play a pattern
function playPattern(pattern) {
const kickTime = audioCtx.currentTime;

pattern.forEach((step, index) => {
if (step === “kick”) {
playSound(kickPromise, kickTime + index * 0.5);
} else if (step === “snare”) {
playSound(snarePromise, kickTime + index * 0.5);
} else if (step === “hihat”) {
playSound(hiHatPromise, kickTime + index * 0.5);
}
});
}

// Define function to generate a random pattern
function generateRandomPattern() {
const pattern = [];

for (let i = 0; i < 16; i++) {
const randomIndex = Math.floor(Math.random() * 3);
if (randomIndex === 0) {
pattern.push(“kick”);
} else if (randomIndex === 1) {
pattern.push(“snare”);
} else if (randomIndex === 2) {
pattern.push(“hihat”);
}
}

return pattern;
}

// Set initial pattern
let pattern = generateRandomPattern();

// Define function to draw 3D bars
function drawBars() {
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);

// Draw bars
const barWidth = 40;
const barHeight = 200;
const barSpacing = 20;

pattern.forEach((step, index) => {
let x = (index * (barWidth + barSpacing)) + barSpacing;
let y = canvas.height / 2;
let z = 0;
let width = barWidth;
let height = barHeight;

if (step === “snare”) {
z = -50;
} else if (step === “hihat”) {
z = 50;
}

// Project 3D coordinates to 2D canvas
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const cameraX = 0;
const cameraY = centerY;
const cameraZ = 1000;

const scaleX = cameraZ / (cameraZ - z);
const scaleY = cameraZ / (cameraZ - y - cameraY);
const scaleZ = cameraZ / (cameraZ - x - cameraX);

const projectedX = centerX + (x - cameraX) * scaleX;
const projectedY = centerY + (y - cameraY) * scaleY;
const projectedWidth = width * scaleX;
const projectedHeight = height * scaleY;

// Draw bar
ctx.fillStyle = “white”;
ctx.fillRect(projectedX, projectedY, projectedWidth, projectedHeight);
});
}

// Draw bars initially
drawBars();

// Define function to update pattern and draw bars
function updatePattern() {
pattern = generateRandomPattern();
drawBars();
}

// Define function to play pattern with current tempo
function playCurrentPattern() {
const tempo = parseInt(tempoInput.value);
const quarterNoteTime = 60 / tempo;
const startTime = audioCtx.currentTime;

for (let i = 0; i < 4; i++) {
playPattern(pattern);
}

setTimeout(playCurrentPattern, quarterNoteTime * 4 * 1000);
}

// Event listener for play button
playButton.addEventListener(“click”, () => {
playCurrentPattern();
});

// Event listener for stop button
stopButton.addEventListener(“click”, () => {
location.reload();
});

// Event listener for volume slider
volumeSlider.addEventListener(“input”, () => {
volumeNode.gain.value = volumeSlider.value;
});

// Event listener for panning slider
panningSlider.addEventListener(“input”, () => {
panningNode.pan.value = panningSlider.value;
});

// Event listener for tempo input
tempoInput.addEventListener(“input”, () => {
const tempo = parseInt(tempoInput.value);
const quarterNoteTime = 60 / tempo;
const duration = 4 * quarterNoteTime;
audioCtx.resume().then(() => {
playSound(kickPromise, audioCtx.currentTime, -1);
setTimeout(() => {
playSound(snarePromise, audioCtx.currentTime, 1);
}, duration * 1000);
setTimeout(() => {
playSound(hiHatPromise, audioCtx.currentTime);
}, duration * 2 * 1000);
});
});

// Event listener for canvas click
canvas.addEventListener(“click”, () => {
updatePattern();
});