UNPKG

wam-community

Version:

A collection of prebuilt Web Audio Modules ready for use

1,507 lines (1,494 loc) 55 kB
/* * * * WebAudio-Controls is based on * webaudio-knob by Eiji Kitamura http://google.com/+agektmr * webaudio-slider by RYoya Kawai https://plus.google.com/108242669191458983485/posts * webaudio-switch by Keisuke Ai http://d.hatena.ne.jp/aike/ * Integrated and enhanced by g200kg http://www.g200kg.com/ * * Copyright 2013 Eiji Kitamura / Ryoya KAWAI / Keisuke Ai / g200kg(Tatsuya Shinyagaito) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * */ if (window.customElements) { let styles = document.createElement("style"); styles.innerHTML = `#webaudioctrl-context-menu { display: none; position: absolute; z-index: 10; padding: 0; width: 100px; color:#eee; background-color: #268; border: solid 1px #888; box-shadow: 1px 1px 2px #888; font-family: sans-serif; font-size: 11px; line-height:1.7em; text-align:center; cursor:pointer; color:#fff; list-style: none; } #webaudioctrl-context-menu.active { display: block; } .webaudioctrl-context-menu__item { display: block; margin: 0; padding: 0; color: #000; background-color:#eee; text-decoration: none; } .webaudioctrl-context-menu__title{ font-weight:bold; } .webaudioctrl-context-menu__item:last-child { margin-bottom: 0; } .webaudioctrl-context-menu__item:hover { background-color: #b8b8b8; } `; document.head.appendChild(styles); let midimenu = document.createElement("ul"); midimenu.id = "webaudioctrl-context-menu"; midimenu.innerHTML = `<li class="webaudioctrl-context-menu__title">MIDI Learn</li> <li class="webaudioctrl-context-menu__item" id="webaudioctrl-context-menu-learn" onclick="webAudioControlsMidiManager.contextMenuLearn()">Learn</li> <li class="webaudioctrl-context-menu__item" onclick="webAudioControlsMidiManager.contextMenuClear()">Clear</li> <li class="webaudioctrl-context-menu__item" onclick="webAudioControlsMidiManager.contextMenuClose()">Close</li> `; let opt = { useMidi: 0, midilearn: 0, mididump: 0, outline: 0, knobSrc: null, knobSprites: 0, knobWidth: 0, knobHeight: 0, knobDiameter: 64, knobColors: "#fff;#000;#000", sliderSrc: null, sliderKnobsrc: null, sliderWidth: 0, sliderHeight: 0, sliderKnobwidth: 0, sliderKnobheight: 0, sliderDitchlength: 0, sliderColors: "#bbb;#444;#fff", switchWidth: 0, switchHeight: 0, switchDiameter: 24, switchColors: "#e00;#000;#fcc", paramWidth: 32, paramHeight: 16, paramColors: "#fff;#000", }; if (window.WebAudioControlsOptions) Object.assign(opt, window.WebAudioControlsOptions); class WebAudioControlsWidget extends HTMLElement { constructor() { super(); this.addEventListener("keydown", this.keydown); this.addEventListener("mousedown", this.pointerdown, { passive: false }); this.addEventListener("touchstart", this.pointerdown, { passive: false }); this.addEventListener("wheel", this.wheel, { passive: true }); this.addEventListener("mouseover", this.pointerover); this.addEventListener("mouseout", this.pointerout); this.addEventListener("contextmenu", this.contextMenu); this.hover = this.drag = 0; document.body.appendChild(midimenu); this.basestyle = ` .webaudioctrl-tooltip{ display:inline-block; position:absolute; margin:0 -1000px; z-index: 999; background:#eee; color:#000; border:1px solid #666; border-radius:4px; padding:5px 10px; text-align:center; left:0; top:0; font-size:11px; opacity:0; visibility:hidden; } .webaudioctrl-tooltip:before{ content: ""; position: absolute; top: 100%; left: 50%; margin-left: -8px; border: 8px solid transparent; border-top: 8px solid #666; } .webaudioctrl-tooltip:after{ content: ""; position: absolute; top: 100%; left: 50%; margin-left: -6px; border: 6px solid transparent; border-top: 6px solid #eee; } `; } sendEvent(ev) { let event; event = document.createEvent("HTMLEvents"); event.initEvent(ev, false, true); this.dispatchEvent(event); } getAttr(n, def) { let v = this.getAttribute(n); if (v == "" || v == null) return def; switch (typeof (def)) { case "number": if (v == "true") return 1; v = +v; if (isNaN(v)) return 0; return v; } return v; } showtip(d) { function numformat(s, x) { let i = s.indexOf("%"); let c = [0, 0], type = 0, m = 0, r = "", j = i + 1; for (; j < s.length; ++j) { if ("dfxXs".indexOf(s[j]) >= 0) { type = s[j]; break; } if (s[j] == ".") m = 1; else c[m] = c[m] * 10 + parseInt(s[j]); } switch (type) { case "x": r = (x | 0).toString(16); break; case "X": r = (x | 0).toString(16).toUpperCase(); break; case "d": r = (x | 0).toString(); break; case "f": r = x.toFixed(c[1]); break; case "s": r = x.toString(); break; } if (c[0] > 0) r = (" " + r).slice(-c[0]); r = s.replace(/%.*[xXdfs]/, r); return r; } let s = this.tooltip; if (this.drag || this.hover) { if (this.valuetip) { if (s == null) s = `%.${this.digits}f`; else if (s.indexOf("%") < 0) s += ` : %.${this.digits}f`; } if (s) { this.ttframe.innerHTML = numformat(s, this.convValue); this.ttframe.style.display = "inline-block"; this.ttframe.style.width = "auto"; this.ttframe.style.height = "auto"; this.ttframe.style.transition = "opacity 0.5s " + d + "s,visibility 0.5s " + d + "s"; this.ttframe.style.opacity = 0.9; this.ttframe.style.visibility = "visible"; let rc = this.getBoundingClientRect(), rc2 = this.ttframe.getBoundingClientRect(), rc3 = document.documentElement.getBoundingClientRect(); this.ttframe.style.left = ((rc.width - rc2.width) * 0.5 + 1000) + "px"; this.ttframe.style.top = (-rc2.height - 8) + "px"; return; } } this.ttframe.style.transition = "opacity 0.1s " + d + "s,visibility 0.1s " + d + "s"; this.ttframe.style.opacity = 0; this.ttframe.style.visibility = "hidden"; } pointerover(e) { this.hover = 1; this.showtip(0.6); } pointerout(e) { this.hover = 0; this.showtip(0); } contextMenu(e) { if (window.webAudioControlsMidiManager && this.midilearn) { webAudioControlsMidiManager.contextMenuOpen(e, this); } e.preventDefault(); e.stopPropagation(); } setMidiController(channel, cc) { if (this.listeningToThisMidiController(channel, cc)) return; this.midiController = { 'channel': channel, 'cc': cc }; console.log("Added mapping for channel=" + channel + " cc=" + cc + " tooltip=" + this.tooltip); } listeningToThisMidiController(channel, cc) { const c = this.midiController; if ((c.channel === channel || c.channel < 0) && c.cc === cc) return true; return false; } processMidiEvent(event) { const channel = event.data[0] & 0xf; const controlNumber = event.data[1]; if (this.midiMode == 'learn') { this.setMidiController(channel, controlNumber); webAudioControlsMidiManager.contextMenuClose(); this.midiMode = 'normal'; } if (this.listeningToThisMidiController(channel, controlNumber)) { if (this.tagName == "WEBAUDIO-SWITCH") { switch (this.type) { case "toggle": if (event.data[2] >= 64) this.setValue(1 - this.value, true); break; case "kick": this.setValue(event.data[2] >= 64 ? 1 : 0); break; case "radio": let els = document.querySelectorAll("webaudio-switch[type='radio'][group='" + this.group + "']"); for (let i = 0; i < els.length; ++i) { if (els[i] == this) els[i].setValue(1); else els[i].setValue(0); } break; } } else { const val = this.min + (this.max - this.min) * event.data[2] / 127; this.setValue(val, true); } } } } try { customElements.define("webaudio-knob", class WebAudioKnob extends WebAudioControlsWidget { constructor() { super(); } get src() { return this._src; } set src(v) { this._src = v; this.setupImage(); } get value() { return this._value; } set value(v) { this._value = +v; this.redraw(); } get min() { return this._min; } set min(v) { this._min = +v; this.redraw(); } get max() { return this._max; } set max(v) { this._max = +v; this.redraw(); } get step() { return this._step; } set step(v) { this._step = +v; this.redraw(); } get sprites() { return this._sprites; } set sprites(v) { this._sprites = +v; this.setupImage(); } get width() { return this._width; } set width(v) { this._width = +v; this.setupImage(); } get height() { return this._height; } set height(v) { this._height = +v; this.setupImage(); } get diameter() { return this._diameter; } set diameter(v) { this._diameter = +v; this.setupImage(); } get colors() { return this._colors; } set colors(v) { this._colors = v; this.setupImage(); } connectedCallback() { let root; // if(this.attachShadow) // root=this.attachShadow({mode: 'open'}); // else root = this; root.innerHTML = `<style> ${this.basestyle} webaudio-knob{ display:inline-block; position:relative; margin:0; padding:0; cursor:pointer; font-family: sans-serif; font-size: 11px; } .webaudio-knob-body{ display:inline-block; position:relative; z-index:1; margin:0; padding:0; } </style> <div class='webaudio-knob-body' tabindex='1' touch-action='none'></div><div class='webaudioctrl-tooltip'></div> `; this.elem = root.childNodes[2]; this.ttframe = root.childNodes[3]; this.enable = this.getAttr("enable", 1); this._src = this.getAttr("src", opt.knobSrc); this._value = this.getAttr("value", 0); this.defvalue = this.getAttr("defvalue", 0); this._min = this.getAttr("min", 0); this._max = this.getAttr("max", 100); this._step = this.getAttr("step", 1); this._sprites = this.getAttr("sprites", opt.knobSprites); this._width = this.getAttr("width", opt.knobWidth); this._height = this.getAttr("height", opt.knobHeight); this._diameter = this.getAttr("diameter", opt.knobDiameter); this._colors = this.getAttr("colors", opt.knobColors); this.outline = this.getAttr("outline", opt.outline); this.sensitivity = this.getAttr("sensitivity", 1); this.valuetip = this.getAttr("valuetip", 1); this.tooltip = this.getAttr("tooltip", null); this.conv = this.getAttr("conv", null); if (this.conv) this.convValue = eval(this.conv)(this._value); else this.convValue = this._value; this.midilearn = this.getAttr("midilearn", opt.midilearn); this.midicc = this.getAttr("midicc", null); this.midiController = {}; this.midiMode = "normal"; if (this.midicc) { let ch = parseInt(this.midicc.substring(0, this.midicc.lastIndexOf("."))) - 1; let cc = parseInt(this.midicc.substring(this.midicc.lastIndexOf(".") + 1)); this.setMidiController(ch, cc); } this.setupImage(); this.digits = 0; this.coltab = ["#e00", "#000", "#000"]; if (window.webAudioControlsMidiManager) // window.webAudioControlsMidiManager.updateWidgets(); window.webAudioControlsMidiManager.addWidget(this); } disconnectedCallback() { } setupImage() { this.kw = this.width || this.diameter; this.kh = this.height || this.diameter; if (!this.src) { if (this.colors) this.coltab = this.colors.split(";"); if (!this.coltab) this.coltab = ["#e00", "#000", "#000"]; let svg = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="6464" preserveAspectRatio="none"> <radialGradient id="gr" cx="30%" cy="30%"><stop offset="0%" stop-color="${this.coltab[2]}"/><stop offset="100%" stop-color="${this.coltab[1]}"/></radialGradient> <defs><circle id="B" cx="32" cy="32" r="30" fill="url(#gr)"/></defs> <defs><line id="K" x1="32" y1="28" x2="32" y2="7" stroke-linecap="round" stroke-width="6" stroke="${this.coltab[0]}"/></defs>`; for (let i = 0; i < 101; ++i) { svg += `<use xlink:href="#B" y="${64 * i}"/>`; svg += `<use xlink:href="#K" y="${64 * i}" transform="rotate(${(-135 + 270 * i / 101).toFixed(2)},32,${64 * i + 32})"/>`; } svg += "</svg>"; this.elem.style.backgroundImage = "url(data:image/svg+xml;base64," + btoa(svg) + ")"; // this.elem.style.backgroundSize = "100% 10100%"; this.elem.style.backgroundSize = `${this.kw}px ${this.kh * 101}px`; } else { this.elem.style.backgroundImage = "url(" + (this.src) + ")"; if (!this.sprites) this.elem.style.backgroundSize = "100% 100%"; else { // this.elem.style.backgroundSize = `100% ${(this.sprites+1)*100}%`; this.elem.style.backgroundSize = `${this.kw}px ${this.kh * (this.sprites + 1)}px`; } } this.elem.style.outline = this.outline ? "" : "none"; this.elem.style.width = this.kw + "px"; this.elem.style.height = this.kh + "px"; this.redraw(); } redraw() { if (!this.isConnected) return; this.digits = 0; if (this.step && this.step < 1) { for (let n = this.step; n < 1; n *= 10) ++this.digits; } if (this.value < this.min) { this.value = this.min; return; } if (this.value > this.max) { this.value = this.max; return; } let range = this.max - this.min; let style = this.elem.style; let sp = this.src ? this.sprites : 100; if (sp >= 1) { let offset = ((sp * (this.value - this.min) / range) | 0); style.backgroundPosition = "0px " + (-offset * this.kh) + "px"; style.transform = 'rotate(0deg)'; } else { let deg = 270 * ((this.value - this.min) / range - 0.5); style.backgroundPosition = "0px 0px"; style.transform = 'rotate(' + deg + 'deg)'; } } _setValue(v) { if (this.step) v = (Math.round((v - this.min) / this.step)) * this.step + this.min; this._value = Math.min(this.max, Math.max(this.min, v)); if (this._value != this.oldvalue) { this.oldvalue = this._value; if (this.conv) this.convValue = eval(this.conv)(this._value); else this.convValue = this._value; this.redraw(); this.showtip(0); return 1; } return 0; } setValue(v, f) { if (this._setValue(v) && f) this.sendEvent("input"), this.sendEvent("change"); } wheel(e) { let delta = (this.max - this.min) * 0.01; delta = e.deltaY > 0 ? -delta : delta; if (!e.shiftKey) delta *= 5; if (Math.abs(delta) < this.step) delta = (delta > 0) ? +this.step : -this.step; this.setValue(+this.value + delta, true); e.preventDefault(); e.stopPropagation(); } pointerdown(ev) { if (!this.enable) return; let e = ev; if (ev.touches) { e = ev.changedTouches[0]; this.identifier = e.identifier; } else { if (e.buttons != 1 && e.button != 0) return; } this.elem.focus(); this.drag = 1; this.showtip(0); let pointermove = (ev) => { let e = ev; if (ev.touches) { for (let i = 0; i < ev.touches.length; ++i) { if (ev.touches[i].identifier == this.identifier) { e = ev.touches[i]; break; } } } if (this.lastShift !== e.shiftKey) { this.lastShift = e.shiftKey; this.startPosX = e.pageX; this.startPosY = e.pageY; this.startVal = this.value; } let offset = (this.startPosY - e.pageY - this.startPosX + e.pageX) * this.sensitivity; this._setValue(this.min + ((((this.startVal + (this.max - this.min) * offset / ((e.shiftKey ? 4 : 1) * 128)) - this.min) / this.step) | 0) * this.step); this.sendEvent("input"); if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; } let pointerup = (ev) => { let e = ev; if (ev.touches) { for (let i = 0; ;) { if (ev.changedTouches[i].identifier == this.identifier) { break; } if (++i >= ev.changedTouches.length) return; } } this.drag = 0; this.showtip(0); this.startPosX = this.startPosY = null; window.removeEventListener('mousemove', pointermove); window.removeEventListener('touchmove', pointermove, { passive: false }); window.removeEventListener('mouseup', pointerup); window.removeEventListener('touchend', pointerup); window.removeEventListener('touchcancel', pointerup); document.body.removeEventListener('touchstart', preventScroll, { passive: false }); this.sendEvent("change"); } let preventScroll = (e) => { e.preventDefault(); } if (e.ctrlKey || e.metaKey) this.setValue(this.defvalue, true); else { this.startPosX = e.pageX; this.startPosY = e.pageY; this.startVal = this.value; window.addEventListener('mousemove', pointermove); window.addEventListener('touchmove', pointermove, { passive: false }); } window.addEventListener('mouseup', pointerup); window.addEventListener('touchend', pointerup); window.addEventListener('touchcancel', pointerup); document.body.addEventListener('touchstart', preventScroll, { passive: false }); ev.preventDefault(); ev.stopPropagation(); return false; } }); } catch (error) { //console.log("webaudio-knob already defined"); } try { customElements.define("webaudio-slider", class WebAudioSlider extends WebAudioControlsWidget { constructor() { super(); } get src() { return this._src; } set src(v) { this._src = v; this.setupImage(); } get knobsrc() { return this._knobsrc; } set knobsrc(v) { this._knobsrc = v; this.setupImage(); } get value() { return this._value; } set value(v) { this._value = +v; this.redraw(); } get min() { return this._min; } set min(v) { this._min = +v; this.redraw(); } get max() { return this._max; } set max(v) { this._max = +v; this.redraw(); } get step() { return this._step; } set step(v) { this._step = +v; this.redraw(); } get sprites() { return this._sprites; } set sprites(v) { this._sprites = +v; this.setupImage(); } get direction() { return this._direction; } set direction(v) { this._direction = +v; this.setupImage(); } get width() { return this._width; } set width(v) { this._width = +v; this.setupImage(); } get height() { return this._height; } set height(v) { this._height = +v; this.setupImage(); } get knobwidth() { return this._knobwidth; } set knobwidth(v) { this._knobwidth = +v; this.setupImage(); } get knobheight() { return this._knobheight; } set knobheight(v) { this._knobheight = +v; this.setupImage(); } get ditchlength() { return this._ditchlength; } set ditchlength(v) { this._ditchlength = +v; this.setupImage(); } get colors() { return this._colors; } set colors(v) { this._colors = v; this.setupImage(); } connectedCallback() { let root; // if(this.attachShadow) // root=this.attachShadow({mode: 'open'}); // else root = this; root.innerHTML = `<style> ${this.basestyle} webaudio-slider{ display:inline-block; position:relative; margin:0; padding:0; font-family: sans-serif; font-size: 11px; cursor:pointer; } .webaudio-slider-body{ display:inline-block; position:relative; margin:0; padding:0; } .webaudio-slider-knob{ display:inline-block; position:absolute; margin:0; padding:0; } </style> <div class='webaudio-slider-body' tabindex='1' touch-action='none'><div class='webaudio-slider-knob' touch-action='none'></div></div><div class='webaudioctrl-tooltip'></div> `; this.elem = root.childNodes[2]; this.knob = this.elem.childNodes[0]; this.ttframe = root.childNodes[3]; this.enable = this.getAttr("enable", 1); this._src = this.getAttr("src", opt.sliderSrc); this._knobsrc = this.getAttr("knobsrc", opt.sliderKnobsrc); this._value = this.getAttr("value", 0); this.defvalue = this.getAttr("defvalue", 0); this._min = this.getAttr("min", 0); this._max = this.getAttr("max", 100); this._step = this.getAttr("step", 1); this._sprites = this.getAttr("sprites", 0); this._direction = this.getAttr("direction", null); this._width = this.getAttr("width", opt.sliderWidth); this._height = this.getAttr("height", opt.sliderHeight); if (this._direction == "horz") { if (this._width == 0) this._width = 128; if (this._height == 0) this._height = 24; } else { if (this._width == 0) this._width = 24; if (this._height == 0) this._height = 128; } this._knobwidth = this.getAttr("knobwidth", opt.sliderKnobwidth); this._knobheight = this.getAttr("knbheight", opt.sliderKnobheight); this._ditchlength = this.getAttr("ditchlength", opt.sliderDitchlength); this._colors = this.getAttr("colors", opt.sliderColors); this.outline = this.getAttr("outline", opt.outline); this.sensitivity = this.getAttr("sensitivity", 1); this.valuetip = this.getAttr("valuetip", 1); this.tooltip = this.getAttr("tooltip", null); this.conv = this.getAttr("conv", null); if (this.conv) this.convValue = eval(this.conv)(this._value); else this.convValue = this._value; this.midilearn = this.getAttr("midilearn", opt.midilearn); this.midicc = this.getAttr("midicc", null); this.midiController = {}; this.midiMode = "normal"; if (this.midicc) { let ch = parseInt(this.midicc.substring(0, this.midicc.lastIndexOf("."))) - 1; let cc = parseInt(this.midicc.substring(this.midicc.lastIndexOf(".") + 1)); this.setMidiController(ch, cc); } this.setupImage(); this.digits = 0; if (window.webAudioControlsMidiManager) // window.webAudioControlsMidiManager.updateWidgets(); window.webAudioControlsMidiManager.addWidget(this); this.elem.onclick = (e) => { e.stopPropagation() }; } disconnectedCallback() { } setupImage() { this.coltab = this.colors.split(";"); this.dr = this.direction; this.dlen = this.ditchlength; if (!this.width) { if (this.dr == "horz") this.width = 128; else this.width = 24; } if (!this.height) { if (this.dr == "horz") this.height = 24; else this.height = 128; } if (!this.dr) this.dr = (this.width <= this.height) ? "vert" : "horz"; if (this.dr == "vert") { if (!this.dlen) this.dlen = this.height - this.width; } else { if (!this.dlen) this.dlen = this.width - this.height; } this.knob.style.backgroundSize = "100% 100%"; this.elem.style.backgroundSize = "100% 100%"; this.elem.style.width = this.width + "px"; this.elem.style.height = this.height + "px"; this.kwidth = this.knobwidth || (this.dr == "horz" ? this.height : this.width); this.kheight = this.knobheight || (this.dr == "horz" ? this.height : this.width); this.knob.style.width = this.kwidth + "px"; this.knob.style.height = this.kheight + "px"; if (!this.src) { let r = Math.min(this.width, this.height) * 0.5; let svgbody = `<svg xmlns="http://www.w3.org/2000/svg" width="${this.width}" height="${this.height}" preserveAspectRatio="none"> <rect x="1" y="1" rx="${r}" ry="${r}" width="${this.width - 2}" height="${this.height - 2}" fill="${this.coltab[1]}"/></svg>`; this.elem.style.backgroundImage = "url(data:image/svg+xml;base64," + btoa(svgbody) + ")"; } else { this.elem.style.backgroundImage = "url(" + (this.src) + ")"; } if (!this.knobsrc) { let svgthumb = `<svg xmlns="http://www.w3.org/2000/svg" width="${this.kwidth}" height="${this.kheight}" preserveAspectRatio="none"> <radialGradient id="gr" cx="30%" cy="30%"><stop offset="0%" stop-color="${this.coltab[2]}"/><stop offset="100%" stop-color="${this.coltab[0]}"/></radialGradient> <rect x="2" y="2" width="${this.kwidth - 4}" height="${this.kheight - 4}" rx="${this.kwidth * 0.5}" ry="${this.kheight * 0.5}" fill="url(#gr)"/></svg>`; this.knob.style.backgroundImage = "url(data:image/svg+xml;base64," + btoa(svgthumb) + ")"; } else { this.knob.style.backgroundImage = "url(" + (this.knobsrc) + ")"; } this.elem.style.outline = this.outline ? "" : "none"; this.redraw(); } redraw() { if (!this.isConnected) return; this.digits = 0; if (this.step && this.step < 1) { for (let n = this.step; n < 1; n *= 10) ++this.digits; } if (this.value < this.min) { this.value = this.min; return; } if (this.value > this.max) { this.value = this.max; return; } let range = this.max - this.min; let style = this.knob.style; if (this.dr == "vert") { style.left = (this.width - this.kwidth) * 0.5 + "px"; style.top = (1 - (this.value - this.min) / range) * this.dlen + "px"; this.sensex = 0; this.sensey = 1; } else { style.top = (this.height - this.kheight) * 0.5 + "px"; style.left = (this.value - this.min) / range * this.dlen + "px"; this.sensex = 1; this.sensey = 0; } } _setValue(v) { v = (Math.round((v - this.min) / this.step)) * this.step + this.min; this._value = Math.min(this.max, Math.max(this.min, v)); if (this._value != this.oldvalue) { this.oldvalue = this._value; if (this.conv) this.convValue = eval(this.conv)(this._value); else this.convValue = this._value; this.redraw(); this.showtip(0); return 1; } return 0; } setValue(v, f) { if (this._setValue(v) && f) this.sendEvent("input"), this.sendEvent("change"); } wheel(e) { let delta = (this.max - this.min) * 0.01; delta = e.deltaY > 0 ? -delta : delta; if (!e.shiftKey) delta *= 5; if (Math.abs(delta) < this.step) delta = (delta > 0) ? +this.step : -this.step; this.setValue(+this.value + delta, true); e.preventDefault(); e.stopPropagation(); this.redraw(); } pointerdown(ev) { if (!this.enable) return; let e = ev; if (ev.touches) { e = ev.changedTouches[0]; this.identifier = e.identifier; } else { if (e.buttons != 1 && e.button != 0) return; } this.elem.focus(); this.drag = 1; this.showtip(0); let pointermove = (ev) => { let e = ev; if (ev.touches) { for (let i = 0; i < ev.touches.length; ++i) { if (ev.touches[i].identifier == this.identifier) { e = ev.touches[i]; break; } } } if (this.lastShift !== e.shiftKey) { this.lastShift = e.shiftKey; this.startPosX = e.pageX; this.startPosY = e.pageY; this.startVal = this.value; } let offset = ((this.startPosY - e.pageY) * this.sensey - (this.startPosX - e.pageX) * this.sensex) * this.sensitivity; this._setValue(this.min + ((((this.startVal + (this.max - this.min) * offset / ((e.shiftKey ? 4 : 1) * this.dlen)) - this.min) / this.step) | 0) * this.step); this.sendEvent("input"); if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; } let pointerup = (ev) => { let e = ev; if (ev.touches) { for (let i = 0; ;) { if (ev.changedTouches[i].identifier == this.identifier) { break; } if (++i >= ev.changedTouches.length) return; } } this.drag = 0; this.showtip(0); this.startPosX = this.startPosY = null; window.removeEventListener('mousemove', pointermove); window.removeEventListener('touchmove', pointermove, { passive: false }); window.removeEventListener('mouseup', pointerup); window.removeEventListener('touchend', pointerup); window.removeEventListener('touchcancel', pointerup); document.body.removeEventListener('touchstart', preventScroll, { passive: false }); this.sendEvent("change"); } let preventScroll = (e) => { e.preventDefault(); } if (e.touches) e = e.touches[0]; if (e.ctrlKey || e.metaKey) this.setValue(this.defvalue, true); else { this.startPosX = e.pageX; this.startPosY = e.pageY; this.startVal = this.value; window.addEventListener('mousemove', pointermove); window.addEventListener('touchmove', pointermove, { passive: false }); } window.addEventListener('mouseup', pointerup); window.addEventListener('touchend', pointerup); window.addEventListener('touchcancel', pointerup); document.body.addEventListener('touchstart', preventScroll, { passive: false }); e.preventDefault(); e.stopPropagation(); return false; } }); } catch (error) { //console.log("webaudio-slider already defined"); } try { customElements.define("webaudio-switch", class WebAudioSwitch extends WebAudioControlsWidget { constructor() { super(); } get src() { return this._src; } set src(v) { this._src = v; this.setupImage(); } get value() { return this._value; } set value(v) { this._value = +v; this.redraw(); } get width() { return this._width; } set width(v) { this._width = +v; this.setupImage(); } get height() { return this._height; } set height(v) { this._height = +v; this.setupImage(); } get diameter() { return this._diameter; } set diameter(v) { this._diameter = +v; this.setupImage(); } get colors() { return this._colors; } set colors(v) { this._colors = v; this.setupImage(); } connectedCallback() { let root; // if(this.attachShadow) // root=this.attachShadow({mode: 'open'}); // else root = this; root.innerHTML = `<style> ${this.basestyle} webaudio-switch{ display:inline-block; margin:0; padding:0; font-family: sans-serif; font-size: 11px; cursor:pointer; } .webaudio-switch-body{ display:inline-block; margin:0; padding:0; } </style> <div class='webaudio-switch-body' tabindex='1' touch-action='none'><div class='webaudioctrl-tooltip'></div></div> `; this.elem = root.childNodes[2]; this.ttframe = this.elem.childNodes[0]; this.enable = this.getAttr("enable", 1); this._src = this.getAttr("src", null); this._value = this.getAttr("value", 0); this.defvalue = this.getAttr("defvalue", 0); this.type = this.getAttr("type", "toggle"); this.group = this.getAttr("group", ""); this._width = this.getAttr("width", 0); this._height = this.getAttr("height", 0); this._diameter = this.getAttr("diameter", 0); this.invert = this.getAttr("invert", 0); this._colors = this.getAttr("colors", opt.switchColors); this.outline = this.getAttr("outline", opt.outline); this.valuetip = 0; this.tooltip = this.getAttr("tooltip", null); this.midilearn = this.getAttr("midilearn", opt.midilearn); this.midicc = this.getAttr("midicc", null); this.midiController = {}; this.midiMode = "normal"; if (this.midicc) { let ch = parseInt(this.midicc.substring(0, this.midicc.lastIndexOf("."))) - 1; let cc = parseInt(this.midicc.substring(this.midicc.lastIndexOf(".") + 1)); this.setMidiController(ch, cc); } this.setupImage(); this.digits = 0; if (window.webAudioControlsMidiManager) // window.webAudioControlsMidiManager.updateWidgets(); window.webAudioControlsMidiManager.addWidget(this); this.elem.onclick = (e) => { e.stopPropagation() }; } disconnectedCallback() { } setupImage() { let w = this.width || this.diameter || opt.switchWidth || opt.switchDiameter; let h = this.height || this.diameter || opt.switchHeight || opt.switchDiameter; if (!this.src) { this.coltab = this.colors.split(";"); let mm = Math.min(w, h); let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h * 2}" preserveAspectRatio="none"> <radialGradient id="gr" cx="30%" cy="30%"><stop offset="0%" stop-color="${this.coltab[2]}"/><stop offset="100%" stop-color="${this.coltab[0]}"/></radialGradient> <rect x="${w * 0.05}" y="${h * 0.05}" width="${w * 0.9}" height="${h * 0.9}" rx="${mm * 0.1}" ry="${mm * 0.1}" fill="${this.coltab[1]}"/> <rect x="${w * 0.05}" y="${h * 1.05}" width="${w * 0.9}" height="${h * 0.9}" rx="${mm * 0.1}" ry="${mm * 0.1}" fill="${this.coltab[1]}"/> <circle cx="${w * 0.5}" cy="${h * 0.5}" r="${mm * 0.3}" stroke="${this.coltab[0]}" stroke-width="2"/> <circle cx="${w * 0.5}" cy="${h * 1.5}" r="${mm * 0.3}" stroke="${this.coltab[0]}" stroke-width="2" fill="url(#gr)"/></svg>`; this.elem.style.backgroundImage = "url(data:image/svg+xml;base64," + btoa(svg) + ")"; this.elem.style.backgroundSize = "100% 200%"; } else { this.elem.style.backgroundImage = "url(" + (this.src) + ")"; if (!this.sprites) this.elem.style.backgroundSize = "100% 200%"; else this.elem.style.backgroundSize = `100% ${(this.sprites + 1) * 100}%`; } this.elem.style.width = w + "px"; this.elem.style.height = h + "px"; this.elem.style.outline = this.outline ? "" : "none"; this.redraw(); } redraw() { if (!this.isConnected) return; let style = this.elem.style; if (this.value ^ this.invert) style.backgroundPosition = "0px -100%"; else style.backgroundPosition = "0px 0px"; } setValue(v, f) { console.log(v, f) this.value = v; this.checked = (!!v); if (this.value != this.oldvalue) { this.redraw(); this.showtip(0); if (f) { this.sendEvent("input"); this.sendEvent("change"); } this.oldvalue = this.value; } } pointerdown(ev) { if (!this.enable) return; let e = ev; if (ev.touches) { e = ev.changedTouches[0]; this.identifier = e.identifier; } else { if (e.buttons != 1 && e.button != 0) return; } this.elem.focus(); this.drag = 1; this.showtip(0); let pointermove = (e) => { e.preventDefault(); e.stopPropagation(); return false; } let pointerup = (e) => { this.drag = 0; this.showtip(0); window.removeEventListener('mousemove', pointermove); window.removeEventListener('touchmove', pointermove, { passive: false }); window.removeEventListener('mouseup', pointerup); window.removeEventListener('touchend', pointerup); window.removeEventListener('touchcancel', pointerup); document.body.removeEventListener('touchstart', preventScroll, { passive: false }); if (this.type == "kick") { this.value = 0; this.checked = false; this.redraw(); this.sendEvent("change"); } this.sendEvent("click"); e.preventDefault(); e.stopPropagation(); } let preventScroll = (e) => { e.preventDefault(); } switch (this.type) { case "kick": this.setValue(1); this.sendEvent("change"); break; case "toggle": if (e.ctrlKey || e.metaKey) this.value = defvalue; else this.value = 1 - this.value; this.checked = !!this.value; this.sendEvent("change"); break; case "radio": let els = document.querySelectorAll("webaudio-switch[type='radio'][group='" + this.group + "']"); for (let i = 0; i < els.length; ++i) { if (els[i] == this) els[i].setValue(1); else els[i].setValue(0); } this.sendEvent("change"); break; } window.addEventListener('mouseup', pointerup); window.addEventListener('touchend', pointerup); window.addEventListener('touchcancel', pointerup); document.body.addEventListener('touchstart', preventScroll, { passive: false }); this.redraw(); e.preventDefault(); e.stopPropagation(); return false; } }); } catch (error) { //console.log("webaudio-switch already defined"); } try { customElements.define("webaudio-param", class WebAudioParam extends WebAudioControlsWidget { constructor() { super(); this.addEventListener("keydown", this.keydown); this.addEventListener("mousedown", this.pointerdown, { passive: false }); this.addEventListener("touchstart", this.pointerdown, { passive: false }); this.addEventListener("wheel", this.wheel); this.addEventListener("mouseover", this.pointerover); this.addEventListener("mouseout", this.pointerout); this.addEventListener("contextmenu", this.contextMenu); } get src() { return this._src; } set src(v) { this._src = v; this.setupImage(); } get value() { return this._value; } set value(v) { this._value = +v; this.redraw(); } get fontsize() { return this._fontsize; } set fontsize(v) { this._fontsize = +v; this.setupImage(); } get width() { return this._width; } set width(v) { this._width = +v; this.setupImage(); } get height() { return this._height; } set height(v) { this._height = +v; this.setupImage(); } get colors() { return this._colors; } set colors(v) { this._colors = v; this.setupImage(); } connectedCallback() { let root; // if(this.attachShadow) // root=this.attachShadow({mode: 'open'}); // else root = this; root.innerHTML = `<style> ${this.basestyle} webaudio-param{ display:inline-block; user-select:none; margin:0; padding:0; font-family: sans-serif; font-size: 8px; cursor:pointer; } .webaudio-param-body{ display:inline-block; text-align:center; vertical-align:middle; position:relative; border:1px solid #888; background:none; border-radius:4px; margin:0; padding:0; font-family:sans-serif; font-size:11px; } </style> <input class='webaudio-param-body' value='0' tabindex='1' touch-action='none'/><div class='webaudioctrl-tooltip'></div> `; this.elem = root.childNodes[2]; this.ttframe = root.childNodes[3]; this.enable = this.getAttr("enable", 1); this._value = this.getAttr("value", 0); this.defvalue = this.getAttr("defvalue", 0); this._fontsize = this.getAttr("fontsize", 9); this._src = this.getAttr("src", null); this.link = this.getAttr("link", ""); this._width = this.getAttr("width", 32); this._height = this.getAttr("height", 16); this._colors = this.getAttr("colors", "#fff;#000"); this.outline = this.getAttr("outline", opt.outline); this.midiController = {}; this.midiMode = "normal"; if (this.midicc) { let ch = parseInt(this.midicc.substring(0, this.midicc.lastIndexOf("."))) - 1; let cc = parseInt(this.midicc.substring(this.midicc.lastIndexOf(".") + 1)); this.setMidiController(ch, cc); } this.setupImage(); if (window.webAudioControlsMidiManager) // window.webAudioControlsMidiManager.updateWidgets(); window.webAudioControlsMidiManager.addWidget(this); this.fromLink = ((e) => { this.setValue(e.target.convValue.toFixed(e.target.digits)); }).bind(this); this.elem.onchange = () => { let le = document.getElementById(this.link); if (le) le.setValue(+this.elem.value); } } disconnectedCallback() { } setupImage() { this.coltab = this.colors.split(";"); this.elem.style.color = this.coltab[0]; if (!this.src) { this.elem.style.backgroundColor = this.coltab[1]; } else { this.elem.style.backgroundImage = "url(" + (this.src) + ")"; this.elem.style.backgroundSize = "100% 100%"; } this.elem.style.width = this.width + "px"; this.elem.style.height = this.height + "px"; this.elem.style.fontSize = this.fontsize + "px"; this.elem.style.outline = this.outline ? "" : "none"; let l = document.getElementById(this.link); if (l) { this.setValue(l.value.toFixed(l.digits)); l.addEventListener("input", (e) => { this.setValue(l.value.toFixed(l.digits)) }); } this.redraw(); } redraw() { if (!this.isConnected) return; this.elem.value = this.value; } setValue(v, f) { this.value = v; if (this.value != this.oldvalue) { this.redraw(); this.showtip(0); if (f) { let event = document.createEvent("HTMLEvents"); event.initEvent("change", false, true); this.dispatchEvent(event); } this.oldvalue = this.value; } } pointerdown(ev) { if (!this.enable) return; let e = ev; if (ev.touches) e = ev.touches[0]; else { if (e.buttons != 1 && e.button != 0) return; } this.elem.focus(); this.drag = 1; this.showtip(0); let pointermove = (e) => { e.preventDefault(); e.stopPropagation(); return false; } let pointerup = (e) => { this.drag = 0; this.showtip(0); window.removeEventListener('mouseup', pointerup); window.removeEventListener('touchend', pointerup); window.removeEventListener('touchcancel', pointerup); document.body.removeEventListener('touchstart', preventScroll, { passive: false }); } let preventScroll = (e) => { e.preventDefault(); } window.addEventListener('mouseup', pointerup); window.addEventListener('touchend', pointerup); window.addEventListener('touchcancel', pointerup); document.body.addEventListener('touchstart', preventScroll, { passive: false }); this.redraw(); if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; } }); } catch (error) { //console.log("webaudio-param already defined"); } try { customElements.define("webaudio-keyboard", class WebAudioKeyboard extends WebAudioControlsWidget { constructor() { super(); } get width() { return this._width; } set width(v) { this._width = v; this.setupImage(); } get height() { return this._height; } set height(v) { this._height = v; this.setupImage(); } get min() { return this._min; } set min(v) { this._min = +v; this.redraw(); } get step() { return this._step; } set step(v) { this._step = +v; this.redraw(); } get keys() { return this._keys; } set keys(v) { this._keys = +v; this.setupImage(); } get colors() { return this._colors; } set colors(v) { this._colors = v; this.setupImage(); } connectedCallback() { let root; // if(this.attachShadow) // root=this.attachShadow({mode: 'open'}); // else root = this; root.innerHTML = `<style> ${this.basestyle} webaudio-keyboard{ display:inline-block; position:relative; margin:0; padding:0; font-family: sans-serif; font-size: 11px; } .webaudio-keyboard-body{ display:inline-block; margin:0; padding:0; } </style> <canvas class='webaudio-keyboard-body' tabindex='1' touch-action='none'></canvas><div class='webauioctrl-tooltip'></div> `; this.cv = root.childNodes[2]; this.ttframe = root.childNodes[3]; this.ctx = this.cv.getContext("2d"); this._values = []; this.enable = this.getAttr("enable", 1); this._width = this.getAttr("width", 480); this._height = this.getAttr("height", 128); this._min = this.getAttr("min", 0); this._keys = this.getAttr("keys", 25); this._colors = this.getAttr("colors", "#222;#eee;#ccc;#333;#000;#e88;#c44;#c33;#800"); this.outline = this.getAttr("outline", opt.outline); this.midilearn = this.getAttr("midilearn", 0); this.midicc = this.getAttr("midicc", null); this.press = 0; this.keycodes1 = [90, 83, 88, 68, 67, 86, 71, 66, 72, 78, 74, 77, 188, 76, 190, 187, 191, 226]; this.keycodes2 = [81, 50, 87, 51, 69, 82, 53, 84, 54, 89, 55, 85, 73, 57, 79, 48, 80, 192, 222, 219]; this.addEventListener("keyup", this.keyup); this.midiController = {}; this.midiMode = "normal"; if (this.midicc) { let ch = parseInt(this.midicc.substring(0, this.midicc.lastIndexOf("."))) - 1; let cc = parseInt(this.midicc.substring(this.midicc.lastIndexOf(".") + 1)); this.setMidiController(ch, cc); } this.setupImage(); this.digits = 0; this.addEventListener("mousemove", this.pointermove); this.addEventListener("touchmove", this.pointermove, { passive: false }); this.addEventListener("mouseup", this.pointerup); this.addEventListener("touchend", this.pointerup); this.addEventListener("touchcancel", this.pointerup); if (window.webAudioControlsMidiManager) // window.webAudioControlsMidiManager.updateWidgets(); window.webAudioControlsMidiManager.addWidget(this); } disconnectedCallback() { } setupImage() { this.cv.style.width = this.width + "px"; this.cv.style.height = this.height + "px"; this.bheight = this.height * 0.55; this.kp = [0, 7 / 12, 1, 3 * 7 / 12, 2, 3, 6 * 7 / 12, 4, 8 * 7 / 12, 5, 10 * 7 / 12, 6]; this.kf = [0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0]; this.ko = [0, 0, (7 * 2) / 12 - 1, 0, (7 * 4) / 12 - 2, (7 * 5) / 12 - 3, 0, (7 * 7) / 12 - 4, 0, (7 * 9) / 12 - 5, 0, (7 * 11) / 12 - 6]; this.kn = [0, 2, 4, 5, 7, 9, 11]; this.coltab = this.colors.split(";"); this.cv.width = this.width; this.cv.height = this.height; this.cv.style.width = this.width + 'px'; this.cv.style.height = this.height + 'px'; this.cv.style.outline = this.outline ? "" : "none"; this.bheight = this.height * 0.55; this.max = this.min + this.keys - 1; this.dispvalues = []; this.valuesold = []; if (this.kf[this.min % 12]) --this.min; if (this.kf[this.max % 12]) ++this.max; this.redraw(); } redraw() { if (!this.isConnected) return; function rrect(ctx, x, y, w, h, r, c1, c2) { if (c2) { let g = ctx.createLinearGradient(x, y, x + w, y); g.addColorStop(0, c1); g.addColorStop(1, c2); ctx.fillStyle = g; } else ctx.fillStyle = c1; ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + w, y); ctx.lineTo(x + w, y + h - r); ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); ctx.lineTo(x + r, y + h); ctx.quadraticCurveTo(x, y + h, x, y + h - r); ctx.lineTo(x, y); ctx.fill(); } this.ctx.fillStyle = this.coltab[0]; this.ctx.fillRect(0, 0, this.width, this.height); let x0 = 7 * ((this.min / 12) | 0) + this.kp[this.min % 12]; let x1 = 7 * ((this.max / 12) | 0) + this.kp[this.max % 12]; let n = x1 - x0; this.wwidth = (this.width - 1) / (n + 1); this.bwidth = this.wwidth * 7 / 12; let h2 = this.bheight; let r = Math.min(8, this.wwidth * 0.2); for (let i = this.min, j = 0; i <= this.max; ++i) { if (this.kf[i % 12] == 0) { let x = this.wwidth * (j++) + 1; if (this.dispvalues.indexOf(i) >= 0) rrect(this.ctx, x, 1, this.wwidth - 1, this.height - 2, r, this.coltab[5], this.coltab[6]); else rrect(this.ctx, x, 1, this.wwidth - 1, this.height - 2, r, this.coltab[1], this.coltab[2]); } } r = Math.min(8, this.bwidth * 0.3); for (let i = this.min; i < this.max; ++i) { if (this.kf[i % 12]) { let x = this.wwidth * this.ko[this.min % 12] + this.bwidth * (i - this.min) + 1; if (this.dispvalues.indexOf(i) >= 0) rrect(this.ctx, x, 1, this.bwidth, h2, r, this.coltab[7], this.coltab[8]); else rrect(this.ctx, x, 1, this.bwidth, h2, r, this.coltab[3], this.coltab[4]); this.ctx.strokeStyle = this.coltab[0]; this.ctx.stroke(); } } } _setValue(v) { if (this.step) v = (Math.round((v - this.min) / this.step)) * this.step + this.min; this._value = Math.min(this.max, Math.max(this.min, v)); if (this._value != this.oldvalue) { this.oldvalue = this._value; this.redraw(); this.showtip(0); return 1; } return 0; } setValue(v, f) { if (this._setValue(v) && f) this.sendEvent("input"), this.sendEvent("change"); } wheel(e) { } keydown(e) { let m = Math.floor((this.min + 11) / 12) * 12; let k = this.keycodes1.indexOf(e.keyCode); if (k < 0) { k = this.keycodes2.indexOf(e.keyCode); if (k >= 0) k += 12; } if (k >= 0) { k += m; if (this.currentKey != k) { this.currentKey = k; this.sendEventFromKey(1, k); this.setNote(1, k); } } } keyup(e) { let m = Math.floor((this.min + 11) / 12) * 12; let k = this.keycodes1.indexOf(e.keyCode); if (k < 0) { k = this.keycodes2.indexOf(e.keyCode); if (k >= 0) k += 12; } if (k >= 0) { k += m; this.currentKey = -1; this.sendEventFromKey(0, k); this.setNote(0, k); } } pointerdown(e) { this.cv.focus(); // document.body.addEventListener('touchstart',this.preventScroll,{passive:false}); if (this.enable) { ++this.press; this.pointermove(e); } e.preventDefault(); } pointermove(e) { if (!this.enable) return; let r = this.getBoundingClientRect(); let v = [], p; if (e.touches) p = e.targetTouches; else if (this.press) p = [e]; else p = []; if (p.length > 0) this.drag = 1; for (let i = 0; i < p.length; ++i) { let px = p[i].clientX - r.left; let py = p[i].clientY - r.top; let x, k, ko; if (py < this.bheight) { x = px - this.wwidth * this.ko[this.min % 12]; k = this.min + ((x / this.bwidth) | 0); } else { k = (px / this.wwidth) | 0; ko = this.kp[this.min % 12]; k += ko; k = this.min + ((k / 7)