PageRenderTime 50ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/examples/lib/MIDIBridge.js

http://github.com/abudaan/MIDIBridge
JavaScript | 678 lines | 543 code | 76 blank | 59 comment | 75 complexity | 47e9d5da9e70a7439e90bc200a040c92 MD5 | raw file
  1. /*
  2. * copyright 2012 abudaan
  3. * code licensed under MIT
  4. * http://abumarkub.net/midibridge/license
  5. *
  6. *
  7. * This version is supported by:
  8. * - Firefox 3.5+ Linux | OSX | Windows
  9. * - Chrome 4.0+ Linux | OSX | Windows
  10. * - Safari 4.0+ --- | OSX | ---
  11. * - Opera 10.5+ Linux | OSX | Windows
  12. * - Internet Explorer 8.0+
  13. *
  14. * Safari/Windows is not supported because it does not fully support Live Connect
  15. *
  16. * Note for IE8 users: if you include MidiBridge.js (or preferably the minified version of it: midibridge-latest.min.js) in your html,
  17. * the method addEventListener will be added to the window object. In fact this method is just a wrapper around the attachEvent method,
  18. * see code at the bottom of this file.
  19. *
  20. */
  21. (function() {
  22. try {
  23. console.log("");
  24. } catch (e) {
  25. console = {
  26. 'log': function(args) {}
  27. };
  28. }
  29. var midiBridge = {
  30. //all MIDI commands
  31. NOTE_OFF : 0x80, //128
  32. NOTE_ON : 0x90, //144
  33. POLY_PRESSURE : 0xA0, //160
  34. CONTROL_CHANGE : 0xB0, //176
  35. PROGRAM_CHANGE : 0xC0, //192
  36. CHANNEL_PRESSURE : 0xD0, //208
  37. PITCH_BEND : 0xE0, //224
  38. SYSTEM_EXCLUSIVE : 0xF0, //240
  39. MIDI_TIMECODE : 241,
  40. SONG_POSITION : 242,
  41. SONG_SELECT : 243,
  42. TUNE_REQUEST : 246,
  43. EOX : 247,
  44. TIMING_CLOCK : 248,
  45. START : 250,
  46. CONTINUE : 251,
  47. STOP : 252,
  48. ACTIVE_SENSING : 254,
  49. SYSTEM_RESET : 255,
  50. //other statics
  51. NOTE_NAMES_SHARP : "sharp",
  52. NOTE_NAMES_FLAT : "flat",
  53. NOTE_NAMES_SOUNDFONT : "soundfont",
  54. NOTE_NAMES_ENHARMONIC_SHARP : "enh-sharp",
  55. NOTE_NAMES_ENHARMONIC_FLAT : "enh-flat",
  56. //rest
  57. version : "0.6.3",
  58. noteNameModus : "sharp",
  59. userAgent : ""
  60. },
  61. filterCommands = null,//these are the command codes that get filtered; the midibridge will not pass them on to your application
  62. midiCommands = [],//all existing command codes
  63. noteNames = {},
  64. javaDir = "lib",//directory of the applet, relative to the directory of the html file
  65. debug = false,
  66. onReady = null,
  67. onError = null,
  68. onSequencerMIDIData = null,
  69. onSequencerMetaData = null,
  70. midiInputListeners = {},
  71. midiBridgeJar = "midiapplet-" + midiBridge.version + ".jar",
  72. applet = null,
  73. MIDIAccess = null,
  74. Sequencer = null,
  75. sequencerJs = null,
  76. ua = navigator.userAgent.toLowerCase(),
  77. userAgent;
  78. if(ua.indexOf("firefox") !== -1){
  79. userAgent = "firefox";
  80. }else if(ua.indexOf("chrome") !== -1){
  81. userAgent = "chrome";
  82. }else if(ua.indexOf("safari") !== -1){
  83. userAgent = "safari";
  84. }else if(ua.indexOf("opera") !== -1){
  85. userAgent = "opera";
  86. }else if(ua.indexOf("msie 7") !== -1){
  87. userAgent = "msie7";
  88. }else if(ua.indexOf("msie 8") !== -1){
  89. userAgent = "msie8";
  90. }else if(ua.indexOf("msie 9") !== -1){
  91. userAgent = "msie9";
  92. }
  93. if(ua.indexOf("linux") !== -1){
  94. userAgent += "/linux";
  95. }else if(ua.indexOf("macintosh") !== -1){
  96. userAgent += "/osx";
  97. }else if(ua.indexOf("windows") !== -1){
  98. userAgent += "/win";
  99. }
  100. midiBridge.userAgent = userAgent;
  101. //console.log(ua," => ",userAgent);
  102. //human readable representation of command byte in MIDI data
  103. midiCommands[128] = "NOTE OFF";
  104. midiCommands[144] = "NOTE ON";
  105. midiCommands[160] = "POLY PRESSURE";//POLYPHONIC AFTERTOUCH
  106. midiCommands[176] = "CONTROL CHANGE";
  107. midiCommands[192] = "PROGRAM CHANGE";
  108. midiCommands[208] = "CHANNEL PRESSURE";//AFTERTOUCH
  109. midiCommands[224] = "PITCH BEND";
  110. midiCommands[240] = "SYSTEM EXCLUSIVE";
  111. midiCommands[241] = "MIDI TIMECODE";
  112. midiCommands[242] = "SONG POSITION";
  113. midiCommands[243] = "SONG SELECT";
  114. midiCommands[244] = "RESERVED 1";
  115. midiCommands[245] = "RESERVED 2";
  116. midiCommands[246] = "TUNE REQUEST";
  117. midiCommands[247] = "EOX";
  118. midiCommands[248] = "TIMING CLOCK";
  119. midiCommands[249] = "RESERVED 3";
  120. midiCommands[250] = "START";
  121. midiCommands[251] = "CONTINUE";
  122. midiCommands[252] = "STOP";
  123. midiCommands[254] = "ACTIVE SENSING";
  124. midiCommands[255] = "SYSTEM RESET";
  125. //notenames in different modi
  126. noteNames = {
  127. "sharp" : ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"],
  128. "flat" : ["C", "D♭", "D", "E♭", "E", "F", "G♭", "G", "A♭", "A", "B♭", "B"],
  129. "soundfont" : ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"],
  130. "enh-sharp" : ["B#", "C#", "C##", "D#", "D##", "E#", "F#", "F##", "G#", "G##", "A#", "A##"],
  131. "enh-flat" : ["D♭♭", "D♭", "E♭♭", "E♭", "F♭", "G♭♭", "G♭", "A♭♭", "A♭", "B♭♭", "B♭", "C♭"]
  132. };
  133. /**
  134. * static method called to initialize the MidiBridge
  135. * possible arguments:
  136. * 1) callback [function] callback when the MidiBridge is ready
  137. * 2) configuration object
  138. * - onError : [function] callback in case of an error
  139. * - debug : [true,false] midiBridge prints out error messages
  140. * - javaDir : [string] the folder where you store the midiapplet.jar on your webserver, defaults to "java"
  141. * - passCommands : [array] an array that contains MIDI commands that are send to the application
  142. * - filterCommands : [array] an array that contains MIDI commands that are *not* send to the application
  143. */
  144. midiBridge.init = function () {
  145. function browserNotSupported(browser,alternatives){
  146. document.body.style.color = "#f00";
  147. document.body.style.fontSize = "20px";
  148. document.body.style.padding = "50px";
  149. document.body.style.lineHeight = "1.4em";
  150. document.body.innerHTML = browser + " is not supported.<br/>please use " + alternatives;
  151. }
  152. if(userAgent === "safari/win"){
  153. browserNotSupported("Safari for Windows", "Internet Explorer 8+, Chrome, Firefox or Opera");
  154. return;
  155. }else if(userAgent === "msie7/win"){
  156. browserNotSupported("Internet Explorer 7", "Internet Explorer 8+, Chrome, Firefox or Opera");
  157. return;
  158. }
  159. var args = Array.prototype.slice.call(arguments);
  160. if (args.length === 1 && typeof args[0] === "function") {
  161. onReady = args[0];
  162. } else if (args.length === 2 && typeof args[0] === "object" && typeof args[1] === "function") {
  163. var config = args[0],
  164. i,maxi,command1,command2;
  165. onReady = args[1];
  166. onError = config.onError;
  167. debug = config.debug;
  168. javaDir = config.javaDir || javaDir;
  169. function setCommandFilter(commands,filter){
  170. filterCommands = {};
  171. for(command1 in midiCommands){
  172. var filterCommand = !filter;
  173. for(i = 0, maxi = commands.length; i < maxi; i++){
  174. command2 = commands[i];
  175. if(command2 == command1){
  176. filterCommand = filter;
  177. break;
  178. }
  179. }
  180. filterCommands[command1] = filterCommand;
  181. }
  182. }
  183. if(config.filterCommands){
  184. setCommandFilter(config.filterCommands,true);
  185. }else if(config.passCommands){
  186. setCommandFilter(config.passCommands,false);
  187. }
  188. if(debug){
  189. console.log(filterCommands,userAgent);
  190. }
  191. }
  192. //very simple java plugin detection
  193. //console.log(navigator.javaEnabled())
  194. if (!navigator.javaEnabled()) {
  195. if (onError) {
  196. onError("No java plugin found; install or enable the java plugin");
  197. } else {
  198. //console.log("no java plugin found; install or enable the java plugin");
  199. document.body.style.color = "#f00";
  200. document.body.style.fontSize = "20px";
  201. document.body.style.padding = "50px";
  202. document.body.innerHTML = "No java plugin found; install or enable the java plugin";
  203. }
  204. return;
  205. }
  206. loadJava();
  207. };
  208. //called by the applet in case of error (i.e. the user doesn't have the right version of the Java plugin)
  209. midiBridge.error = function(message){
  210. if(onError){
  211. onError(message);
  212. }else if(debug){
  213. console.log(message);
  214. }
  215. }
  216. //called by the applet when the applet is initialized
  217. midiBridge.ready = function(){
  218. var timeout = 25;
  219. (function getApplet(callback){
  220. if(debug){
  221. console.log("applet:",applet === null);
  222. }
  223. try {
  224. applet = getObject("midibridge-applet");
  225. applet.ready();
  226. } catch(e) {
  227. if(debug){
  228. console.log(e)
  229. }
  230. setTimeout(function(){
  231. getApplet(callback);
  232. },timeout);
  233. return;
  234. }
  235. if(!applet){
  236. if(debug){
  237. console.log(applet);
  238. }
  239. setTimeout(function(){
  240. getApplet(callback);
  241. },timeout);
  242. }else{
  243. callback();
  244. }
  245. })(wrapJavaObjects);
  246. }
  247. function wrapJavaObjects(){
  248. MIDIAccess = applet.getMIDIAccess();
  249. Sequencer = applet.getSequencer();
  250. //wrap the Java Sequencer object
  251. var stripBase64Header = function(data){
  252. return data.replace(/data:audio\/mid[i]?;base64,/,"");
  253. }
  254. sequencerJs = {
  255. addEventListener:function(eventId,callback,context){
  256. if(context){//send events via AppletContext
  257. Sequencer.addEventListener(eventId);
  258. if(eventId === "midimessage"){
  259. onSequencerMIDIData = callback;
  260. }else if(eventId === "metamessage"){
  261. onSequencerMetaData = callback;
  262. }
  263. }else{//send events via Live Connect
  264. Sequencer.addEventListener(eventId,{
  265. listener:callback
  266. });
  267. }
  268. },
  269. stop:function(){
  270. Sequencer.stop();
  271. },
  272. play:function(){
  273. Sequencer.play();
  274. },
  275. pause:function(){
  276. Sequencer.pause();
  277. },
  278. isPlaying:function(){
  279. return Sequencer.isRunning();
  280. },
  281. loadBase64String:function(data){
  282. return Sequencer.loadBase64String(stripBase64Header(data));
  283. },
  284. playBase64String:function(data){
  285. return Sequencer.playBase64String(stripBase64Header(data));
  286. },
  287. getMicrosecondPosition:function(){
  288. return Sequencer.getMicrosecondPosition();
  289. },
  290. setMicrosecondPosition:function(microseconds){
  291. Sequencer.setMicrosecondPosition(microseconds);
  292. },
  293. setTempoInBPM:function(bpm){
  294. Sequencer.setTempoInBPM(bpm);
  295. },
  296. getTempoFactor:function(){
  297. return Sequencer.getTempoFactor();
  298. },
  299. setTempoFactor:function(factor){
  300. Sequencer.setTempoFactor(factor);
  301. },
  302. getTempoInBPM:function(){
  303. return Sequencer.getTempoInBPM();
  304. },
  305. getTracks:function(){
  306. return Sequencer.getTracks();
  307. },
  308. muteTrack:function(index){
  309. return Sequencer.muteTrack(index);
  310. },
  311. unmuteTrack:function(index){
  312. return Sequencer.unmuteTrack(index);
  313. },
  314. getSequence:function(){
  315. return Sequencer.getSequence();
  316. },
  317. setDirectOutput:function(output){
  318. return Sequencer.setDirectOutput(output.getDevice());
  319. },
  320. removeDirectOutput:function(){
  321. Sequencer.removeDirectOutput();
  322. },
  323. hasDirectOutput:function(){
  324. return Sequencer.hasDirectOutput();
  325. }
  326. };
  327. //wrap the Java MIDI input and output devices
  328. var wrapDevice = function(device){
  329. if(!device){
  330. if(debug){
  331. console.log("device does not exist");
  332. return null;
  333. }
  334. return null;
  335. }
  336. try{
  337. if(device.deviceType == "input"){
  338. device = MIDIAccess.getInput(device);
  339. }else if(device.deviceType == "output"){
  340. device = MIDIAccess.getOutput(device);
  341. }else{
  342. if(debug){
  343. console.log("error while getting device",device.deviceName);
  344. return null;
  345. }
  346. return null;
  347. }
  348. }catch(e){
  349. if(debug){
  350. console.log("error while getting device",device.deviceName);
  351. return null;
  352. }
  353. return null;
  354. }
  355. return {
  356. close:function(){
  357. device.close();
  358. },
  359. open:function(){
  360. if(device.open()){
  361. return true;
  362. }else{
  363. if(debug){
  364. console.log("could not open device", device.deviceName);
  365. }
  366. return false;
  367. }
  368. },
  369. addEventListener:function(eventId,callback,context){
  370. if(context){//send events via AppletContext
  371. //passing events from Java to Javascript is faster with AppletContext than with Live Connect!
  372. device.addEventListener(eventId);
  373. midiInputListeners[device.id] = function(e){
  374. console.log("applet context");
  375. if(filterCommands && filterCommands[e.command]){
  376. if(debug){
  377. console.log("MIDI message intercepted", e.command, e.channel, e.data1, e.data2);
  378. }
  379. }else{
  380. callback(e);
  381. }
  382. }
  383. }else{//send events via Live Connect
  384. device.addEventListener(eventId,{
  385. listener:function(e){
  386. if(filterCommands && filterCommands[e.command]){
  387. if(debug){
  388. console.log("MIDI message intercepted", e.command, e.channel, e.data1, e.data2);
  389. }
  390. }else{
  391. callback(e);
  392. }
  393. }
  394. });
  395. }
  396. },
  397. sendMIDIMessage:function(e){
  398. if(device.getDevice().isOpen()){
  399. //@todo:make this dependent on what send method is selected (AppletContext or Live Connect)
  400. //device.sendMIDIMessage(MIDIAccess.createMIDIMessage(e.command,e.channel,e.data1,e.data2,e.timeStamp));
  401. //device.sendMIDIMessage(e.command,e.channel,e.data1,e.data2,e.timeStamp);
  402. device.sendMIDIMessage(e);
  403. }
  404. },
  405. toString:function(){
  406. return device.toString();
  407. },
  408. getDevice:function(){
  409. return device;
  410. },
  411. setDirectOutput:function(output){
  412. return device.setDirectOutput(output.getDevice());
  413. },
  414. removeDirectOutput:function(){
  415. device.removeDirectOutput();
  416. },
  417. hasDirectOutput:function(){
  418. return device.hasDirectOutput();
  419. },
  420. deviceType:device.deviceType,
  421. deviceName:device.deviceName,
  422. deviceManufacturer:device.deviceManufacturer,
  423. deviceDescription:device.deviceDescription
  424. }
  425. };
  426. //wrap the Java MIDIAccess object and pass it to the callback of midiBridge.init();
  427. onReady({
  428. enumerateInputs:function(){
  429. return MIDIAccess.enumerateInputs();
  430. },
  431. enumerateOutputs:function(){
  432. return MIDIAccess.enumerateOutputs();
  433. },
  434. getInput:function(input){
  435. return wrapDevice(input);
  436. },
  437. getOutput:function(output){
  438. return wrapDevice(output);
  439. },
  440. closeInputs:function(){
  441. MIDIAccess.closeInputs();
  442. },
  443. closeOutputs:function(){
  444. MIDIAccess.closeOutputs();
  445. },
  446. createMIDIMessage : function(command,channel,data1,data2,timeStamp){
  447. timeStamp = timeStamp || -1;
  448. //var MIDIMessage = java.lang.Thread.currentThread().getContextClassLoader().loadClass("net.abumarkub.midi.MIDIMessage");
  449. return MIDIAccess.createMIDIMessage(command,channel,data1,data2,timeStamp);
  450. }
  451. });
  452. }
  453. midiBridge.getSequencer = function(){
  454. if(!sequencerJs){
  455. if(debug){
  456. console.log("Sequencer not (yet) available");
  457. return null;
  458. }
  459. return null;
  460. }
  461. return sequencerJs;
  462. }
  463. //if data is sent via AppletContext it arrives here
  464. midiBridge.onMIDIData = function(){
  465. var args = Array.prototype.slice.call(arguments);
  466. midiInputListeners[args[0]]({
  467. command:args[1],
  468. channel:args[2],
  469. data1:args[3],
  470. data2:args[4],
  471. timeStamp:args[5],
  472. toString:function(){
  473. return args[6];
  474. }
  475. });
  476. }
  477. //if data is sent via AppletContext it arrives here
  478. midiBridge.onSequencerMetaData = function(){
  479. var args = Array.prototype.slice.call(arguments);
  480. if(onSequencerMetaData){
  481. var msg = {
  482. type:args[0],
  483. status:args[1],
  484. data:[]
  485. };
  486. for(var i = 2, maxi = args.length; i < maxi; i++){
  487. msg.data.push(args[i])
  488. }
  489. onSequencerMetaData(msg);
  490. }
  491. }
  492. //if data is sent via AppletContext it arrives here
  493. midiBridge.onSequencerMIDIData = function(){
  494. var args = Array.prototype.slice.call(arguments);
  495. if(onSequencerMIDIData){
  496. onSequencerMIDIData({
  497. command:args[0],
  498. channel:args[1],
  499. data1:args[2],
  500. data2:args[3],
  501. timeStamp:args[4],
  502. toString:function(){
  503. return args[5];
  504. }
  505. });
  506. }
  507. }
  508. midiBridge.getNoteName = function(noteNumber, mode) {
  509. if(!mode){
  510. mode = midiBridge.NOTE_NAMES_SHARP;
  511. }
  512. var octave = Math.floor(((noteNumber) / 12) - 1),
  513. noteName = noteNames[mode][noteNumber % 12];
  514. return noteName + "" + octave;
  515. };
  516. midiBridge.getNoteNumber = function(noteName, octave) {
  517. var index = -1,
  518. noteNumber;
  519. noteName = noteName.toUpperCase();
  520. for(var key in noteNames) {
  521. var modus = noteNames[key];
  522. for(var i = 0, max = modus.length; i < max; i++) {
  523. if(modus[i] === noteName) {
  524. index = i;
  525. break;
  526. }
  527. }
  528. }
  529. if(index === -1) {
  530. return "invalid note name";
  531. }
  532. noteNumber = (12 + index) + (octave * 12);
  533. return noteNumber;
  534. };
  535. midiBridge.getCommandVerbose = function(code) {
  536. return midiCommands[code];
  537. };
  538. midiBridge.formatMicroseconds = function(microseconds)
  539. {
  540. //console.log(microseconds);
  541. var r = "",
  542. t = (microseconds / 1000 / 1000) >> 0,
  543. h = (t / (60 * 60)) >> 0,
  544. m = ((t % (60 * 60)) / 60) >> 0,
  545. s = t % (60),
  546. ms = (((microseconds /1000) - (h * 3600000) - (m * 60000) - (s * 1000)) + 0.5) >> 0;
  547. //console.log(t,h,m,s,ms);
  548. r += h > 0 ? h + ":" : "";
  549. r += h > 0 ? m < 10 ? "0" + m : m : m;
  550. r += ":";
  551. r += s < 10 ? "0" + s : s;
  552. r += ":";
  553. r += ms === 0 ? "000" : ms < 10 ? "00" + ms : ms < 100 ? "0" + ms : ms;
  554. return r;
  555. };
  556. //a div with the applet object is added to the body of your html document
  557. function loadJava(){
  558. //console.log("loadJava");
  559. var javaDiv = document.createElement("div"),
  560. html = "";
  561. //if(userAgent.indexOf("chrome") === -1){
  562. if(userAgent !== "safari/osx" && userAgent.indexOf("chrome") === -1){
  563. html += '<object tabindex="0" id="midibridge-applet" type="application/x-java-applet" height="1" width="1">';
  564. html += '<param name="codebase" value="' + javaDir + '/" />';
  565. html += '<param name="archive" value="' + midiBridgeJar + '" />';
  566. html += '<param name="code" value="net.abumarkub.midi.MIDIApplet" />';
  567. html += '<param name="scriptable" value="true" />';
  568. html += '<param name="minJavaVersion" value="1.6" />';
  569. html += 'Your browser needs the Java plugin to use the midibridge. You can download it <a href="http://www.java.com/en/" target="blank" title="abumarkub midibridge download java" rel="abumarkub midibridge download java">here</a>';
  570. html += '</object>';
  571. }else{
  572. html += '<applet id="midibridge-applet" code="net.abumarkub.midi.MIDIApplet.class" archive="' + midiBridgeJar + '" codebase="' + javaDir + '" width="1" height="1" mayscript>';
  573. html += '<param name="minJavaVersion" value="1.6">';
  574. html += '</applet>';
  575. }
  576. javaDiv.setAttribute("id", "midibridge-java");
  577. javaDiv.innerHTML = html;
  578. document.body.appendChild(javaDiv);
  579. }
  580. function getObject(objectName) {
  581. if(userAgent.indexOf("msie") !== -1 || userAgent.indexOf("chrome") !== -1 || userAgent.indexOf("safari") !== -1) {
  582. return window[objectName];
  583. } else {
  584. return document[objectName];
  585. }
  586. }
  587. midiBridge.wrapElement = function(element){
  588. if(userAgent !== "msie8/win"){
  589. return;
  590. }
  591. element.addEventListener = function(id, callback, bubble){
  592. element.attachEvent(id, callback);
  593. }
  594. }
  595. //add addEventListener to IE8
  596. if(!window.addEventListener) {
  597. window.addEventListener = function(id, callback, bubble) {
  598. window.attachEvent("onload", callback);
  599. };
  600. }
  601. window.midiBridge = midiBridge;
  602. })(window);