LJnano/www/webaudio-controls.js

1876 lines
66 KiB
JavaScript
Raw Permalink Normal View History

2020-10-25 19:18:56 +00:00
/* *
*
* 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:"#e00;#000;#000",
sliderSrc:null,
sliderKnobsrc:null,
sliderWidth:0,
sliderHeight:0,
sliderKnobwidth:0,
sliderKnobheight:0,
sliderDitchlength:0,
sliderColors:"#e00;#000;#fcc",
switchWidth:0,
switchHeight:0,
switchDiameter:24,
switchColors:"#e00;#000;#fcc",
paramWidth:32,
paramHeight:16,
paramColors:"#fff;#000",
xypadColors:"#e00;#000;#fcc",
};
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);
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 valstr(x,c,type){
switch(type){
case "x": return (x|0).toString(16);
case "X": return (x|0).toString(16).toUpperCase();
case "d": return (x|0).toString();
case "f": return x.toFixed(c);
case "s": return x.toString();
}
return "";
}
function numformat(s,x){
if(typeof(x)=="undefined")
return;
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]);
}
if(typeof(x)=="number")
r=valstr(x,c[1],type);
else
r=valstr(x.x,c[1],type)+","+valstr(x.y,c[1],type);
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();
}
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;
margin:0;
padding:0;
vertical-align:bottom;
}
</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); Object.defineProperty(this,"src",{get:()=>{return this._src},set:(v)=>{this._src=v;this.setupImage()}});
this._value=this.getAttr("value",0); Object.defineProperty(this,"value",{get:()=>{return this._value},set:(v)=>{this._value=v;this.redraw()}});
this.defvalue=this.getAttr("defvalue",0);
this._min=this.getAttr("min",0); Object.defineProperty(this,"min",{get:()=>{return this._min},set:(v)=>{this._min=+v;this.redraw()}});
this._max=this.getAttr("max",100); Object.defineProperty(this,"max",{get:()=>{return this._max},set:(v)=>{this._max=+v;this.redraw()}});
this._step=this.getAttr("step",1); Object.defineProperty(this,"step",{get:()=>{return this._step},set:(v)=>{this._step=+v;this.redraw()}});
this._sprites=this.getAttr("sprites",opt.knobSprites); Object.defineProperty(this,"sprites",{get:()=>{return this._sprites},set:(v)=>{this._sprites=v;this.setupImage()}});
this._width=this.getAttr("width",opt.knobWidth); Object.defineProperty(this,"width",{get:()=>{return this._width},set:(v)=>{this._width=v;this.setupImage()}});
this._height=this.getAttr("height",opt.knobHeight); Object.defineProperty(this,"height",{get:()=>{return this._height},set:(v)=>{this._height=v;this.setupImage()}});
this._diameter=this.getAttr("diameter",opt.knobDiameter); Object.defineProperty(this,"diameter",{get:()=>{return this._diameter},set:(v)=>{this._diameter=v;this.setupImage()}});
this._colors=this.getAttr("colors",opt.knobColors); Object.defineProperty(this,"colors",{get:()=>{return this._colors},set:(v)=>{this._colors=v;this.setupImage()}});
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.style.height=this.kh+"px";
this.redraw();
}
redraw() {
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();
}
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;
vertical-align:bottom;
}
.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); Object.defineProperty(this,"src",{get:()=>{return this._src},set:(v)=>{this._src=v;this.setupImage()}});
this._knobsrc=this.getAttr("knobsrc",opt.sliderKnobsrc); Object.defineProperty(this,"knobsrc",{get:()=>{return this._knobsrc},set:(v)=>{this._knobsrc=v;this.setupImage()}});
this._value=this.getAttr("value",0); Object.defineProperty(this,"value",{get:()=>{return this._value},set:(v)=>{this._value=v;this.redraw()}});
this.defvalue=this.getAttr("defvalue",0);
this._min=this.getAttr("min",0); Object.defineProperty(this,"min",{get:()=>{return this._min},set:(v)=>{this._min=v;this.redraw()}});
this._max=this.getAttr("max",100); Object.defineProperty(this,"max",{get:()=>{return this._max},set:(v)=>{this._max=v;this.redraw()}});
this._step=this.getAttr("step",1); Object.defineProperty(this,"step",{get:()=>{return this._step},set:(v)=>{this._step=v;this.redraw()}});
this._sprites=this.getAttr("sprites",0); Object.defineProperty(this,"sprites",{get:()=>{return this._sprites},set:(v)=>{this._sprites=v;this.setupImage()}});
this._direction=this.getAttr("direction",null); Object.defineProperty(this,"direction",{get:()=>{return this._direction},set:(v)=>{this._direction=v;this.setupImage()}});
this._width=this.getAttr("width",opt.sliderWidth); Object.defineProperty(this,"width",{get:()=>{return this._width},set:(v)=>{this._width=v;this.setupImage()}});
this._height=this.getAttr("height",opt.sliderHeight); Object.defineProperty(this,"height",{get:()=>{return this._height},set:(v)=>{this._height=v;this.setupImage()}});
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); Object.defineProperty(this,"knobwidth",{get:()=>{return this._knobwidth},set:(v)=>{this._knobwidth=v;this.setupImage()}});
this._knobheight=this.getAttr("knbheight",opt.sliderKnobheight); Object.defineProperty(this,"knobheight",{get:()=>{return this._knobheight},set:(v)=>{this._knobheight=v;this.setupImage()}});
this._ditchlength=this.getAttr("ditchlength",opt.sliderDitchlength); Object.defineProperty(this,"ditchlength",{get:()=>{return this._ditchlength},set:(v)=>{this._ditchlength=v;this.setupImage()}});
this._colors=this.getAttr("colors",opt.sliderColors); Object.defineProperty(this,"colors",{get:()=>{return this._colors},set:(v)=>{this._colors=v;this.setupImage()}});
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.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() {
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();
}
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;
vertical-align:bottom;
}
</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); Object.defineProperty(this,"src",{get:()=>{return this._src},set:(v)=>{this._src=v;this.setupImage()}});
this._value=this.getAttr("value",0); Object.defineProperty(this,"value",{get:()=>{return this._value},set:(v)=>{this._value=v;this.redraw()}});
this.defvalue=this.getAttr("defvalue",0);
this.type=this.getAttr("type","toggle");
this.group=this.getAttr("group","");
this._width=this.getAttr("width",0); Object.defineProperty(this,"width",{get:()=>{return this._width},set:(v)=>{this._width=v;this.setupImage()}});
this._height=this.getAttr("height",0); Object.defineProperty(this,"height",{get:()=>{return this._height},set:(v)=>{this._height=v;this.setupImage()}});
this._diameter=this.getAttr("diameter",0); Object.defineProperty(this,"diameter",{get:()=>{return this._diameter},set:(v)=>{this._diameter=v;this.setupImage()}});
this.invert=this.getAttr("invert",0);
this._colors=this.getAttr("colors",opt.switchColors); Object.defineProperty(this,"colors",{get:()=>{return this._colors},set:(v)=>{this._colors=v;this.setupImage()}});
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.style.height=h+"px";
this.elem.style.outline=this.outline?"":"none";
this.redraw();
}
redraw() {
let style = this.elem.style;
if(this.value^this.invert)
style.backgroundPosition = "0px -100%";
else
style.backgroundPosition = "0px 0px";
}
setValue(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);
}
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;
position:relative;
vertical-align:baseline;
}
.webaudio-param-body{
display:inline-block;
position:relative;
text-align:center;
border:1px solid #888;
background:none;
border-radius:4px;
margin:0;
padding:0;
font-family:sans-serif;
font-size:11px;
vertical-align:bottom;
}
</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); Object.defineProperty(this,"value",{get:()=>{return this._value},set:(v)=>{this._value=v;this.redraw()}});
this.defvalue=this.getAttr("defvalue",0);
this._fontsize=this.getAttr("fontsize",9); Object.defineProperty(this,"fontsize",{get:()=>{return this._fontsize},set:(v)=>{this._fontsize=v;this.setupImage()}});
this._src=this.getAttr("src",null); Object.defineProperty(this,"src",{get:()=>{return this._src},set:(v)=>{this._src=v;this.setupImage()}});
this.link=this.getAttr("link","");
this._width=this.getAttr("width",32); Object.defineProperty(this,"width",{get:()=>{return this._width},set:(v)=>{this._width=v;this.setupImage()}});
this._height=this.getAttr("height",20); Object.defineProperty(this,"height",{get:()=>{return this._height},set:(v)=>{this._height=v;this.setupImage()}});
this._colors=this.getAttr("colors","#fff;#000"); Object.defineProperty(this,"colors",{get:()=>{return this._colors},set:(v)=>{this._colors=v;this.setupImage()}});
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=()=>{
this.value=this.elem.value;
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&&typeof(l.value)!="undefined"){
this.setValue(l.value.toFixed(l.digits));
l.addEventListener("input",(e)=>{this.setValue(l.value.toFixed(l.digits))});
}
this.redraw();
}
redraw() {
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.redraw();
}
});
} catch(error){
console.log("webaudio-param already defined");
}
try{
customElements.define("webaudio-keyboard", class WebAudioKeyboard extends WebAudioControlsWidget {
constructor(){
super();
}
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;
vertical-align:bottom;
}
</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); Object.defineProperty(this,"width",{get:()=>{return this._width},set:(v)=>{this._width=v;this.setupImage()}});
this._height=this.getAttr("height",128); Object.defineProperty(this,"height",{get:()=>{return this._height},set:(v)=>{this._height=v;this.setupImage()}});
this._min=this.getAttr("min",0); Object.defineProperty(this,"min",{get:()=>{return this._min},set:(v)=>{this._min=+v;this.redraw()}});
this._keys=this.getAttr("keys",25); Object.defineProperty(this,"keys",{get:()=>{return this._keys},set:(v)=>{this._keys=+v;this.setupImage()}});
this._colors=this.getAttr("colors","#222;#eee;#ccc;#333;#000;#e88;#c44;#c33;#800"); Object.defineProperty(this,"colors",{get:()=>{return this._colors},set:(v)=>{this._colors=v;this.setupImage()}});
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;
if(window.webAudioControlsMidiManager)
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.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(){
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(ev){
this.cv.focus();
if(this.enable) {
++this.press;
}
let pointermove=(ev)=>{
if(!this.enable)
return;
let r=this.getBoundingClientRect();
let v=[],p;
if(ev.touches)
p=ev.targetTouches;
else if(this.press)
p=[ev];
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>=0&&py<this.height){
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();
}
let pointerup=(ev)=>{
if(this.enable) {
if(ev.touches)
this.press=ev.touches.length;
else
this.press=0;
pointermove(ev);
this.sendevent();
if(this.press==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});
}
this.redraw();
}
this.drag=0;
ev.preventDefault();
}
let preventScroll=(ev)=>{
ev.preventDefault();
}
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});
pointermove(ev);
ev.preventDefault();
ev.stopPropagation();
}
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");
}
try{
customElements.define("webaudio-xypad", class WebAudioXYPad extends WebAudioControlsWidget {
constructor(){
super();
}
connectedCallback(){
let root;
// if(this.attachShadow)
// root=this.attachShadow({mode: 'open'});
// else
root=this;
root.innerHTML=
`<style>
${this.basestyle}
webaudio-xypad{
display:inline-block;
position:relative;
margin:0;
padding:0;
font-family: sans-serif;
font-size: 11px;
cursor:pointer;
}
.webaudio-xypad-body{
display:inline-block;
position:relative;
margin:0;
padding:0;
vertical-align:bottom;
}
.webaudio-xypad-knob{
display:inline-block;
position:absolute;
margin:0;
padding:0;
}
</style>
<div class='webaudio-xypad-body' tabindex='1' touch-action='none'><div class='webaudio-xypad-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); Object.defineProperty(this,"src",{get:()=>{return this._src},set:(v)=>{this._src=v;this.setupImage()}});
this._knobsrc=this.getAttr("knobsrc",opt.sliderKnobsrc); Object.defineProperty(this,"knobsrc",{get:()=>{return this._knobsrc},set:(v)=>{this._knobsrc=v;this.setupImage()}});
this._x=this.getAttr("x",50); Object.defineProperty(this,"x",{get:()=>{return this._x},set:(v)=>{this._x=v;this.redraw()}});
this._y=this.getAttr("y",50); Object.defineProperty(this,"y",{get:()=>{return this._y},set:(v)=>{this._y=v;this.redraw()}});
this.defx=this.getAttr("defx",50);
this.defy=this.getAttr("defy",50);
this._min=this.getAttr("min",0); Object.defineProperty(this,"min",{get:()=>{return this._min},set:(v)=>{this._min=v;this.redraw()}});
this._max=this.getAttr("max",100); Object.defineProperty(this,"max",{get:()=>{return this._max},set:(v)=>{this._max=v;this.redraw()}});
this._step=this.getAttr("step",1); Object.defineProperty(this,"step",{get:()=>{return this._step},set:(v)=>{this._step=v;this.redraw()}});
this._sprites=this.getAttr("sprites",0); Object.defineProperty(this,"sprites",{get:()=>{return this._sprites},set:(v)=>{this._sprites=v;this.setupImage()}});
this._width=this.getAttr("width",128); Object.defineProperty(this,"width",{get:()=>{return this._width},set:(v)=>{this._width=v;this.setupImage()}});
this._height=this.getAttr("height",128); Object.defineProperty(this,"height",{get:()=>{return this._height},set:(v)=>{this._height=v;this.setupImage()}});
this._knobwidth=this.getAttr("knobwidth",28); Object.defineProperty(this,"knobwidth",{get:()=>{return this._knobwidth},set:(v)=>{this._knobwidth=v;this.setupImage()}});
this._knobheight=this.getAttr("knbheight",28); Object.defineProperty(this,"knobheight",{get:()=>{return this._knobheight},set:(v)=>{this._knobheight=v;this.setupImage()}});
this._colors=this.getAttr("colors",opt.sliderColors); Object.defineProperty(this,"colors",{get:()=>{return this._colors},set:(v)=>{this._colors=v;this.setupImage()}});
this.outline=this.getAttr("outline",opt.outline);
this.valuetip=this.getAttr("valuetip",1);
this.tooltip=this.getAttr("tooltip",null);
this.conv=this.getAttr("conv",null);
if(this.conv){
this.convValue={x:eval(this.conv)(this._x),y:eval(this.conv)(this._y)};
}
else
this.convValue={x:this._x,y:this._y};
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)
this.width=256;
if(!this.height)
this.height=256;
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.width*0.15|0);
this.kheight=this.knobheight||(this.height*0.15|0);
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.02;
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() {
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;
style.left=(this.width-this.kwidth)*(this._x-this.min)/(this.max-this.min)+"px"; style.top=(this.height-this.kheight)*(1-(this._y-this.min)/(this.max-this.min))+"px";
this.sensex=0; this.sensey=1;
}
_setX(v){
v=(Math.round((v-this.min)/this.step))*this.step+this.min;
this._x=Math.min(this.max,Math.max(this.min,v));
if(this._x!=this.oldx){
this.oldx=this._x;
if(this.conv){
this.convValue={x:eval(this.conv)(this._x),y:eval(this.conv)(this._y)};
}
else
this.convValue={x:this._x,y:this._y};
this.redraw();
this.showtip(0);
return 1;
}
return 0;
}
_setY(v){
v=(Math.round((v-this.min)/this.step))*this.step+this.min;
this._y=Math.min(this.max,Math.max(this.min,v));
if(this._y!=this.oldy){
this.oldy=this._y;
if(this.conv){
this.convValue={x:eval(this.conv)(this._x),y:eval(this.conv)(this._y)};
}
else
this.convValue={x:this._x,y:this._y};
this.redraw();
this.showtip(0);
return 1;
}
return 0;
}
setX(v,f){
if(this._setX(v)&&f)
this.sendEvent("input"),this.sendEvent("change");
}
setY(v,f){
if(this._setY(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 offsetX = (e.pageX - this.startPosX);
let offsetY = (this.startPosY - e.pageY);
let rc=this.getBoundingClientRect();
this._setX(this.min+(this.max-this.min)*(e.pageX-rc.x-this.kwidth*.5)/(this.width-this.kwidth));
this._setY(this.min+(this.max-this.min)*(1-(e.pageY-rc.y-this.kheight*.5)/(this.height-this.kheight)));
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");
}
pointermove(ev);
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-xypad 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();
}