webmidi+shake+svg实现音乐可视化绘制谱曲效果代码

代码语言:html

所属分类:多媒体

代码描述:webmidi+shake+svg实现音乐可视化绘制谱曲效果代码,点击上面的曲线拖动鼠标进行音乐轨道绘制,可以切换不同的频率和声音,最终点击播放按钮可以播放自己绘制的曲谱。

代码标签: webmidi shake svg 音乐 可视化 绘制 谱曲

下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <style>
        *{box-sizing:border-box}body{background:#222;margin:0;width:100vw;height:100vh;overflow:hidden}nav{background:#000;position:absolute;width:64px;bottom:0;top:0}nav svg{width:64px;height:64px}main svg{width:100%;height:100%}main{position:absolute;left:64px;top:0;cursor:crosshair;bottom:0;right:0}nav ul{z-index:2;margin:0;padding:0;width:100%;height:100%;display:flex;flex-wrap:wrap;align-items:center;align-content:center;justify-content:center;list-style:none}nav ul li{display:block}nav a.selected>svg,nav a.selected>svg{background:#333}svg.move{cursor:move}#bounds{stroke:mediumseagreen;stroke-width:1px}#cursor{stroke:white;stroke-width:1px}#grid{stroke:#333;stroke-width:1px;fill:none}.hidden{display:none}@media only screen and (orientation:landscape){nav{height:64px;width:100vw;bottom:auto}main{left:0;top:64px;right:0;bottom:0}}.overlay{position:absolute;top:0;left:0;right:0;bottom:0;display:none;background:rgba(0,0,0,0.5)}.overlay__tap2start{height:100vh;font-family:sans-serif;color:#fff;display:flex;justify-content:center;align-items:center;font-size:10vw}
    </style>
</head>

<body>

<svg id="symbols"><symbol id="square" viewBox="0 0 128 128"><path style="fill:none;stroke:#aad400;stroke-width:4px;stroke-linecap:square;" d="M35,43 l0,40 l30,0 l0,-40 l30,0 l0,40" /></symbol><symbol id="sawtooth" viewBox="0 0 128 128"><path style="fill:none;stroke:#c83737;stroke-width:4px;stroke-linecap:square;stroke-linejoin:bevel;" d="M30,80 l30,-40 l0,40 l30,-40 l0,40"/></symbol><symbol id="triangle" viewBox="0 0 128 128"><path style="fill:none;stroke:#2a7fff;stroke-width:4px;stroke-linecap:square;stroke-linejoin:bevel;" d="M30,80 l17,-40 l17,40 l17,-40 l17,40"/></symbol><symbol id="sine" viewBox="0 0 128 128"><path style="fill:none;stroke:#9944ff;stroke-width:4px;stroke-linecap:square;stroke-linejoin:bevel;" d="M30,64 q17,-40 32,0 q17,40 32,0"/></symbol><symbol id="play" viewBox="0 0 128 128"><path style="stroke:#aad400;fill:none;stroke-width:4px;stroke-linecap:square;stroke-linejoin:bevel" d="M33,30 l0,68 l68,-34z"/></symbol><symbol id="pause" viewBox="0 0 128 128"><rect style="stroke:#c83737;fill:none;stroke-width:4px;stroke-linecap:square;stroke-linejoin:bevel" x="30" y="30" width="30" height="68"/><rect style="stroke:#c83737;fill:none;stroke-width:4px;stroke-linecap:square;stroke-linejoin:bevel;" x="68" y="30" width="30" height="68"/></symbol><symbol id="record" viewBox="0 0 128 128"><circle style="fill:#c83737" cx="64" cy="64" r="32"/></symbol><symbol id="rewind" viewBox="0 0 128 128"><path style="stroke:#4466ff;fill:none;stroke-width:4px;stroke-linecap:square;stroke-linejoin:bevel;" d="M30,64 l34,-34 l0,34 l34,-34 l0,68 l-34,-34 l0,34Z" /></symbol><symbol id="clrscr" viewBox="0 0 128 128"><path style="stroke:#c83737;stroke-width:4px;stroke-linecap:square;fill:none;" d="M30,30 l68,68 M30,98 l68,-68"/></symbol><symbol id="move" viewBox="0 0 128 128"><path style="stroke:#4488ff;stroke-width:4px;stroke-linecap:square;stroke-linejoin:bevel;fill:none;" d="M30,30 l20,0 l-20,20 l0,-20 l68,68 l0,-20 l-20,20 l 20,0 M30,98 l0,-20 l20,20 l-20,0 l68,-68 l0,20 l-20,-20 l20,0"/></symbol><symbol id="cloud" viewBox="0 0 128 128"><path style="stroke:#44ccff;stroke-width:4px;stroke-linecap:square;stroke-linejoin:bevel;fill:none;" d="M30,64 q12,0 12,-8 q0,-16 16,-16 q16,0 16,16 q24,0 24,16 q0,16 -16,16  l-42,0 q-16,0 -16,-18 Z"/></symbol><symbol id="disk" viewBox="0 0 128,128"><path style="stroke:#44ccff;stroke-width:4px;stroke-linecap:square;stroke-linejoin:bevel;fill:none;" d="M30,30 l52,0 l16,16 l0,52 l-68,0 l 0,-68 M40,30 l0,24 l36,0 l0,-24 M48,30 l0,16 l6,0 l0,-16 M40,98 l0,-30 l48,0 l0,30"/></symbol><symbol id="open" viewBox="0 0 128,128"><path style="stroke:#ffcc33;stroke-width:4px;stroke-linecap:square;stroke-linejoin:bevel;fill:none;" d="M30,98 l15,-30 l60,0 l-15,30 l-60,0 l0,-45 l8,-8 l8,0 l8,8 l36,0 l0,15"/></symbol><symbol id="noise" viewBox="0 0 128 128"><path id="p" d="M30,92.5L32,59.5L34,42L36,32.5L38,35.5L40,41.5L42,84.5L44,60.5L46,52.5L48,77.5L50,88L52,53.5L54,79L56,57.5L58,59L60,37.5L62,45.5L64,88L66,53L68,72L70,90L72,91.5L74,95.5L76,51.5L78,95.5L80,50.5L82,44.5L84,93L86,52L88,61" style="fill:none;stroke:#fff;stroke-width:4px;"></path></symbol></svg>
    <nav>
        <ul>
            <li><a id="rewBtn" title="rewind" href="#"><svg><use xlink:href="#rewind"/></svg></a></li>
            <li><a id="playBtn" title="play" href="#"><svg><use xlink:href="#play"/></svg></a></li>
            <li class="hidden"><a id="pauseBtn" href="#"><svg><use xlink:href="#pause"/></svg></a></li>
            <li><a role="type" href="#sine" title="sine"><svg><use xlink:href="#sine"/></svg></a></li>
            <li class="hidden"><a role="type" href="#sawtooth" title="sawtooth"><svg><use xlink:href="#sawtooth"/></svg></a></li>
            <li class="hidden"><a role="type" href="#triangle" title="triangle"><svg><use xlink:href="#triangle"/></svg></a></li>
            <li class="hidden"><a role="type" href="#square" title="square"><svg><use xlink:href="#square"/></svg></a></li>
            <li class="hidden"><a role="type" href="#noise" title="noise"><svg><use xlink:href="#noise"/></svg></a></li>
            <li><a id="moveBtn" href="#move" title="move"><svg><use xlink:href="#move"/></svg></a></li>
            <li><a id="clrBtn" href="#clear" title="clear screen"><svg><use xlink:href="#clrscr"/></svg></a></li>
            <li><a id="dlBtn" href="#disk" title="save"><svg><use xlink:href="#disk"/></svg></a></li>
            <li><a id="ulBtn" href="#disk" title="open"><svg><use xlink:href="#open"/></svg><input id="inputFile" type="file" class="hidden" accept="image/svg+xml"/></a></li>
        </ul>
    </nav>
    <main><svg id="main"><path id="grid" /><path id="bounds"/><path id="cursor"/></svg></main>
    <div class="overlay">
        <div class="overlay__tap2start">tap to start </div>
    </div>
    <script type="text/javascript" src="//repo.bfw.wiki/bfwrepo/js/shake.js"></script>
    <script type="text/javascript" src="//repo.bfw.wiki/bfwrepo/js/webmidi.min.js"></script>
    <script>
        const isDebug = /debug/.test(window.location.href);
    const w3 = "http://www.w3.org/";
    const svgNS = w3 + "2000/svg";
    const xlinkNS = w3 + "1999/xlink";
    const notes = "C C# D D# E F F# G G# A A# B".split(" ");
    let w, h;
    let scrollX = 0,scrollY = 0,cursorX = 0;
    const borders = { l: 0, r: 250 };
    const borderExtend = 250;
    let moveMode = null;
    let playing = false;
    const d = document;
    const $ = document.querySelector.bind(d);
    const $$ = (sel, con) => Array.prototype.slice.call((con || d).querySelectorAll(sel));
    const { sqrt, min, max } = Math;
    const distance = (a, b) => sqrt((b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2);
    const freq = y => max(880 - y, 10);
    const freqToY = f => 880 - f;
    const svg = $`#main`;
    let masterVolume;
    let AC = new AudioContext();
    const defaultVolume = 0.5;
    const initAudioContext = () => {
      if (AC.state === "suspended") {
        $`.overlay`.style.display = 'block';
        $`.overlay`.addEventListener('click', () => {
          $`.overlay`.style.display = 'none';
          AC.resume();
        });
      }
      masterVolume = AC.createGain();
      masterVolume.gain.value = defaultVolume;
      masterVolume.connect(AC.destination);
    };
    initAudioContext();
    const music = [];
    let mouseNoise = null;
    let touchNoises = [];
    let midiNoises = {};
    let currentType = "sine";
    let clientRect = svg.getBoundingClientRect();
    
    Array.prototype.avg = function () {
      let r = 0,i = 0;
      for (i = 0; i < this.length; i++)
      r += this[i];
      return r / this.length;
    };
    
    const typeColors = {
      "sine": "#9944ff",
      "square": "#aad400",
      "sawtooth": "#c83737",
      "triangle": "#2a7fff",
      "noise": "#ffffff" };
    
    
    const noteToFreq = (note, octave) => {
      const n = typeof note === "string" ? notes.indexOf(note.replace(/_/, '')) : n;
      const f = 110 * 2 ** octave * 2 ** ((n + 3) / 12);
      return f;
    };
    
    const hexColor = c => {
      if (c.slice(0) === '#') return c;
      if (c.slice(0, 4) === 'rgb(') {
        return "#" + c.slice(4, -1).split(",").map(a => ((a | 0) >> 4).toString(16) + ((a | 0) % 16).toString(16)).join("");
      }
      return c;
    };
    
    const types = Object.keys(typeColors);
    
    const attribs = (el, attrs, x) => {
      for (x in attrs)
      if (attrs.hasOwnProperty(x) && attrs[x] !== undefined)
      el.setAttribute(x, attrs[x]);
    };
    
    const draw = (name, attrs) => {
      const el = document.createElementNS(svgNS, name);
      if (attrs) attribs(el, attrs);
      return el;
    };
    
    const scrollToCursor = () => {
      const middleX = (clientRect.right - clientRect.left) / 2;
      if (cursorX > scrollX + middleX || scrollX > cursorX) {
        scrollX = cursorX - middleX;
      }
    };
    
    const relPos = (x, y) => {
      // calculate from mouse/touch position 
      // to svg coordinate
      const relativeX = scrollX + x - clientRect.left;
      const relativeY = scrollY + y - clientRect.top;
      return { relX: relativeX, relY: relativeY };
    };
    
    const userIsJamming = () => {
      return !!mouseNoise || touchNoises.length > 0 || Object.keys(midiNoises).length > 0;
    };
    
    const setCursor = x => {
      if (userIsJamming() && x < cursorX) {
        return;
      }
      cursorX = x;
      scrollToCursor();
      setViewBox();
    };
    
    const SoundMachine = {
      // Helper factory that creates and reuses 
      // oscillators. Usage:
      // SoundMachine.noise(frequency, type)
      // returns a noise that starts playing
      // immediately. It does not create a
      // new oscillator each time. After a noise 
      // is muted, it can be reused, avoiding
      // memory issues.
    
      noises: [],
    
      whiteNoiseBuffer: null,
      createWhiteNoise: () => {
        if (!SoundMachine.whiteNoiseBuffer) {
          const len = AC.sampleRate * 2;
          const buf = AC.createBuffer(1, AC.sampleRate, len);
          const data = buf.getChannelData(0);
          for (let i = 0; i < len; i++) {
            data[i] = Math.random() * 2 - 1;
          }
          SoundMachine.whiteNoiseBuffer = buf;
        }
        const bufSrc = AC.createBufferSource();
        bufSrc.buffer = SoundMachine.whiteNoiseBuffer;
        bufSrc.loop = true;
        bufSrc.playbackRate.value = 1.0;
        return bufSrc;
      },
    
      makeSomeNoise: (freq, type) => {
        const noise = {
          osc: type === "noise" ?
          SoundMachine.createWhiteNoise() : AC.createOscillator(),
          env: AC.createGain() };
    
        const { osc, env } = noise;
        if (type !== "noise") {
          osc.type = type;
          osc.frequency.value = freq;
          osc.detune.value = 0;
        }
        env.gain.value = 1.0;
        osc.start();
        osc.connect(env);
        env.connect(masterVolume);
        SoundMachine.noises.push(noise);
        return noise;
      },
    
      noise: (freq, type) => {
        // reuse a noise we muted before
        const noise = SoundMachine.noises.find(n => {
          return n.env.gain.value === 0.0 &&
          n.osc.type === type;
        });
        if (!noise) {
          // create new noise
          return SoundMachine.makeSomeNoise(freq, type);
        }
        const { osc, env } = noise;
        osc.frequency.setValueAtTime(freq, 0);
        // chromium issue 645776
        // osc.frequency.value = freq
        env.gain.value = 1.0;
        return noise;
      } };
    
    
    
    class Noise {
      constructor(x, y, type) {
        this.element = draw("path", {
          "d": "",
          "style": `stroke:${typeColors[type]};stroke-width:4;fill:none;` });
    
        this.coords = [];
        this.type = type;
        if (x) {
          if (typeof x === "object") {
            this.add(x);
          } else {
            if (isFinite(x) && y && isFinite(y)) this.add(x, y);
          }
        }
        svg.appendChild(this.element);
        this.render();
      }
    
      add(x, y, quiet) {
        this.lastX = x;
        this.lastY = y;
        const { relX, relY } = typeof x === "object" ? x : relPos(x, y);
        if (this.coords.length > 0) {
          const lastCoord = this.coords[this.coords.length - 1];
          const firstCoord = this.coords[0];
          if (distance([relX, relY], lastCoord) < 5) {
            return;
          }
          if (firstCoord[0] < relX) {
            this.coords = this.coords.filter(c => c[0] < relX);
          } else {
            this.coords = this.coords.filter(c => c[0] > relX);
          }
        }
        this.coords.push([relX, relY]);
        if (relX > borders.r) borders.r += borderExtend;
        if (relX < borders.l) borders.l -= borderExtend;
        this.render();
        if (quiet) {
          return;
        }
        if (!this.noise) {
          this.noise = SoundMachine.noise(freq(relY), this.type);
        }
        if (this.type !== "noise") {
          this.noise.osc.frequency.value = freq(relY);
        } else {
          this.noise.osc.playbackRate.value = freq(relY) / 1e4;
        }
    
      }
    
      playAtX(x) {
        const { coords } = this;
        const l = coords.length;
        if (l < 2) {
          return;
        }
        const isMuted = coords[0][0] > x || coords[l - 1][0] < x;
        if (isMuted) {
          if (this.playerN.........完整代码请登录后点击上方下载按钮下载查看

网友评论0