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