text: // @ts-nocheck
  
  /* *
   *
   *  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) | 0) * 12 + this.kn[k % 7] - this.kn[ko % 7];
  					}
  					if (k >= this.min && k <= this.max)
  						v.push(k);
  				}
  				v.sort();
  				this.values = v;
  				this.sendevent();
  				this.redraw();
  			}
  			pointerup(e) {
  				if (this.enable) {
  					--this.press;
  					this.pointermove(e);
  					this.sendevent();
  					this.redraw();
  				}
  				this.drag = 0;
  				e.preventDefault();
  			}
  			sendEventFromKey(s, k) {
  				let ev = document.createEvent('HTMLEvents');
  				ev.initEvent('change', true, true);
  				ev.note = [s, k];
  				this.dispatchEvent(ev);
  			}
  			sendevent() {
  				let notes = [];
  				for (let i = 0, j = this.valuesold.length; i < j; ++i) {
  					if (this.values.indexOf(this.valuesold[i]) < 0)
  						notes.push([0, this.valuesold[i]]);
  				}
  				for (let i = 0, j = this.values.length; i < j; ++i) {
  					if (this.valuesold.indexOf(this.values[i]) < 0)
  						notes.push([1, this.values[i]]);
  				}
  				if (notes.length) {
  					this.valuesold = this.values;
  					for (let i = 0; i < notes.length; ++i) {
  						this.setdispvalues(notes[i][0], notes[i][1]);
  						let ev = document.createEvent('HTMLEvents');
  						ev.initEvent('change', true, true);
  						ev.note = notes[i];
  						this.dispatchEvent(ev);
  					}
  				}
  			}
  			setdispvalues(state, note) {
  				let n = this.dispvalues.indexOf(note);
  				if (state) {
  					if (n < 0) this.dispvalues.push(note);
  				}
  				else {
  					if (n >= 0) this.dispvalues.splice(n, 1);
  				}
  			}
  			setNote(state, note) {
  				this.setdispvalues(state, note);
  				this.redraw();
  			}
  		});
  	} catch (error) {
  		//console.log("webaudio-keyboard already defined");
  	}
  
  	// FOR MIDI LEARN
  	class WebAudioControlsMidiManager {
  		constructor() {
  			this.midiAccess = null;
  			this.listOfWidgets = [];
  			this.listOfExternalMidiListeners = [];
  			this.updateWidgets();
  			this.initWebAudioControls();
  		}
  		addWidget(w) {
  			this.listOfWidgets.push(w);
  		}
  		updateWidgets() {
  			//      this.listOfWidgets = document.querySelectorAll("webaudio-knob,webaudio-slider,webaudio-switch");
  		}
  		initWebAudioControls() {
  			if (navigator.requestMIDIAccess) {
  				navigator.requestMIDIAccess().then(
  					(ma) => { this.midiAccess = ma, this.enableInputs() },
  					(err) => { console.log("MIDI not initialized - error encountered:" + err.code) }
  				);
  			}
  		}
  		enableInputs() {
  			let inputs = this.midiAccess.inputs.values();
  			//console.log("Found " + this.midiAccess.inputs.size + " MIDI input(s)");
  			for (let input = inputs.next(); input && !input.done; input = inputs.next()) {
  				//console.log("Connected input: " + input.value.name);
  				input.value.onmidimessage = this.handleMIDIMessage.bind(this);
  			}
  		}
  		midiConnectionStateChange(e) {
  			// console.log("connection: " + e.port.name + " " + e.port.connection + " " + e.port.state);
  			enableInputs();
  		}
  
  		onMIDIStarted(midi) {
  			this.midiAccess = midi;
  			midi.onstatechange = this.midiConnectionStateChange;
  			enableInputs(midi);
  		}
  		// Add hooks for external midi listeners support
  		addMidiListener(callback) {
  			this.listOfExternalMidiListeners.push(callback);
  		}
  		getCurrentConfigAsJSON() {
  			return currentConfig.stringify();
  		}
  		handleMIDIMessage(event) {
  			this.listOfExternalMidiListeners.forEach(function (externalListener) {
  				externalListener(event);
  			});
  			if (((event.data[0] & 0xf0) == 0xf0) || ((event.data[0] & 0xf0) == 0xb0 && event.data[1] >= 120))
  				return;
  			for (let w of this.listOfWidgets) {
  				if (w.processMidiEvent)
  					w.processMidiEvent(event);
  			}
  			if (opt.mididump)
  				console.log(event.data);
  		}
  		contextMenuOpen(e, knob) {
  			if (!this.midiAccess)
  				return;
  			let menu = document.getElementById("webaudioctrl-context-menu");
  			menu.style.left = e.pageX + "px";
  			menu.style.top = e.pageY + "px";
  			menu.knob = knob;
  			menu.classList.add("active");
  			menu.knob.focus();
  			//      document.activeElement.onblur=this.contextMenuClose;
  			menu.knob.addEventListener("keydown", this.contextMenuCloseByKey.bind(this));
  		}
  		contextMenuCloseByKey(e) {
  			if (e.keyCode == 27)
  				this.contextMenuClose();
  		}
  		contextMenuClose() {
  			let menu = document.getElementById("webaudioctrl-context-menu");
  			menu.knob.removeEventListener("keydown", this.contextMenuCloseByKey);
  			menu.classList.remove("active");
  			let menuItemLearn = document.getElementById("webaudioctrl-context-menu-learn");
  			menuItemLearn.innerHTML = 'Learn';
  			menu.knob.midiMode = 'normal';
  		}
  		contextMenuLearn() {
  			let menu = document.getElementById("webaudioctrl-context-menu");
  			let menuItemLearn = document.getElementById("webaudioctrl-context-menu-learn");
  			menuItemLearn.innerHTML = 'Listening...';
  			menu.knob.midiMode = 'learn';
  		}
  		contextMenuClear(e) {
  			let menu = document.getElementById("webaudioctrl-context-menu");
  			menu.knob.midiController = {};
  			this.contextMenuClose();
  		}
  	}
  	if (window.UseWebAudioControlsMidi || opt.useMidi)
  		window.webAudioControlsMidiManager = new WebAudioControlsMidiManager();
  }
  