canvas+webgl实现三维shader驱动的音乐立体空间效果代码
代码语言:html
所属分类:三维
代码描述:canvas+webgl实现三维shader驱动的音乐立体空间效果代码,可拖动旋转切换视角,墙上流体动画。
代码标签: canvas webgl 三维 shader 驱动 音乐 立体 空间
下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开
<!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8"> </head> <body> <script > const choose = a => a[Math.floor(Math.random() * a.length)]; const speeds = [.8]; const resont = [.9]; const volmns = [1]; const options = { volume: choose(volmns), resonance: choose(resont), speed: choose(speeds) }; let shaderTime = 0; let pan = 0; const Composer = () => { let context, main, verb, bus, compressor; const trigger = Object.freeze({ trig: function () { let prev = 0; let t = false; return function (val) { t = prev < 0 && val >= 0; prev = val; return t; }; }, change: function () { let prev = 0; let t = false; return function (val) { t = prev != val; prev = val; return t; }; }, pulse: function (threshold) { let prev = 0; let t = false; return function (b) { if (b) prev++; if (prev == threshold) { t = true; prev = 0; } else {t = false;} return t; }; }, skip: function (n) { let count = n; return function (b) { return count++ % n == 0; }; } }); const lowTrig = trigger.trig(); const highTrig = trigger.trig(); const bassTrig = trigger.trig(); const lowChanged = trigger.change(); const bassChanged = trigger.change(); const highChanged = trigger.change(); const roots = [21, 23, 24, 26, 28, 30]; let b = Float32Array.from({ length: 24 }, () => Math.random() < .25 ? Math.random() * 2 - 1 : 0); let m = [0, 4, 0, 9, 0, 4, 9]; let root = pickItem(b[1], 0, 20, roots); function loop() { main.gain.value = dbamp(lerp(options.volume, 0, 1, -60, 3)); compose(); setTimeout(() => loop(), expInterp(options.speed, 0, 1, 800, 80)); } function compose() { options.resonance = .2 + .8 * (1. - Math.abs(pan)); for (let i = 0; i < b.length; i++) { b[i] = -Math.min(b[mod(i + 3, b.length)], Math.random() * 2 - 1); } if (lowTrig(b[21])) { const note = pickNote(b[21], 15, 35); if (lowChanged(note)) { sine( midicps(note), pickVolume(1 - Math.abs(pan), -24, -9), pan); } } { const note = pickNote(b[11], 20, 25); if (highChanged(note)) { tri( midicps(note), pickVolume(b[12], -24, -9), b[13]); } sine( midicps(pickNote(b[11], 15, 35)), pickVolume(1 - Math.abs(pan), -24, -9), pan); } if (bassTrig(b[11])) { const note = pickNote(b[11], 5, 15); if (bassChanged(note)) { sine( midicps(note), pickVolume(1 - Math.abs(pan), -24, -9), pan); } } } function pickItem(v, a, b, array) { return array[round(lerp(v, -1, 1, a, b)) % (array.length - 1)]; } function pickNote(v, a, b) { return deg2key(lerp(v, -1, 1, a, b), m) + root; } function pickVolume(v, a, b) { return dbamp(lerp(v, -1, 1, a, b)); } function mod(n, m) {return (n % m + m) % m;} function div(a, b) {return a / b >> 0;} function deg2key(degree, mode) { const size = mode.length; const deg = round(degree); return 12 * div(deg, size) + mode[mod(deg, size)]; } function fractInterp(a, b, fraction) { return a + fraction * (b - a); } function expInterp(x, a, b, c, d) { if (x <= a) { return c; } if (x >= b) { return d; } return Math.pow(d / c, (x - a) / (b - a)) * c; } function lerp(x, a, b, c, d) { if (x <= a) { return c; } if (x >= b) { return d; } return (x - a) * (d - c) / (b - a) + c; } function round(a) {return a + (a > 0 ? 0.5 : -0.5) << 0;} function midicps(a) {return 440. * Math.pow(2.0, (a - 69.0) * 0.083333333333);} function dbamp(a) {return Math.pow(10, a * 0.05);} function createReverb(context) { const length = 6 * context.sampleRate; const decay = .8; const buffer = context.createBuffer(2, length, context.sampleRate); const noiseData = [buffer.getChannelData(0), buffer.getChannelData(1)]; const noise = Float32Array.from({ length }, () => Math.random() * 2 - 1); for (let i = 0; i < length; i++) { const k = noise[i]; noiseData[0][i] = k * Math.pow(1 - i / length, decay); noiseData[1][i] = k * Math.pow(1 - i / length, decay); } const convolver = context.createConvolver(); convolver.buffer = buffer; return convolver; } function init() { window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.oAudioContext; context = new AudioContext({ latencyHint: 'balanced' }); verb = createReverb(context); compressor = context.createDynamicsCompressor(); main = context.createGain(); bus = context.createGain(); bus.connect(verb); verb.connect(main); main.connect(compressor); compressor.ratio.value = 12; // 12 compressor.release.value = .25; // 0.25 compressor.knee.value = 0; compressor.threshold.value = -25; // -24 compressor.connect(context.destination); console.table(options); requestAnimationFrame(loop); } function instrument(freq, amp, pan, type, att, rel, detune) { const HALF_PI = Math.PI * 0.5; const vco = context.createOscillator(); const vca = context.createGain(); const out = context.createGain(); const panner = context.createStereoPanner(); panner.pan.value = pan; vco.detune.value = detune; vco.type = type; vco.frequency.value = freq; // xfade resonance out.gain.value = Math.cos(options.resonance * HALF_PI); bus.gain.value = Math.cos((1.0 - options.resonance) * HALF_PI); vco.connect(vca); vca.connect(panner); panner.connect(out); out.connect(main); panner.connect(bus); // env const now = context.currentTime; const attack = now + att; const release = attack + rel; vca.gain.value = 0; vca.gain.setValueAtTime(vca.gain.value, now); vca.gain.linearRampToValueAtTime(amp, attack); vca.gain.exponentialRampToValueAtTime(.001, release); vco.start(now); // gc vco.stop(release + .1); setTimeout(() => { vco.disconnect(); }, 1000 * (release + .2)); } function sine(freq, amp, pan) { instrument(freq, amp, pan, 'sine', .01, .5, 0); } function tri(freq, amp, pan) { instrument(freq, amp, pan, 'triangle', .01, .5, 0); } function saw(freq, amp, pan) { instrument(freq, amp, pan, 'sawtooth', .025, .5, 5); } function sqr(freq, amp, pan) { instrument(freq, amp, pan, 'square', .01, .5, 5); } let bkpVol = options.volume; function toggle() { if (options.volume > 0) { bkpVol = options.volume; options.volume = 0; } else { options.volume = bkpVol; } } play.addEventListener("click", () => { if (!context) init();else toggle(); }); }; const canvas = document.createElement("canvas"); const gl = canvas.getContext("webgl2"); document.title = "🎄"; document.body.innerHTML = ""; document.body.appendChild(canvas); document.body.style = "margin:0;touch-action:none;overflow:hidden"; canvas.style.width = "100%"; canvas.style.height = "auto"; canvas.style.userSelect = "none"; const style = document.createElement("style"); style.innerHTML = ` input { all: unset; position: fixed; top: 1rem; right: 1rem; width: 3rem; height: 2rem; opacity: .2; filter: saturate(0) inv.........完整代码请登录后点击上方下载按钮下载查看
网友评论0