Skip to content

Instantly share code, notes, and snippets.

@TomWhitwell
Last active May 6, 2025 15:42
Show Gist options
  • Save TomWhitwell/48cefdd751ad5afcd8fac66f6b6762af to your computer and use it in GitHub Desktop.
Save TomWhitwell/48cefdd751ad5afcd8fac66f6b6762af to your computer and use it in GitHub Desktop.
GPT Visualisation MIDI code
<!--
Prompt used to generate this code:
Create an HTML file that shows rhythmic pulsing retro wireframe visuals that can be controlled using 8 MIDI CCs 35–41. Return the full code.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Retro Wireframe MIDI Visuals</title>
<style>
body { margin: 0; overflow: hidden; background: black; }
canvas { display: block; }
</style>
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
<script>
let scene, camera, renderer, grid;
let clock = new THREE.Clock();
let params = {
pulseSpeed: 1.0, // CC 35
gridSpacing: 10, // CC 36
gridSize: 500, // CC 37
colorHue: 0.6, // CC 38
waveAmount: 0.5, // CC 39
waveSpeed: 1.0, // CC 40
lineThickness: 1.0 // CC 41 (not literal, affects line scale)
};
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 50, 100);
camera.lookAt(0, 0, 0);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
createGrid();
animate();
}
function createGrid() {
if (grid) {
scene.remove(grid);
}
const geometry = new THREE.PlaneGeometry(params.gridSize, params.gridSize, params.gridSpacing, params.gridSpacing);
const material = new THREE.MeshBasicMaterial({
color: new THREE.Color().setHSL(params.colorHue, 1, 0.5),
wireframe: true
});
grid = new THREE.Mesh(geometry, material);
grid.rotation.x = -Math.PI / 2;
scene.add(grid);
}
function animate() {
requestAnimationFrame(animate);
const t = clock.getElapsedTime();
if (grid.geometry && grid.geometry.attributes.position) {
const pos = grid.geometry.attributes.position;
for (let i = 0; i < pos.count; i++) {
let x = pos.getX(i);
let y = pos.getY(i);
let z = Math.sin((x + t * params.waveSpeed * 5) * 0.1) * params.waveAmount * 10 +
Math.cos((y + t * params.waveSpeed * 5) * 0.1) * params.waveAmount * 10;
pos.setZ(i, z);
}
pos.needsUpdate = true;
}
grid.material.color.setHSL(params.colorHue, 1, 0.5);
grid.scale.setScalar(1 + Math.sin(t * params.pulseSpeed) * 0.1 * params.lineThickness);
renderer.render(scene, camera);
}
// MIDI setup
function onMIDIMessage(message) {
const [status, cc, value] = message.data;
if (status === 0xB0) { // MIDI CC message
const normalized = value / 127;
switch (cc) {
case 35: params.pulseSpeed = 0.1 + normalized * 5; break;
case 36: params.gridSpacing = Math.round(5 + normalized * 50); createGrid(); break;
case 37: params.gridSize = Math.round(100 + normalized * 1000); createGrid(); break;
case 38: params.colorHue = normalized; break;
case 39: params.waveAmount = normalized * 2; break;
case 40: params.waveSpeed = 0.1 + normalized * 4; break;
case 41: params.lineThickness = 0.5 + normalized * 30; break;
}
}
}
function onMIDISuccess(midiAccess) {
for (let input of midiAccess.inputs.values()) {
input.onmidimessage = onMIDIMessage;
}
}
function onMIDIFailure() {
console.warn("Could not access your MIDI devices.");
}
if (navigator.requestMIDIAccess) {
navigator.requestMIDIAccess().then(onMIDISuccess, onMIDIFailure);
} else {
alert("Web MIDI is not supported in this browser.");
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
init();
</script>
</body>
</html>
@jhw
Copy link

jhw commented Apr 11, 2025

Amazing 🤩

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment