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