├── .gitignore ├── extras ├── wm-fireworks │ ├── images │ │ ├── moon.png │ │ ├── background.jpg │ │ └── background.png │ └── wm-fireworks.html ├── wm-nsx1text │ ├── wm-nsx1text.html │ └── js │ │ ├── nsx39.js │ │ └── nsx1.js ├── peppermicro-monaka │ └── peppermicro-monaka.html ├── wm-webmidilink │ └── wm-webmidilink.html ├── wm-pckeyboard │ └── wm-pckeyboard.html └── wm-smfplayer │ └── wm-smfplayer.html ├── package.json ├── index.html ├── xwebmidi.css ├── README.md ├── LICENSE └── xwebmidi.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | bower_components 3 | -------------------------------------------------------------------------------- /extras/wm-fireworks/images/moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryoyakawai/x-webmidi/HEAD/extras/wm-fireworks/images/moon.png -------------------------------------------------------------------------------- /extras/wm-fireworks/images/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryoyakawai/x-webmidi/HEAD/extras/wm-fireworks/images/background.jpg -------------------------------------------------------------------------------- /extras/wm-fireworks/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryoyakawai/x-webmidi/HEAD/extras/wm-fireworks/images/background.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "x-webmidi", 3 | "version": "0.9.0", 4 | "description": "ES Modulees for Web MIDI API ", 5 | "main": "xwebmidi.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/ryoyakawai/x-webmidi.git" 12 | }, 13 | "keywords": [ 14 | "webmidiapi" 15 | ], 16 | "author": "Ryoya KAWAI (https://ryoyakawai.com/)", 17 | "license": "Apache-2.0", 18 | "bugs": { 19 | "url": "https://github.com/ryoyakawai/x-webmidi/issues" 20 | }, 21 | "homepage": "https://github.com/ryoyakawai/x-webmidi#readme" 22 | } 23 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | INPUT: 7 | 10 |
11 | OUTPUT: 12 | 15 | 16 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /extras/wm-nsx1text/wm-nsx1text.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 55 | -------------------------------------------------------------------------------- /xwebmidi.css: -------------------------------------------------------------------------------- 1 | .xwebmidi-select-midi { 2 | font-family:arial; 3 | color:#000; 4 | overflow: hidden; 5 | outline: none; 6 | font-size:15px; /*-webkit-small-control;*/ 7 | -webkit-appearance: button; 8 | -moz-appearance: button; 9 | appearance: button; 10 | -webkit-border-radius: 6px; 11 | -moz-border-radius: 6px; 12 | box-sizing: border-box; 13 | -webkit-box-sizing: border-box; 14 | -moz-box-sizing: border-box; 15 | background:#fff url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGgAAABYCAYAAAAZZrIMAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gwMDjIbBk010QAAASBJREFUeNrt2D0uRGEYhuGHiUShodQolFQq5dQUVqCyATuQsAW2IAobUFrBbGBKBeWUIn6akxAiM86cnw/XlUw/ee9zkpknAQAAAAAAAAAAAAAAAAAA/pSFgr7LTpLrQu/0kmSY5O6/PzD7SV4L/Gx7l98dFhZnKMlXx4XEOZDie2c9xzmSYLqLnuKcOP3svzKvOo5z7uw/M0hy21Gcy8L+evwaS0lGLce5SbLo1PUtJxm3FGdUPQTMaSXJfcNxxlV8GrKWZNJQnIckq07avPUkj3PGmVSxaclmkueacZ6SbDhh+3ZrBtpyuu7sxfhZvFkXcONnj6Yt4MbPApzG+Fm8zwu48bMwHxdw42ehBtWbNHAKAAAAAAAAAAAAAAAo3BvDPY/9X9SQowAAAABJRU5ErkJggg==) no-repeat right center; 16 | background-size: 22px 24px; 17 | cursor: pointer; 18 | vertical-align: middle; 19 | user-select: none; /* CSS3 */ 20 | -moz-user-select: none; /* Firefox */ 21 | -webkit-user-select: none; /* Safari、Chromeなど */ 22 | 23 | padding:5px 30px 5px 5px; 24 | border-radius:2px; 25 | border:0px solid #ddd; 26 | margin:3px 3px; 27 | 28 | max-width: 200px; 29 | } 30 | .xwebmidi-port-active { 31 | animation-name: xwebmidi-animation-port-active; 32 | animation-duration: 0.6s; 33 | } 34 | @keyframes xwebmidi-animation-port-active { 35 | 0% { box-shadow: 0 0 0 rgba(0,0,0,0), 0 0 0 rgba(0,0,0,0); } 36 | 10% { box-shadow: 0 1px 1px rgba(0,0,0,0.2), 0 1px 1px rgba(0,0,0,0.2); } 37 | 45% { box-shadow: 0 0.5px 0.2px rgba(0,0,0,0.025), 0 0.5px 0.2px rgba(0,0,0,0.025); }} 38 | 95% { box-shadow: 0 0 0 rgba(0,0,0,0), 0 0 0 rgba(0,0,0,0);} 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # x-webmidi 2 | ## Live Demo 3 | [http://ryoyakawai.github.io/x-webmidi/](http://ryoyakawai.github.io/x-webmidi/) 4 | 5 | ## What is this? 6 | This is a polymer component, and this is a wrapping Web MIDI API. 7 | 8 | - What is MIDI? -> [MIDI@wikipedia](http://en.wikipedia.org/wiki/MIDI) 9 | - What is Web MIDI API? -> [Web MIDI API@W3C](http://webaudio.github.io/web-midi-api/) 10 | 11 | ## Purpose of using this component 12 | MIDI is well defined protocol. But to use the protocol you must learn 7bit code, such as NoteOn: 9nH, NoteOff: 8nH. 13 | With using this component you do NOT have to learn those 7bit code. And also, you do NOT have to know about method in Web MIDI API. 14 | What you need to understand to use this component are HTML and a few JavaScript, which is eventlistener. 15 | Using this component allow you a quick and an easy MIDI application development. 16 | 17 | ## Supported to send MIDI Message (as is 2014 Dec. 12) 18 | 19 | - NoteOn 20 | - NoteOff 21 | - Program Change 22 | - PitchBend 23 | - Sustain 24 | - Modulation 25 | - AllSoundOff 26 | - ResetAllController 27 | - AllNoteOff 28 | 29 | ## Requirements (as is 2014 Dec 15) 30 | [Google Chrome](http://www.google.co.jp/intl/ja/chrome/browser/) to use this component. 31 | 32 | ## Preparation 33 | ### Get component 34 | #### Clone this repository 35 | 36 | ```shell 37 | $ git clone https://github.com/ryoyakawai/x-webmidi.git; 38 | ``` 39 | 40 | ### How to use xwebmidi.js 41 | 42 | - import xwebmidi.js 43 | - create an instance of xwebmidi.js 44 | - create div element and tell xwebmidi.js that where to add pull down menu input/output device to provide. 45 | - add eventlistener to handle for MIDI input, and send MIDI message against MIDI OUTPUT 46 | 47 | ### include and import 48 | ```html 49 | 50 | 51 | 52 | 53 | 54 | INPUT: 55 | 58 |
59 | OUTPUT: 60 | 63 | 64 | 78 | 79 | 80 | ``` 81 | 82 | Note: 83 | Second parameter of initInput()/initOutput() is `NOT REQURED` parameter. And the parameter for to specifying the class where to update when MIDI message is incoming/outgoing. 84 | 85 | ### MIDI input 86 | #### Basic Demo 87 | - input 88 | - [Code] [https://github.com/ryoyakawai/x-webmidi/blob/master/inputsample.html](https://github.com/ryoyakawai/x-webmidi/blob/gh-pages/inputsample.html) 89 | - [Live Demo] [http://ryoyakawai.github.io/x-webmidi/inputsample.html](http://ryoyakawai.github.io/x-webmidi/inputsample.html) 90 | - input & output 91 | - [Code] [https://github.com/ryoyakawai/x-webmidi/blob/master/inoutsample.html](https://github.com/ryoyakawai/x-webmidi/blob/gh-pages/inoutsample.html) 92 | - [Live Demo] [http://ryoyakawai.github.io/x-webmidi/inpotsample.html](http://ryoyakawai.github.io/x-webmidi/inoutsample.html) 93 | 94 | ### MIDI output 95 | #### Basic Demo 96 | - input 97 | - [Code] [https://github.com/ryoyakawai/x-webmidi/blob/master/outputsample.html](https://github.com/ryoyakawai/x-webmidi/blob/gh-pages/outputsample.html) 98 | - [Live Demo] [http://ryoyakawai.github.io/x-webmidi/outputsample.html](http://ryoyakawai.github.io/x-webmidi/outputsample.html) 99 | 100 | ## License 101 | Apache License, Version 2.0 102 | 103 | 104 | ## Copyright 105 | Copyright (c) 2014 Ryoya Kawai 106 | -------------------------------------------------------------------------------- /extras/peppermicro-monaka/peppermicro-monaka.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /extras/wm-nsx1text/js/nsx39.js: -------------------------------------------------------------------------------- 1 | /* 2 | * nsx39.js v1.0.1 by @ryoyakawai 3 | * Copyright (c) 2014 Yamaha Corporation. All rights reserved. 4 | */ 5 | 6 | var Nsx39=function(){ 7 | this.sysExPrefix=[ 0xF0, 0x43, 0x79, 0x09, 0x11 ]; 8 | this.sysExSuffix=[ 0xF7 ]; 9 | this.devide=64; 10 | this.textMap={ 11 | 1: { 12 | "あ": 0x00, "い": 0x01, "う": 0x02, "え": 0x03, "お": 0x04, "か": 0x05, "き": 0x06, "く": 0x07, 13 | "け": 0x08, "こ": 0x09, "が": 0x0A, "ぎ": 0x0B, "ぐ": 0x0C, "げ": 0x0D, "ご": 0x0E, 14 | "さ": 0x15, "す": 0x17, 15 | "せ": 0x18, "そ": 0x19, "ざ": 0x1A, "ず": 0x1C, "ぜ": 0x1D, "ぞ": 0x1E, 16 | "づ": 0x1C, 17 | "し": 0x20, "じ": 0x25, 18 | "た": 0x29, "て": 0x2C, "と": 0x2D, "だ": 0x2E, 19 | "で": 0x31, "ど": 0x32, "ち": 0x36, 20 | "つ": 0x3C, "な": 0x3F, 21 | "に": 0x40, "ぬ": 0x41, "ね": 0x42, "の": 0x43, "は": 0x47, 22 | "ひ": 0x48, "ふ": 0x49, "へ": 0x4A, "ほ": 0x4B, "ば": 0x4C, "び": 0x4D, "ぶ": 0x4E, "べ": 0x4F, 23 | "ぼ": 0x50, "ぱ": 0x51, "ぴ": 0x52, "ぷ": 0x53, "ぺ": 0x54, "ぽ": 0x55, 24 | "ま": 0x64, "み": 0x65, "む": 0x66, "め": 0x67, 25 | "も": 0x68, "や": 0x6C, "ゆ": 0x6D, "よ": 0x6E, "ら": 0x6F, 26 | "り": 0x70, "る": 0x71, "れ": 0x72, "ろ": 0x73, "わ": 0x77, 27 | "ん": 0x7B, //, "ん": 0x7C, "ん": 0x7D, "ん": 0x7E, "ん": 0x7F 28 | "を": 0x7A 29 | }, 30 | 2: { 31 | "きゃ": 0x0F, 32 | "きゅ": 0x10, "きょ": 0x11, "ぎゃ": 0x12, "ぎゅ": 0x13, "ぎょ": 0x14, 33 | "すぃ": 0x16, 34 | "ずぃ": 0x1B, 35 | "づぇ": 0x1D, "づぉ": 0x1E, "しゃ": 0x1F, 36 | "づぁ": 0x1A, "づぃ": 0x1B, 37 | "しゅ": 0x21, "しぇ": 0x22, "しょ": 0x23, "じゃ": 0x24, 38 | "じゅ": 0x26, "じぇ": 0x27, 39 | "じょ": 0x28, "てぃ": 0x2A, "とぅ": 0x2B, 40 | "でぃ": 0x2F, 41 | "どぅ": 0x30, 42 | "てゅ": 0x33, "でゅ": 0x34, "ちゃ": 0x35, 43 | "ちゅ": 0x37, 44 | "ちぇ": 0x38, "ちょ": 0x39, "つぁ": 0x3A, "つぃ": 0x3B, 45 | "つぇ": 0x3D, "つぉ": 0x3E, 46 | "にゃ": 0x44, "にゅ": 0x45, "にょ": 0x46, 47 | "ひゃ": 0x56, "ひゅ": 0x57, 48 | "ひょ": 0x58, "びゃ": 0x59, "びゅ": 0x5A, "びょ": 0x5B, "ぴゃ": 0x5C, "ぴゅ": 0x5D, "ぴょ": 0x5E, "ふぁ": 0x5F, 49 | "ふぃ": 0x60, "ふゅ": 0x61, "ふぇ": 0x62, "ふぉ": 0x63, 50 | "みゃ": 0x69, "みゅ": 0x6A, "みょ": 0x6B, 51 | "りゃ": 0x74, "りゅ": 0x75, "りょ": 0x76, 52 | "うぃ": 0x78, "うぇ": 0x79, "うぉ": 0x7A 53 | } 54 | }; 55 | // create Phonetic Symbol's RegExp 56 | for(var i=1, rexp=[]; i<=2; i++) { 57 | for(var key in this.textMap[i]) { 58 | rexp.push(key); 59 | } 60 | }; 61 | this.pRegExp=rexp; 62 | }; 63 | 64 | Nsx39.prototype={ 65 | addd0: function(d0) { 66 | this.sysExPrefix.push( d0 ); 67 | }, 68 | deleted0: function() { 69 | this.sysExPrefix.splice(this.sysExPrefix.length-1, 1); 70 | }, 71 | setDevide: function(devide) { 72 | this.devide=devide; 73 | }, 74 | getUpdateSysExByText: function(ls, Idx, chop) { 75 | this.addd0( 0x0A ); 76 | var errCount=0; 77 | var outTmp=[], out=[]; 78 | var devide=this.devide; 79 | outTmp=[]; 80 | outTmp.push(Idx); 81 | 82 | var tmp=ls; 83 | if(chop==true) { 84 | tmp=ls.substr(0, devide); 85 | } 86 | 87 | if(ls.substr(devide-1, 2).length==2 && typeof this.textMap[2][ls.substr(devide-1, 2)]!="undefined") { 88 | tmp=ls.substr(0, devide+1); 89 | } 90 | 91 | var re= new RegExp("^"+tmp); 92 | ls=ls.replace(re, ""); 93 | 94 | for(var j=0; j0) { 74 | outTmp=[]; 75 | 76 | var tmp=ls.substr(0, devide); 77 | if(ls.substr(devide-1, 2).length==2 && typeof this.textMap[2][ls.substr(devide-1, 2)]!="undefined") { 78 | tmp=ls.substr(0, devide+1); 79 | } 80 | 81 | var re= new RegExp("^"+tmp); 82 | ls=ls.replace(re, ""); 83 | 84 | for(var j=0; j 5 | 6 | 168 | 175 | 176 | 306 | -------------------------------------------------------------------------------- /extras/wm-webmidilink/wm-webmidilink.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 91 | 106 | 107 | 302 | -------------------------------------------------------------------------------- /extras/wm-pckeyboard/wm-pckeyboard.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 146 | 195 | 196 | -------------------------------------------------------------------------------- /extras/wm-smfplayer/wm-smfplayer.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /xwebmidi.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | export class xWebMIDI { 4 | constructor() { 5 | this.sysex = false; 6 | this.midiAccess = {}; 7 | this.midi = { inputs: new Array(), outputs: new Array() }; 8 | this.midiAccess = {}; 9 | this.ready = { input: false, output: false }; 10 | this.autoselect = true; 11 | this.autoreselect = true; 12 | this.additionalid = ""; 13 | this.midiAccess = {}; 14 | this.inputIdx = "false"; 15 | this.outputIdx = "false"; 16 | this.pfmNow = 0; 17 | this.pitchBendRange = { min:0, max:16384, center:8192 }; // Apple DLS Synth 18 | this.targetDomId = { input: null, output: null }; 19 | this.activeTimerId = { input: 0, output: 0 }; 20 | this.activeClassName = { input: 'xwebmidi-port-active', output: 'xwebmidi-port-active' }; 21 | 22 | this.itnl2Key = []; 23 | this.key2Itnl = []; 24 | const key = { 25 | "note": ["C", "D", "E", "F", "G", "A", "B"], 26 | "order": ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] 27 | }; 28 | for(let i=24, j=0, number=1; i<=108; i++) { 29 | this.itnl2Key[key["order"][j]+number]=i; 30 | this.key2Itnl[i]=key["order"][j]+number; 31 | j++; 32 | if(j==key["order"].length) { 33 | j=0; number++; 34 | } 35 | } 36 | this.itnl2Key["A0"]=21, this.key2Itnl[21]="A0"; 37 | this.itnl2Key["A#0"]=22, this.key2Itnl[22]="A#0"; 38 | this.itnl2Key["B0"]=23, this.key2Itnl[23]="B0"; 39 | 40 | this.addLinkStyleSheet(); 41 | } 42 | addLinkStyleSheet() { 43 | let head_link = document.head.getElementsByTagName("link"); 44 | let isFound = false; 45 | for(let i in head_link) { 46 | if(head_link[i].rel == "stylesheet") { 47 | isFound = true; 48 | break; 49 | } 50 | } 51 | if(isFound == false) { 52 | let link_rel_css = document.createElement("link"); 53 | link_rel_css.rel = "stylesheet"; 54 | link_rel_css.href = "./xwebmidi.css"; 55 | document.head.appendChild(link_rel_css); 56 | } 57 | } 58 | initializePerformanceNow() { 59 | this.pfmNow = window.performance.now(); 60 | } 61 | convertKey2Itnl(keyno) { 62 | return this.key2Itnl[parseInt(keyno)]; 63 | } 64 | convertItnl2Key(itnl) { 65 | return this.itnl2Key[itnl]; 66 | } 67 | async sleep(ms) { 68 | return new Promise(resolve => setTimeout(resolve, ms)); 69 | } 70 | 71 | 72 | /** 73 | * 74 | * Web MIDI API 75 | * 76 | **/ 77 | async requestMIDIAccess(sysex) { 78 | this.sysex = sysex; 79 | if(typeof sysex === "undefined") { 80 | this.sysex = false; 81 | } 82 | return new Promise( async (resolve, reject) => { 83 | try { 84 | let options = { sysex: this.sysex }; 85 | let access = await navigator.requestMIDIAccess(options); 86 | this.successCallback.bind(this)(access); 87 | resolve(); 88 | } catch(e) { 89 | this.errorCallback(e); 90 | reject(); 91 | } 92 | }); 93 | } 94 | errorCallback(msg) { 95 | console.log("[ERROR] ", msg); 96 | } 97 | successCallback(access) { 98 | this.midiAccess = access; 99 | 100 | let inputIterator = access.inputs.values(); 101 | for(let o=inputIterator.next(); !o.done; o=inputIterator.next()) { 102 | if(this.midi.inputs.length==0) this.midi.inputs = []; 103 | this.midi.inputs.push(o.value); 104 | } 105 | 106 | let outputIterator = access.outputs.values(); 107 | for(let o=outputIterator.next(); !o.done; o=outputIterator.next()) { 108 | if(this.midi.outputs === false) this.midi.outputs=[]; 109 | this.midi.outputs.push(o.value); 110 | } 111 | 112 | access.onstatechange = this._onStateChange.bind(this); 113 | this.ready.input = this.ready.output = true; 114 | } 115 | 116 | /** 117 | * 118 | * for MIDI INPUT and OUTPUT Port 119 | * 120 | **/ 121 | _onStateChange(event) { 122 | let portList = this.midi[event.port.type + "s"]; // this.midi["inputs"/"outputs"] 123 | let port_exists = { status: false, id: 99999999 }; 124 | let exist = false; 125 | let Idx = false; 126 | let i = 0; 127 | // to check whether the informed port's existed in the past 128 | for(let portIdx in portList) { 129 | if(portList[portIdx].id == event.port.id) { 130 | // tagged as OLD due to the port existed in the past 131 | port_exists = { 132 | status: true, 133 | id: portIdx 134 | }; 135 | portList[portIdx] = event.port; 136 | break; 137 | } 138 | } 139 | if(port_exists.status == false) { 140 | let index_max = 0; 141 | for(let key in portList) { 142 | if(key < 99999) { 143 | index_max = key; 144 | } 145 | }; 146 | console.log(index_max); 147 | portList[index_max] = event.port; 148 | port_exists.id = index_max; 149 | } 150 | 151 | this.midi[event.port.type + "s"] = portList; 152 | 153 | let value_detail = {idx: port_exists.id, member: (port_exists.status == true ? 'old' : 'new' ), port: event.port}; 154 | switch(event.port.type){ 155 | case 'input': 156 | this.inputUpdated({detail: value_detail}); 157 | break; 158 | case 'output': 159 | this.outputUpdated({detail: value_detail}); 160 | break; 161 | } 162 | } 163 | setSoftwareOutputDeveice(additionalDevice, additionalDeviceName, elemId, exSelected, autoSelect) { 164 | let _additionalDevice = additionalDevice; 165 | _additionalDevice.name = additionalDeviceName; 166 | return this.addAdditionalDevice('output', _additionalDevice, elemId, exSelected, autoSelect); 167 | } 168 | addAdditionalDevice(midiType, additionalDevice, elemId, exSelected, autoSelect) { 169 | let result = { autoselected: false, idx: false }; 170 | let idx = 99999; 171 | switch(midiType) { 172 | case 'input': 173 | this.midi.inputs[idx] = additionalDevice; 174 | result = this.addOptions.bind(this)(midiType, 'add', { idx: idx, port: additionalDevice }, elemId, exSelected, autoSelect); 175 | break; 176 | case 'output': 177 | this.midi.outputs[idx] = additionalDevice; 178 | result = this.addOptions.bind(this)(midiType, 'add', { idx: idx, port: additionalDevice }, elemId, exSelected, autoSelect); 179 | break; 180 | } 181 | return result; 182 | } 183 | addOptions(midiType, updateType, detail, selectElemId, exSelected, autoSelect) { 184 | let elem = document.getElementById(selectElemId); 185 | let ports, result = { autoselected: false, idx:false }; 186 | let addIdx = detail.idx; 187 | let err = new Error(); 188 | 189 | switch(midiType) { 190 | case 'input': 191 | ports = this.midi.inputs; 192 | break; 193 | case 'output': 194 | ports = this.midi.outputs; 195 | break; 196 | } 197 | let out; 198 | switch(updateType) { 199 | case 'add': 200 | if(addIdx == 'all') { 201 | if (midiType == "input") { } 202 | let i = 0; 203 | for(let idx in ports) { 204 | out = this._appendOption.bind(this)(ports[idx].name, i, selectElemId, autoSelect); 205 | if(out.autoselected === true) result = out; 206 | i++; 207 | } 208 | } else if(typeof addIdx == 'number'){ 209 | result = this._appendOption.bind(this)(detail.port.name, addIdx, selectElemId, autoSelect); 210 | } 211 | break; 212 | case 'update': 213 | result = this._updateOption.bind(this)(ports[addIdx].state, addIdx, selectElemId, exSelected); 214 | break; 215 | } 216 | return result; 217 | } 218 | // only used by addOptions() 219 | _appendOption(name, idx, selectElemId, autoSelect) { 220 | let selectElem = document.getElementById(selectElemId); 221 | let ret = false; 222 | if(selectElem == null) { 223 | console.log('[INPUT] Not set inputport.'); 224 | } else { 225 | selectElem.appendChild((new Option(name, idx))); 226 | let autoselect = false, selectedIdx = false; 227 | if(name.match(new RegExp(autoSelect)) != null) { 228 | let op_idx=""; 229 | for(let i in selectElem.options) { 230 | if(parseInt(selectElem.options[i].value, 10) == parseInt(idx, 10)) { 231 | op_idx = i; 232 | idx = selectElem.options[i].value; 233 | break; 234 | } 235 | } 236 | selectElem.selectedIndex = op_idx; 237 | autoselect = true; selectedIdx = idx; 238 | }; 239 | ret = { autoselected: autoselect, idx: selectedIdx }; 240 | } 241 | return ret; 242 | } 243 | // only used by addOptions() 244 | _updateOption(updateType, idx, selectElemId, exSelected) { 245 | let selectElem = document.getElementById(selectElemId); 246 | let ret = false; 247 | if(selectElem == null) { 248 | console.log('[INPUT] Not set inputport.'); 249 | } else { 250 | let autoselect = false, selectedIdx = false; 251 | let i = 0; 252 | for(let tmp_op_idx in selectElem.options) { 253 | if(selectElem.options[tmp_op_idx].value==idx) { 254 | if(updateType=="disconnected") { 255 | selectElem.options[tmp_op_idx].setAttribute("hidden", "hidden"); 256 | if(selectElem.selectedIndex==tmp_op_idx) { 257 | selectElem.selectedIndex=0; 258 | } 259 | } else if(updateType=="connected") { 260 | selectElem.options[tmp_op_idx].removeAttribute("hidden"); 261 | if(exSelected == true) { 262 | selectElem.selectedIndex=i; 263 | autoselect = true; selectedIdx=selectElem.options[tmp_op_idx].value; 264 | } 265 | } 266 | } 267 | i++; 268 | } 269 | ret = { autoselected: autoselect, idx: selectedIdx }; 270 | } 271 | return ret; 272 | } 273 | 274 | 275 | /** 276 | * 277 | * for MIDI INPUT Port 278 | * 279 | **/ 280 | initInput(elemId='', activeClassName) { 281 | if(elemId == '') { 282 | console.error('[ERROR] initInput(elemId, activeClassName) elemId is required to specify to init.'); 283 | return; 284 | } 285 | if(document.getElementById(elemId) == null) { 286 | console.error('[ERROR] initInput(elemId, activeClassName) :: Specified elemId is not defined in the DOM'); 287 | return; 288 | } 289 | 290 | this.targetDomId.input = elemId; 291 | if(typeof activeClassName!= "undefined") this.activeClassName.input = activeClassName; 292 | 293 | document.getElementById(elemId).addEventListener("change", function(event){ 294 | this.setMIDIINDevice.bind(this)(event.target); 295 | }.bind(this)); 296 | 297 | let result = { autoselected: false, idx: false }; 298 | let mididom = document.getElementById(elemId); 299 | 300 | // add 'not selected' option automatically 301 | let options = mididom.getElementsByTagName("option"); 302 | let not_selected_find = false; 303 | for(let i=0; i { 380 | target.classList.remove(this.activeClassName.input); 381 | this.activeTimerId.input = 0; 382 | }, 600); 383 | } 384 | onMIDIMessage(event){ 385 | let d16 = []; 386 | let p_d16 = this.parseMIDIMessage(event.data); 387 | p_d16.device = this.midi.inputs[this.inputIdx]; 388 | p_d16.device.Idx = this.inputIdx; 389 | for(let i=0; i { 512 | target.classList.remove(this.activeClassName.output); 513 | this.activeTimerId.output = 0; 514 | }, 1000); 515 | } 516 | sendRawMessage2(msg, time) { 517 | if(this.checkOutputIdx()=="false") { 518 | return; 519 | } 520 | if(typeof time === "undefined") { 521 | time = 0; 522 | } 523 | //console.log(msg, time); 524 | this.midi.outputs[this.outputIdx].send(msg, time); 525 | // indicator 526 | this.updateOutputIndicator.bind(this)(); 527 | } 528 | sendRawMessage(msg, timestamp) { 529 | if(this.checkOutputIdx()=="false") { 530 | return; 531 | } 532 | if(typeof timestamp==="undefined") { 533 | timestamp = 0; 534 | } 535 | this.initializePerformanceNow(); 536 | let sendTimestamp = this.pfmNow + timestamp; 537 | this.midi.outputs[this.outputIdx].send(msg, sendTimestamp); 538 | // indicator 539 | this.updateOutputIndicator.bind(this)(); 540 | } 541 | sendHRMessage(type, ch, param, timestamp) { //hex format 542 | if(this.checkOutputIdx()=="false") { 543 | return; 544 | } 545 | let msg = false; 546 | let key = false; 547 | if(typeof timestamp==="undefined") timestamp=0; 548 | if(ch>=10) ch=ch.toString(16); 549 | type=type.toLowerCase(); 550 | switch(type) { 551 | case "noteon": 552 | if(typeof param!="object") { 553 | console.log("[noteon: Parameter Error:param must be object] "+param); 554 | return; 555 | } 556 | if(typeof param[0]=="string") { 557 | param[0]=this.convertItnl2Key(param[0].toUpperCase()); 558 | } 559 | key = param[0]; 560 | if(param[0]!=parseInt(param[0])) { 561 | key = this.convertItnl2Key(param[0].toUpperCase()); 562 | } 563 | //msg=["0x9"+ch, param[0], param[1]]; 564 | msg = ["0x9"+ch, key, param[1]]; 565 | break; 566 | case "noteoff": 567 | if(typeof param!="object") { 568 | console.log("[noteoff: Parameter Error:param must be object] "+param); 569 | return; 570 | } 571 | if(typeof param[0]=="string") { 572 | param[0]=this.convertItnl2Key(param[0].toUpperCase()); 573 | } 574 | key = param[0]; 575 | if(param[0]!=parseInt(param[0])) { 576 | key = this.convertItnl2Key(param[0].toUpperCase()); 577 | } 578 | msg = ["0x8"+ch, key, param[1]]; 579 | break; 580 | case "programchange": 581 | msg = ["0xc"+ch, param]; 582 | break; 583 | case "setpitchbendrange": 584 | if(typeof param!="object") { 585 | console.log("[setpitchbendvalue: Parameter Error:param must be object] "+param); 586 | return; 587 | } 588 | msg = false; 589 | this.pitchBendRange={"min":param[0], "max":param[1], "center":(param[0]+param[1]+1)/2}; 590 | break; 591 | case "pitchbend": 592 | if(typeof param!="number") { 593 | console.log("[pitchbend: Parameter Error:param must be object] "+param); 594 | return; 595 | } 596 | let value = param < this.pitchBendRange.min ? this.pitchBendRange.min : param > this.pitchBendRange.max ? this.pitchBendRange.max : param; 597 | let msb=(~~(value/128)); 598 | let lsb=(value%128); 599 | msg=["0xe"+ch, lsb, msb]; 600 | break; 601 | case "sustain": 602 | msg=["0xb"+ch, 0x40, 0x00]; 603 | switch(param) { 604 | case "on": 605 | msg=["0xb"+ch, 0x40, 0x7f]; 606 | break; 607 | } 608 | break; 609 | case "modulation": 610 | if(typeof param!="number") { 611 | console.log("[Parameter Error:param must be number] "+param); 612 | return; 613 | } 614 | value = param < 0 ? 0 : param > 127 ? 127 : param; 615 | msg=["0xb"+ch, 0x01, value]; 616 | break; 617 | case "allsoundoff": 618 | msg=[ "0xb"+ch, 0x78, 0x00 ]; 619 | break; 620 | case "resetallcontroller": 621 | msg=[ "0xb"+ch, 0x79, 0x00 ]; 622 | break; 623 | case "allnoteoff": 624 | msg=[ "0xb"+ch, 0x7b, 0x00 ]; 625 | break; 626 | } 627 | // send message 628 | if(msg!=false) { 629 | this.initializePerformanceNow(); 630 | let sendTimestamp=this.pfmNow+timestamp; 631 | this.midi.outputs[this.outputIdx].send(msg, sendTimestamp); 632 | // indicator 633 | this.updateOutputIndicator.bind(this)(); 634 | } 635 | } 636 | parseMIDIMessage(msg) { 637 | let event = { }; 638 | let out = { }; 639 | if(typeof msg !== "object") { 640 | event.type = "notObject"; 641 | event.subType = "unkown"; 642 | event.data = event.raw; 643 | event.property = event; 644 | } else { 645 | let msg16 = new Array(); 646 | for(let i = 0; i=0x40) event.ctrlStatus = "On"; 831 | break; 832 | case 0x41: 833 | case "0x41": 834 | event.ctrlName = "Portament"; 835 | event.ctrlStatus = "Off"; 836 | if(event.value>=0x40) event.ctrlStatus = "On"; 837 | break; 838 | case 0x42: 839 | case "0x42": 840 | event.ctrlName = "SosTenuto"; 841 | event.ctrlStatus = "Off"; 842 | if(event.value>=0x40) event.ctrlStatus = "On"; 843 | break; 844 | case 0x43: 845 | case "0x43": 846 | event.ctrlName = "SoftPedal"; 847 | event.ctrlStatus = "Off"; 848 | if(event.value>=0x40) event.ctrlStatus = "On"; 849 | break; 850 | case 0x46: 851 | case "0x46": 852 | event.ctrlName = "SoundController1"; 853 | break; 854 | case 0x47: 855 | case "0x47": 856 | event.ctrlName = "SoundController2"; 857 | break; 858 | case 0x48: 859 | case "0x48": 860 | event.ctrlName = "SoundController3"; 861 | break; 862 | case 0x49: 863 | case "0x49": 864 | event.ctrlName = "SoundController4"; 865 | break; 866 | case 0x50: 867 | case "0x50": 868 | event.ctrlName = "SoundController5"; 869 | break; 870 | case 0x5b: 871 | case "0x5b": 872 | event.ctrlName = "effectSendLevel1"; // SendLevel: Reberb 873 | break; 874 | case 0x5d: 875 | case "0x5d": 876 | event.ctrlName = "effectSendLevel3"; // SendLevel: Chrus 877 | break; 878 | case 0x5e: 879 | case "0x5e": 880 | event.ctrlName = "effectSendLevel4"; // [XG] ValiationEffect, [SC-88] SendLevel: Delay 881 | break; 882 | case 0x60: 883 | case "0x60": 884 | event.ctrlName = "DataIncrement"; 885 | break; 886 | case 0x61: 887 | case "0x61": 888 | event.ctrlName = "DataDecrement"; 889 | break; 890 | case 0x62: 891 | case "0x62": 892 | event.ctrlName = "NRPN"; 893 | event.valueType = "LSB"; 894 | break; 895 | case 0x63: 896 | case "0x63": 897 | event.ctrlName = "NRPN"; 898 | event.valueType = "MSB"; 899 | break; 900 | case 0x64: 901 | case "0x64": 902 | event.ctrlName = "RPN"; 903 | event.valueType = "LSB"; 904 | break; 905 | case 0x65: 906 | case "0x65": 907 | event.ctrlName = "RPN"; 908 | event.valueType = "MSB"; 909 | break; 910 | case 0x78: 911 | case "0x78": 912 | event.ctrlName = "AllSoundOff"; 913 | break; 914 | case 0x79: 915 | case "0x79": 916 | event.ctrlName = "ResetAllController"; 917 | break; 918 | case 0x7b: 919 | case "0x7b": 920 | event.ctrlName = "OmniOff"; 921 | break; 922 | case 0x7c: 923 | case "0x7c": 924 | event.ctrlName = "OmniOn"; 925 | break; 926 | case 0x7e: 927 | case "0x7e": 928 | event.ctrlName = "Mono"; 929 | break; 930 | case 0x7f: 931 | case "0x7f": 932 | event.ctrlName = "Poly"; 933 | break; 934 | default: 935 | event.ctrlName = "NotDefined"; 936 | break; 937 | } 938 | break; 939 | case "c": 940 | event.subType = 'programChange'; 941 | event.programNumber = msg[1]; 942 | break; 943 | case "d": 944 | event.subType = 'channelAftertouch'; 945 | event.amount = msg[1]; 946 | break; 947 | case "e": 948 | event.subType = 'pitchBend'; 949 | let msb = msg[2], lsb = msg[1]; 950 | if( (msg[2]>>6).toString(2) == "1" ) { 951 | event.value = -1*(((msb-64)<<7) + lsb +1) ; 952 | } else { 953 | let bsMsb = msb<<7; 954 | event.value = bsMsb + lsb; 955 | } 956 | break; 957 | default: 958 | event.subType = "unknown"; 959 | break; 960 | } 961 | } 962 | } 963 | out = { 964 | type: event.type, 965 | subType: event.subType, 966 | data : event.raw, 967 | property: event 968 | }; 969 | return out; 970 | } 971 | 972 | } 973 | --------------------------------------------------------------------------------