Skip to content

Instantly share code, notes, and snippets.

@mode-mercury
Created December 12, 2025 23:46
Show Gist options
  • Select an option

  • Save mode-mercury/07aa7c49593cfebac193f47f91b5fd56 to your computer and use it in GitHub Desktop.

Select an option

Save mode-mercury/07aa7c49593cfebac193f47f91b5fd56 to your computer and use it in GitHub Desktop.
Untitled
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>OSC+ Sensor Chaos DAW</title>
<style>
body {
background: #111;
color: #eee;
font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
padding: 1rem;
}
h1 {
font-size: 1.2rem;
margin-bottom: 0.5rem;
}
.row {
margin: 0.5rem 0;
}
label {
display: block;
margin-bottom: 0.25rem;
font-size: 0.85rem;
opacity: 0.9;
}
input, select, button {
background: #222;
color: #eee;
border: 1px solid #444;
padding: 0.3rem 0.5rem;
border-radius: 4px;
font-size: 0.85rem;
}
button {
cursor: pointer;
}
button:hover {
background: #333;
}
#status {
font-size: 0.8rem;
opacity: 0.85;
margin-top: 0.5rem;
}
#mapping {
font-size: 0.8rem;
margin-top: 0.5rem;
line-height: 1.35;
white-space: pre-line;
}
#cam-container {
position: relative;
width: 320px;
max-width: 100%;
margin-top: 1rem;
border: 1px solid #444;
border-radius: 4px;
overflow: hidden;
background: #000;
}
#cam {
width: 100%;
display: block;
transform: scaleX(-1); /* mirror selfie-style */
}
#hud {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
pointer-events: none;
color: #0ff;
font-size: 0.7rem;
text-shadow: 0 0 4px #000;
padding: 0.25rem;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: flex-start;
background: linear-gradient(to bottom, rgba(0,0,0,0.35), transparent 40%);
}
.hud-block {
margin-bottom: 0.2rem;
}
.hud-title {
font-weight: 600;
font-size: 0.7rem;
color: #8ff;
}
.hud-line {
font-family: monospace;
}
</style>
</head>
<body>
<h1>OSC+ Sensor Chaos DAW</h1>
<div class="row">
<button id="start">Start</button>
<button id="stop">Stop</button>
<button id="chaos">Chaos Map</button>
</div>
<div class="row">
<label>
Key (MIDI root: 60 = C4, 57 = A3, etc.)
<input id="root" type="number" value="60">
</label>
</div>
<div class="row">
<label>
Scale
<select id="scale">
<option value="pentatonic">Pentatonic</option>
<option value="major">Major</option>
<option value="minor">Minor</option>
</select>
</label>
</div>
<div class="row">
<label>
BPM
<input id="bpm" type="number" value="90">
</label>
</div>
<div id="status">Status: idle</div>
<div id="mapping"></div>
<div id="cam-container">
<video id="cam" autoplay muted playsinline></video>
<div id="hud">
<div class="hud-block">
<div class="hud-title">GPS</div>
<div id="hud-gps" class="hud-line">lat: --, lon: --</div>
</div>
<div class="hud-block">
<div class="hud-title">Accel</div>
<div id="hud-accel" class="hud-line">x: -- y: -- z: --</div>
<div id="hud-angles" class="hud-line">pitch: -- roll: --</div>
</div>
<div class="hud-block">
<div class="hud-title">Mods</div>
<div id="hud-mods" class="hud-line"></div>
</div>
</div>
</div>
<script>
// -------- helpers --------
function midiToFreq(m) {
return 440 * Math.pow(2, (m - 69) / 12);
}
function clamp01(x) {
return x < 0 ? 0 : x > 1 ? 1 : x;
}
const SCALES = {
major: [0, 2, 4, 5, 7, 9, 11],
minor: [0, 2, 3, 5, 7, 8, 10],
pentatonic: [0, 2, 5, 7, 9]
};
class OSCPlusEngine {
constructor() {
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
this.destination = this.audioContext.destination;
}
async ensureRunning() {
if (this.audioContext.state === "suspended") {
await this.audioContext.resume();
}
}
}
class DelayUnit {
constructor(engine, time, feedback, mix, maxTime) {
this.engine = engine;
const ctx = engine.audioContext;
this.input = ctx.createGain();
this.output = ctx.createGain();
this.delay = ctx.createDelay(maxTime || 2.0);
this.feedback = ctx.createGain();
this.wet = ctx.createGain();
this.dry = ctx.createGain();
this.input.connect(this.dry).connect(this.output);
this.input.connect(this.delay);
this.delay.connect(this.wet).connect(this.output);
this.delay.connect(this.feedback).connect(this.delay);
this.setTime(time || 0.3);
this.setFeedback(feedback || 0.3);
this.setMix(mix || 0.5);
}
connect(target) {
this.output.connect(target);
}
setTime(t) {
this.delay.delayTime.setValueAtTime(t, this.engine.audioContext.currentTime);
}
setFeedback(v) {
this.feedback.gain.setValueAtTime(v, this.engine.audioContext.currentTime);
}
setMix(m) {
m = clamp01(m);
const now = this.engine.audioContext.currentTime;
this.wet.gain.setValueAtTime(m, now);
this.dry.gain.setValueAtTime(1 - m, now);
}
}
// main chaos DAW
class ChaosMiniDAW {
constructor(engine, opts, videoEl, hudEls) {
this.engine = engine;
this.ctx = engine.audioContext;
this.rootMidi = (opts && opts.rootMidi) || 60;
this.scaleName = (opts && opts.scale) || "pentatonic";
this.bpm = (opts && opts.bpm) || 90;
// master chain: voices -> masterGain -> masterFilter -> panner -> dest
this.masterGain = this.ctx.createGain();
this.masterGain.gain.value = (opts && opts.masterGain) || 0.35;
this.masterFilter = this.ctx.createBiquadFilter();
this.masterFilter.type = "lowpass";
this.masterFilter.frequency.value = 8000;
this.masterFilter.Q.value = 0.2;
this.panner = this.ctx.createStereoPanner();
this.panner.pan.value = 0;
this.masterGain.connect(this.masterFilter).connect(this.panner).connect(this.engine.destination);
// FX send
this.delay = new DelayUnit(engine, 0.3, 0.35, 0.3);
this.delay.connect(this.masterGain);
this.isRunning = false;
this._timerId = null;
this._step = 0;
// sensors
this.lastGeo = null;
this.lastMotion = null;
this.lastAngles = { pitch: 0, roll: 0 };
this.hasCamera = false;
// RNG state
this._randSlow = Math.random();
this._randSlowTime = this.ctx.currentTime;
// mod routing
this.modSources = [
"timeLFO",
"screenLFO",
"motionMagLFO",
"angleLFO",
"latLFO",
"lonLFO",
"randFast",
"randSlow"
];
this.modTargets = [
"density",
"melodyColor",
"delayTime",
"delayFeedback",
"masterLevel",
"filterCutoff",
"filterRes",
"panPos",
"padIntensity"
];
this.modRouting = {};
this._randomizeMapping();
this.videoEl = videoEl;
this.hudEls = hudEls;
this._initSensors();
this._startHudLoop();
}
async _initSensors() {
// camera
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
this.hasCamera = true;
if (this.videoEl) {
this.videoEl.srcObject = stream;
}
} catch (e) {
this.hasCamera = false;
}
}
// GPS
if ("geolocation" in navigator) {
navigator.geolocation.watchPosition(
(pos) => { this.lastGeo = pos.coords; },
() => { this.lastGeo = null; },
{ enableHighAccuracy: true, maximumAge: 5000, timeout: 10000 }
);
}
// motion / angles
if (typeof window !== "undefined" && "DeviceMotionEvent" in window) {
try {
const handler = (e) => {
this.lastMotion = e.accelerationIncludingGravity;
const x = (this.lastMotion.x || 0);
const y = (this.lastMotion.y || 0);
const z = (this.lastMotion.z || 0);
// crude pitch/roll estimates in degrees
const pitch = Math.atan2(-x, Math.sqrt(y*y + z*z)) * 180 / Math.PI;
const roll = Math.atan2(y, z) * 180 / Math.PI;
this.lastAngles = { pitch, roll };
};
if (typeof DeviceMotionEvent.requestPermission === "function") {
DeviceMotionEvent.requestPermission().then((res) => {
if (res === "granted") {
window.addEventListener("devicemotion", handler);
}
});
} else {
window.addEventListener("devicemotion", handler);
}
} catch (e) {
// ignore
}
}
}
_describeMapping() {
const lines = [];
for (const target of this.modTargets) {
lines.push(`${target.padEnd(13)} ← ${this.modRouting[target]}`);
}
return lines.join("\n");
}
_randomizeMapping() {
const shuffled = this.modSources.slice();
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
this.modTargets.forEach((t, idx) => {
this.modRouting[t] = shuffled[idx % shuffled.length];
});
}
_sampleModSources() {
const t = this.ctx.currentTime;
// time LFO
const timeLFO = (Math.sin(t * 0.25) + 1) / 2;
// screen LFO
let screenFactor = 0;
if (typeof window !== "undefined") {
const w = window.innerWidth || 1;
const h = window.innerHeight || 1;
screenFactor = (((w % 997) / 997) + ((h % 991) / 991)) * 0.5;
}
const screenLFO = clamp01(0.5 * screenFactor + 0.5 * (Math.sin(t * 0.13 + screenFactor * 5) + 1) / 2);
// motion magnitude
let motionMag = 0;
if (this.lastMotion) {
const x = this.lastMotion.x || 0;
const y = this.lastMotion.y || 0;
const z = this.lastMotion.z || 0;
motionMag = Math.sqrt(x*x + y*y + z*z);
}
const motionMagLFO = clamp01(motionMag / 25);
// angle-based LFO
const { pitch, roll } = this.lastAngles;
const angleNorm = ((Math.abs(pitch) + Math.abs(roll)) / 180) / 2;
const angleLFO = clamp01(angleNorm);
// lat/lon as 0–1
let latLFO = 0.5, lonLFO = 0.5;
if (this.lastGeo) {
const lat = this.lastGeo.latitude;
const lon = this.lastGeo.longitude;
latLFO = ((lat % 90) / 90 + 1) / 2;
lonLFO = ((lon % 180) / 180 + 1) / 2;
}
// RNGs
const randFast = Math.random();
if (t - this._randSlowTime > 1.5) {
this._randSlow = Math.random();
this._randSlowTime = t;
}
const randSlow = this._randSlow;
return {
timeLFO,
screenLFO,
motionMagLFO,
angleLFO,
latLFO,
lonLFO,
randFast,
randSlow
};
}
_getScale() {
return SCALES[this.scaleName] || SCALES.pentatonic;
}
_pickNote(chaos, isBass, octaveOffset = 0) {
const scale = this._getScale();
const idx = Math.floor(chaos * scale.length) % scale.length;
const baseOct = isBass ? -1 : 0;
const octSpan = isBass ? 1 : 2;
const oct = baseOct + octaveOffset + Math.floor(chaos * octSpan);
const semitone = this.rootMidi + scale[idx] + oct * 12;
return midiToFreq(semitone);
}
_playVoice(freq, time, duration, type, level, panSkew = 0) {
const ctx = this.ctx;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
const pan = ctx.createStereoPanner();
osc.type = type || "sine";
osc.frequency.setValueAtTime(freq, time);
const attack = 0.01;
const decay = duration * 0.7;
const end = time + duration;
gain.gain.setValueAtTime(0, time);
gain.gain.linearRampToValueAtTime(level, time + attack);
gain.gain.linearRampToValueAtTime(0, time + attack + decay);
pan.pan.value = panSkew;
osc.connect(gain).connect(pan);
pan.connect(this.masterGain);
pan.connect(this.delay.input);
osc.start(time);
osc.stop(end + 0.1);
}
_playPad(freq, time, duration, color = 0.5) {
const ctx = this.ctx;
const osc1 = ctx.createOscillator();
const osc2 = ctx.createOscillator();
const gain = ctx.createGain();
// gentle detune
osc1.type = "sine";
osc2.type = "triangle";
osc1.frequency.setValueAtTime(freq, time);
osc2.frequency.setValueAtTime(freq * (1 + (color - 0.5) * 0.02), time);
const attack = 0.4;
const release = 0.8;
const end = time + duration;
gain.gain.setValueAtTime(0, time);
gain.gain.linearRampToValueAtTime(0.18, time + attack);
gain.gain.setValueAtTime(0.18, end);
gain.gain.linearRampToValueAtTime(0, end + release);
osc1.connect(gain);
osc2.connect(gain);
gain.connect(this.masterGain);
gain.connect(this.delay.input);
osc1.start(time);
osc2.start(time);
osc1.stop(end + release + 0.1);
osc2.stop(end + release + 0.1);
}
start() {
if (this.isRunning) return;
this.isRunning = true;
const beatMs = 60000 / this.bpm / 2;
this._timerId = setInterval(() => this._tick(), beatMs);
}
stop() {
this.isRunning = false;
if (this._timerId) {
clearInterval(this._timerId);
this._timerId = null;
}
}
_tick() {
if (!this.isRunning) return;
const mods = this._sampleModSources();
const now = this.ctx.currentTime + 0.05;
const getMod = (target, fallback) => {
const src = this.modRouting[target];
return (src && mods[src] !== undefined) ? mods[src] : fallback;
};
const densityMod = getMod("density", 0.5);
const colorMod = getMod("melodyColor", 0.5);
const dTimeMod = getMod("delayTime", 0.5);
const dFbMod = getMod("delayFeedback", 0.5);
const masterMod = getMod("masterLevel", 0.5);
const cutoffMod = getMod("filterCutoff", 0.5);
const resMod = getMod("filterRes", 0.3);
const panMod = getMod("panPos", 0.5);
const padIntensity = getMod("padIntensity", 0.4);
// keep it musical: density 0.35–0.85
const density = 0.35 + densityMod * 0.5;
// fixed rhythm grid
const pattern = [1, 0, 1, 1, 0, 1, 0, 1];
const active = pattern[this._step % pattern.length];
if (active && Math.random() < density) {
// bass
if (this._step % 4 === 0) {
const bassChaos = (mods.timeLFO + mods.motionMagLFO) / 2;
const bassFreq = this._pickNote(bassChaos, true);
this._playVoice(bassFreq, now, 0.4 + 0.25 * (1 - densityMod), "triangle", 0.22, (panMod - 0.5) * 0.3);
}
// melody
const chaos = (mods.screenLFO + mods.angleLFO + mods.randFast) / 3;
const melFreq = this._pickNote(chaos, false);
const waveTypes = ["sine", "triangle", "sawtooth", "square"];
const idx = Math.floor(colorMod * waveTypes.length) % waveTypes.length;
const type = waveTypes[idx];
const dur = 0.18 + 0.35 * (1 - densityMod);
this._playVoice(melFreq, now, dur, type, 0.16, (panMod - 0.5));
}
// pad layer: slower
if (this._step % 8 === 0 && Math.random() < padIntensity) {
const padChaos = (mods.latLFO + mods.lonLFO + mods.randSlow) / 3;
const padFreq = this._pickNote(padChaos, false, 1); // one octave up
this._playPad(padFreq, now, 2.5 + padIntensity * 2, colorMod);
}
// FX / master controls
const newTime = 0.18 + 0.32 * dTimeMod;
const newFb = 0.2 + 0.45 * dFbMod;
this.delay.setTime(newTime);
this.delay.setFeedback(newFb);
const masterLevel = 0.18 + 0.25 * masterMod;
this.masterGain.gain.setValueAtTime(masterLevel, this.ctx.currentTime);
const cutoffHz = 800 + cutoffMod * 7000;
const qVal = 0.2 + resMod * 4;
this.masterFilter.frequency.setValueAtTime(cutoffHz, this.ctx.currentTime);
this.masterFilter.Q.setValueAtTime(qVal, this.ctx.currentTime);
const pan = (panMod - 0.5) * 0.8;
this.panner.pan.setValueAtTime(pan, this.ctx.currentTime);
// occasional auto-chaos remap
if (Math.random() < 0.02) {
this._randomizeMapping();
}
this._lastMods = { ...mods };
this._step++;
}
setKey(rootMidi, scaleName) {
this.rootMidi = rootMidi;
if (scaleName) this.scaleName = scaleName;
}
setBpm(bpm) {
this.bpm = bpm;
if (this.isRunning) {
this.stop();
this.start();
}
}
chaosRemap() {
this._randomizeMapping();
}
_startHudLoop() {
const hudGPS = this.hudEls.gps;
const hudAccel = this.hudEls.accel;
const hudAng = this.hudEls.angles;
const hudMods = this.hudEls.mods;
const loop = () => {
// gps
if (this.lastGeo) {
const lat = this.lastGeo.latitude.toFixed(4);
const lon = this.lastGeo.longitude.toFixed(4);
hudGPS.textContent = `lat: ${lat}, lon: ${lon}`;
} else {
hudGPS.textContent = "lat: --, lon: --";
}
// accel
if (this.lastMotion) {
const x = (this.lastMotion.x || 0).toFixed(2);
const y = (this.lastMotion.y || 0).toFixed(2);
const z = (this.lastMotion.z || 0).toFixed(2);
hudAccel.textContent = `x:${x} y:${y} z:${z}`;
} else {
hudAccel.textContent = "x: -- y: -- z: --";
}
const { pitch, roll } = this.lastAngles;
hudAng.textContent = `pitch:${pitch.toFixed(1)}° roll:${roll.toFixed(1)}°`;
// mods
if (this._lastMods) {
const m = this._lastMods;
hudMods.textContent =
`time:${m.timeLFO.toFixed(2)} ` +
`scr:${m.screenLFO.toFixed(2)} ` +
`mot:${m.motionMagLFO.toFixed(2)} ` +
`ang:${m.angleLFO.toFixed(2)} ` +
`lat:${m.latLFO.toFixed(2)} ` +
`lon:${m.lonLFO.toFixed(2)} ` +
`rf:${m.randFast.toFixed(2)} ` +
`rs:${m.randSlow.toFixed(2)}`;
} else {
hudMods.textContent = "";
}
requestAnimationFrame(loop);
};
requestAnimationFrame(loop);
}
}
// ---- UI wiring ----
const engine = new OSCPlusEngine();
let daw = null;
const statusEl = document.getElementById("status");
const mappingEl = document.getElementById("mapping");
const videoEl = document.getElementById("cam");
const hudEls = {
gps: document.getElementById("hud-gps"),
accel: document.getElementById("hud-accel"),
angles: document.getElementById("hud-angles"),
mods: document.getElementById("hud-mods")
};
function updateMappingDisplay() {
if (!daw) return;
mappingEl.textContent = "Mod routing:\n" + daw._describeMapping();
}
document.getElementById("start").addEventListener("click", async () => {
await engine.ensureRunning();
if (!daw) {
daw = new ChaosMiniDAW(
engine,
{
rootMidi: parseInt(document.getElementById("root").value, 10) || 60,
scale: document.getElementById("scale").value,
bpm: parseInt(document.getElementById("bpm").value, 10) || 90
},
videoEl,
hudEls
);
}
daw.start();
statusEl.textContent = "Status: running (sensor-driven, in key, on grid)";
updateMappingDisplay();
});
document.getElementById("stop").addEventListener("click", () => {
if (daw) daw.stop();
statusEl.textContent = "Status: stopped";
});
document.getElementById("chaos").addEventListener("click", () => {
if (!daw) return;
daw.chaosRemap();
updateMappingDisplay();
statusEl.textContent = "Status: routing scrambled via RNG (still musical)";
});
document.getElementById("root").addEventListener("input", (e) => {
if (daw) {
const v = parseInt(e.target.value, 10) || 60;
daw.setKey(v, document.getElementById("scale").value);
}
});
document.getElementById("scale").addEventListener("change", (e) => {
if (daw) {
const root = parseInt(document.getElementById("root").value, 10) || 60;
daw.setKey(root, e.target.value);
}
});
document.getElementById("bpm").addEventListener("input", (e) => {
if (daw) {
const bpm = parseInt(e.target.value, 10) || 90;
daw.setBpm(bpm);
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment