├── .gitignore ├── .babelrc.json ├── tsconfig.json ├── dist ├── types │ ├── zmidi.d.ts.map │ └── zmidi.d.ts ├── zmidi.min.js ├── zmidi.js └── zmidi.amd.js ├── LICENSE ├── webpack.config.js ├── package.json ├── zmidi.js ├── src ├── MIDINotes.js ├── zMIDIEvent.js ├── SysexBuffer.js └── zMIDI.js ├── README.md └── examples └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .project 3 | node_modules 4 | .DS_Store 5 | *.log 6 | -------------------------------------------------------------------------------- /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "presets": ["@babel/preset-env"], 4 | "plugins": [ 5 | ["@babel/plugin-transform-runtime", { "regenerator": true }] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["zmidi.js"], 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "outFile": "dist/types/zmidi.d.ts", 8 | "declarationMap": true, 9 | "removeComments": true, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /dist/types/zmidi.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"zmidi.d.ts","sourceRoot":"","sources":["../../src/zMIDIEvent.js","../../src/SysexBuffer.js","../../src/zMIDI.js","../../src/MIDINotes.js","../../zmidi.js"],"names":[],"mappings":";;IAsBA;QAeI,kBAXW,MAAM,SACN,MAAM,GAAC,UAAU,YAEjB,MAAM,UACN,MAAM,WACN,MAAM,cACN,MAAM,aAEN,OAAO,cAkBjB;QAbkC,aAAZ,MAAM,CAA0B;QACpB,iBAAZ,MAAM,CAA8B;QACxB,eAAZ,MAAM,CAA4B;QACtB,gBAAZ,MAAM,CAA6B;QACvB,aAAZ,MAAM,CAAgC;QAC1B,cAAZ,OAAO,CAA6B;QAGnD,uBAAqD;QAGrD,cAA4C;KAGvD;;uBAIyB,MAAM;wBACN,MAAM;0BACN,MAAM;8BACN,MAAM;4BACN,MAAM;8BACN,MAAM;kCACN,MAAM;qBACN,MAAM;6BACN,MAAM;2BACN,MAAM;6BACN,MAAM;;;;;IC7ChC;QAW2C,mBAAhB,OAAO,CAAgC;QACvB,kBAAhB,OAAO,CAAgC;QACvB,gBAAY;QAWnD,cAHY,UAAU,CAQrB;QAWD,cALW,UAAU,iBACV,MAAM,GAEL,MAAM,CAoBjB;QAMD,cAIC;QAYD,eAUC;KACJ;;;;;QCvDG,oEAaC;QASD,gCAEC;QAQD,gCAEC;QAKD,2GAQC;QAED,sCAKC;QAED,yBAcC;QAQD,2FAoIC;QAOD,yDASC;QAWD,sFAEC;QAOD,8CAmBC;QAOD,gDAmBC;QAUD,kCAEC;;;;;;QClUD;;;;UAQC;yBAIS,MAAM,MAAM,CAAC"} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Igor Zinken 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require( 'path' ); 2 | 3 | const config = { 4 | entry: './zmidi.js', 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.js$/, 9 | loader: "babel-loader", 10 | exclude: /(node_modules)/, 11 | options: { 12 | compact: true 13 | } 14 | } 15 | ] 16 | } 17 | }; 18 | 19 | const browserConfig = { 20 | ...config, 21 | output: { 22 | filename: 'zmidi.min.js', 23 | path: path.resolve( __dirname, 'dist' ) 24 | } 25 | }; 26 | 27 | const amdConfig = { 28 | ...config, 29 | output: { 30 | filename: 'zmidi.amd.js', 31 | path: path.resolve( __dirname, 'dist' ), 32 | libraryTarget: 'amd', 33 | umdNamedDefine: true 34 | } 35 | }; 36 | 37 | const moduleConfig = { 38 | ...config, 39 | output: { 40 | filename: 'zmidi.js', 41 | path: path.resolve( __dirname, 'dist' ), 42 | libraryTarget: 'commonjs-module', 43 | umdNamedDefine: true 44 | } 45 | }; 46 | 47 | module.exports = [ 48 | browserConfig, amdConfig, moduleConfig 49 | ]; 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zmidi", 3 | "homepage": "http://www.igorski.nl/", 4 | "author": "Igor Zinken (http://github.com/igorski/)", 5 | "version": "1.2.1", 6 | "bugs": { 7 | "url": "https://github.com/igorski/zMIDI/issues/" 8 | }, 9 | "main": "dist/zmidi.js", 10 | "jsnext:main": "zmidi.js", 11 | "types": "dist/types/zmidi.d.ts", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/igorski/zMIDI.git" 15 | }, 16 | "description": "Simple library providing an event based model for working with MIDI inside the browser", 17 | "keywords": [ 18 | "web", 19 | "midi", 20 | "simple", 21 | "lightweight", 22 | "clean", 23 | "event", 24 | "abstraction", 25 | "audio", 26 | "music", 27 | "keyboard", 28 | "instrument" 29 | ], 30 | "preferGlobal": false, 31 | "license": "MIT", 32 | "devDependencies": { 33 | "@babel/core": "7.9.6", 34 | "@babel/plugin-transform-runtime": "^7.9.6", 35 | "@babel/preset-env": "7.9.6", 36 | "babel-loader": "8.2.5", 37 | "clean-webpack-plugin": "^4.0.0", 38 | "typescript": "^4.7.3", 39 | "webpack": "^5.72.1", 40 | "webpack-cli": "^4.9.2" 41 | }, 42 | "scripts": { 43 | "build": "webpack --mode=production", 44 | "types": "npx -p typescript tsc", 45 | "prepublish": "npm run build && npm run types" 46 | }, 47 | "engines": { 48 | "node": ">=10.0.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /zmidi.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2021 https://www.igorski.nl 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | * this software and associated documentation files (the "Software"), to deal in 8 | * the Software without restriction, including without limitation the rights to 9 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | * the Software, and to permit persons to whom the Software is furnished to do so, 11 | * subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | import zMIDI from "./src/zMIDI"; 24 | import zMIDIEvent from "./src/zMIDIEvent"; 25 | import MIDINotes from "./src/MIDINotes"; 26 | 27 | /** 28 | * expose the separate actors of 29 | * the zMIDI library 30 | */ 31 | export { 32 | zMIDI, zMIDIEvent, MIDINotes 33 | }; 34 | -------------------------------------------------------------------------------- /src/MIDINotes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2021 https://www.igorski.nl 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | * this software and associated documentation files (the "Software"), to deal in 8 | * the Software without restriction, including without limitation the rights to 9 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | * the Software, and to permit persons to whom the Software is furnished to do so, 11 | * subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /** 25 | * MIDINotes is an enumeration that lists all MIDI note numbers 26 | * as musical pitches (using note name and octave) 27 | */ 28 | const MIDINotes = { 29 | /** 30 | * convert a MIDI note number into a MIDINotes.Pitch Object 31 | * translating its value into more musically coherent values ;) 32 | * 33 | * @param {number} noteNumber 34 | * @return {{ 35 | * note: string, 36 | * octave: number, 37 | * frequency: number 38 | * }} 39 | */ 40 | getPitchByNoteNumber( noteNumber ) { 41 | // note number range is 21 (A0) to 108 (C8) 42 | 43 | return { 44 | note : MIDINotes.noteTable[ noteNumber % 12 ], 45 | octave : Math.floor( noteNumber / 12 ) - 1, 46 | frequency : 440 * Math.pow( 2,( noteNumber - 69 ) / 12 ) 47 | }; 48 | }, 49 | 50 | /** 51 | * @private 52 | * @type {Array} 53 | */ 54 | noteTable : [ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" ] 55 | }; 56 | export default MIDINotes; 57 | -------------------------------------------------------------------------------- /dist/types/zmidi.d.ts: -------------------------------------------------------------------------------- 1 | declare module "src/zMIDIEvent" { 2 | export default zMIDIEvent; 3 | class zMIDIEvent { 4 | constructor(type: number, value: number | Uint8Array, velocity: number, number: number, channel: number, portNumber: number, optSysex?: boolean | undefined); 5 | public type: number; 6 | public velocity: number; 7 | public number: number; 8 | public channel: number; 9 | public port: number; 10 | public sysex: boolean; 11 | sysexValue: Uint8Array; 12 | value: number; 13 | } 14 | namespace zMIDIEvent { 15 | const NOTE_ON: number; 16 | const NOTE_OFF: number; 17 | const AFTERTOUCH: number; 18 | const CONTROL_CHANGE: number; 19 | const CHANNEL_MODE: number; 20 | const PROGRAM_CHANGE: number; 21 | const CHANNEL_AFTERTOUCH: number; 22 | const SYSEX: number; 23 | const QUARTER_FRAME: number; 24 | const SONG_SELECT: number; 25 | const SONG_POSITION: number; 26 | } 27 | } 28 | declare module "src/SysexBuffer" { 29 | export default SysexBuffer; 30 | class SysexBuffer { 31 | public processing: boolean; 32 | public completed: boolean; 33 | private _buffer; 34 | getMessage(): Uint8Array; 35 | process(data: Uint8Array, initialOffset: number): number; 36 | reset(): void; 37 | private append; 38 | } 39 | } 40 | declare module "src/zMIDI" { 41 | export default zMIDI; 42 | namespace zMIDI { 43 | function connect(aOptSysex?: boolean): Promise; 44 | function isSupported(): boolean; 45 | function isConnected(): boolean; 46 | function addChangeListener(listener: (arg0: WebMidi.MIDIInput[], arg1: WebMidi.MIDIOutput[]) => any): void; 47 | function removeChangeListener(): void; 48 | function destroy(): void; 49 | function addMessageListener(portNumber: number, listener: (arg0: ZMIDIEvent) => any): void; 50 | function removeMessageListener(portNumber: number): void; 51 | function sendMessage(portNumber: number, message: number[], timestamp?: number): void; 52 | function getInChannels(): WebMidi.MIDIInput[]; 53 | function getOutChannels(): WebMidi.MIDIOutput[]; 54 | function getCurrentTime(): number; 55 | } 56 | } 57 | declare module "src/MIDINotes" { 58 | export default MIDINotes; 59 | namespace MIDINotes { 60 | function getPitchByNoteNumber(noteNumber: number): { 61 | note: string; 62 | octave: number; 63 | frequency: number; 64 | }; 65 | const noteTable: Array; 66 | } 67 | } 68 | declare module "zmidi" { 69 | import zMIDI from "src/zMIDI"; 70 | import zMIDIEvent from "src/zMIDIEvent"; 71 | import MIDINotes from "src/MIDINotes"; 72 | export { zMIDI, zMIDIEvent, MIDINotes }; 73 | } 74 | //# sourceMappingURL=zmidi.d.ts.map -------------------------------------------------------------------------------- /src/zMIDIEvent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2021 https://www.igorski.nl 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | * this software and associated documentation files (the "Software"), to deal in 8 | * the Software without restriction, including without limitation the rights to 9 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | * the Software, and to permit persons to whom the Software is furnished to do so, 11 | * subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | class zMIDIEvent { 24 | /** 25 | * @constructor 26 | * 27 | * @param {number} type MIDI message type 28 | * @param {number|Uint8Array} value the MIDI message value can be Uint8Array 29 | * for sysex messages 30 | * @param {number} velocity the MIDI velocity value 31 | * @param {number} number the MIDI controller|program number 32 | * @param {number} channel the MIDI channel the message was broadcast over 33 | * @param {number} portNumber the MIDI Input port number the 34 | * message was transmitted to / received on 35 | * @param {boolean=} optSysex optional whether the Event was a sysex message, 36 | * defaults to false 37 | */ 38 | constructor( type, value, velocity, number, channel, portNumber, optSysex = false ) 39 | { 40 | /** @public @type {number} */ this.type = type; 41 | /** @public @type {number} */ this.velocity = velocity; 42 | /** @public @type {number} */ this.number = number; 43 | /** @public @type {number} */ this.channel = channel; 44 | /** @public @type {number} */ this.port = portNumber; 45 | /** @public @type {boolean} */ this.sysex = optSysex; 46 | 47 | if ( this.sysex ) { 48 | this.sysexValue = /** @type {Uint8Array} */ ( value ); 49 | } 50 | else { 51 | this.value = /** @type {number} */ ( value ); 52 | } 53 | }; 54 | }; 55 | 56 | /* class constants */ 57 | 58 | /** @public @const @type {number} */ zMIDIEvent.NOTE_ON = 0; 59 | /** @public @const @type {number} */ zMIDIEvent.NOTE_OFF = 1; 60 | /** @public @const @type {number} */ zMIDIEvent.AFTERTOUCH = 2; 61 | /** @public @const @type {number} */ zMIDIEvent.CONTROL_CHANGE = 3; 62 | /** @public @const @type {number} */ zMIDIEvent.CHANNEL_MODE = 4; 63 | /** @public @const @type {number} */ zMIDIEvent.PROGRAM_CHANGE = 5; 64 | /** @public @const @type {number} */ zMIDIEvent.CHANNEL_AFTERTOUCH = 6; 65 | /** @public @const @type {number} */ zMIDIEvent.SYSEX = 7; 66 | /** @public @const @type {number} */ zMIDIEvent.QUARTER_FRAME = 8; 67 | /** @public @const @type {number} */ zMIDIEvent.SONG_SELECT = 9; 68 | /** @public @const @type {number} */ zMIDIEvent.SONG_POSITION = 10; 69 | 70 | export default zMIDIEvent; 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zMIDI 2 | 3 | zMIDI is a small JavaScript library that provides an easy interface to transfer messages from connected MIDI 4 | devices into your application and vice versa. zMIDI basically enables your web app to communicate with musical hardware. 5 | 6 | Instead of you having to manually translate weird hexadecimal numbers or doing scary masking operations on incoming MIDI messages, zMIDI does the job for you and provides you with enumerated types in an Event-driven model, which makes both more sense in a JavaScript environment and allows for easier development by providing an adequate abstraction layer. 7 | 8 | zMIDI is used by WebSID and Efflux, which you can try out live. See the demos section at the end of this README. 9 | 10 | ## Installation 11 | 12 | You can get zMIDI via NPM: 13 | 14 | ``` 15 | npm install zmidi 16 | ``` 17 | 18 | zMIDI has been written in modern vanilla JavaScript but comes with TypeScript annotations. The annotations rely 19 | on the type annotations for the WebMIDI API. You can install these in your project using: 20 | 21 | ``` 22 | npm install --save-dev @types/webmidi 23 | ``` 24 | 25 | ## Project integration 26 | 27 | zMIDI is compatible with ES6 Modules, CommonJS, AMD/RequireJS or can be included in the browser via script tags: 28 | 29 | ### ES6 module 30 | 31 | ``` 32 | import { zMIDI, zMIDIEvent, MIDINotes } from "zmidi"; 33 | ``` 34 | 35 | ### CommonJS: 36 | 37 | ``` 38 | const ZMIDILib = require( "zmidi" ); 39 | const { zMIDI, zMIDIEvent, MIDINotes } = zMIDILib; 40 | ``` 41 | 42 | (you can subsequently use a tool like Browserify to build for the browser). 43 | 44 | ### RequireJS 45 | 46 | Use _zmidi.amd.js_ inside the _dist/_-folder for a prebuilt, minimized AMD library transpiled to ES5. 47 | 48 | ``` 49 | require( [ "zmidi.amd" ], function( zMIDILib ) { 50 | // do something with zMIDILib-properties: 51 | // "zMIDI", "zMIDIEvent", "MIDINotes" 52 | }); 53 | ``` 54 | 55 | ### Browser: 56 | 57 | Use _zmidi.min.js_ inside the _dist/_-folder for a prebuilt, minimized library transpiled to ES5. 58 | 59 | ``` 60 | 61 | 67 | ``` 68 | 69 | ## Usage 70 | 71 | ### Ensuring WebMIDI is available at the browser level 72 | 73 | WebMIDI is still a W3C draft not implemented globally. Chrome users have already enjoyed this 74 | feature for years across desktop and mobile platforms with Edge and Opera users following suit. 75 | 76 | You can [consult this page](https://caniuse.com/?search=midi) to view the latest on browser support. 77 | 78 | ### Really making sure WebMIDI is available at the application level 79 | 80 | Query the result of _zMIDI.isSupported()_ to really, really make sure it is available! 81 | 82 | ### Making sure you meet Chrome's security standards 83 | 84 | As of M75, Web MIDI API will now ask for permissions. As such, any app using it will have to be served over HTTPS. Chrome’s permission requiring feature is available only on secure origins so effectively only on these the MIDI API will be allowed. The secure origins meet the following format: 85 | 86 | ``` 87 | (https, *, *) 88 | (wss, *, *) 89 | (*, localhost, *) 90 | (*, 127/8, *) 91 | (*, ::1/128, *) 92 | (file, *, — ) 93 | (chrome-extension, *, — ) 94 | ``` 95 | 96 | ## Documentation / Wiki 97 | 98 | You can view the online documentation here on Github : 99 | 100 | https://github.com/igorski/zMIDI/wiki 101 | 102 | ## Demos 103 | 104 | To quickly see what zMIDI is capable of, you can try the following URL with a MIDI keyboard attached to your computer : 105 | 106 | https://rawgit.com/igorski/zMIDI/master/examples/index.html 107 | 108 | those on macOS might find this [guide on creating a virtual MIDI output](https://feelyoursound.com/setup-midi-os-x/) valuable. 109 | 110 | for a demo that packs some more punch, try the following applications: 111 | 112 | * [WebSID](https://www.igorski.nl/application/websid) 113 | * [Efflux](https://www.igorski.nl/application/efflux) 114 | -------------------------------------------------------------------------------- /src/SysexBuffer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2021 https://www.igorski.nl 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | * this software and associated documentation files (the "Software"), to deal in 8 | * the Software without restriction, including without limitation the rights to 9 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | * the Software, and to permit persons to whom the Software is furnished to do so, 11 | * subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | class SysexBuffer { 24 | 25 | /** 26 | * convenience class to receive and store long incoming 27 | * Sysex messages until they have finished their broadcast 28 | * 29 | * @constructor 30 | */ 31 | constructor() { 32 | /* instance properties */ 33 | 34 | /** @public @type {boolean} */ this.processing = false; 35 | /** @public @type {boolean} */ this.completed = false; 36 | /** @private @type {Uint8Array} */ this._buffer; 37 | } 38 | 39 | /* public methods */ 40 | 41 | /** 42 | * retrieve the Sysex message buffer (when buffer has completed writing) 43 | * 44 | * @return {Uint8Array} 45 | * @throws {Error} 46 | */ 47 | getMessage() { 48 | if ( !this.completed || this._buffer === null ) { 49 | throw new Error( "SysexBuffer empty or message broadcast incomplete" ); 50 | } 51 | return this._buffer; 52 | } 53 | 54 | /** 55 | * processes an incoming Sysex message and internally handles 56 | * the message state in the internal buffer 57 | * 58 | * @param {Uint8Array} data 59 | * @param {number} initialOffset 60 | * 61 | * @return {number} offset of the last data read pointer 62 | */ 63 | process( data, initialOffset ) { 64 | let j = initialOffset; 65 | while ( j < data.length ) 66 | { 67 | // end of message received, we're done! 68 | if ( data[ j ] == 0xF7 ) 69 | { 70 | this.append( data.subarray( initialOffset, ++j )); 71 | this.processing = false; 72 | this.completed = true; 73 | return j; 74 | } 75 | ++j; 76 | } 77 | this.append( data.subarray( initialOffset, j )); 78 | this.processing = true; 79 | 80 | return j; 81 | } 82 | 83 | /** 84 | * flush the contents of the current message buffer 85 | * so this instance can be re-used for new messages 86 | */ 87 | reset() { 88 | this._buffer = null; 89 | this.completed = false; 90 | this.processing = false; 91 | } 92 | 93 | /* private methods */ 94 | 95 | /** 96 | * append incoming Sysex Message data to the existing buffer 97 | * (this will also create a buffer if it didn't exist yet) 98 | * 99 | * @private 100 | * 101 | * @param {Uint8Array} data 102 | */ 103 | append( data ) { 104 | const currentLength = this._buffer !== null ? this._buffer.length : 0; 105 | const newBuffer = new Uint8Array( currentLength + data.length ); 106 | 107 | if ( this._buffer !== null ) { 108 | newBuffer.set( this._buffer ); 109 | } 110 | newBuffer.set( data, currentLength ); 111 | 112 | this._buffer = newBuffer; 113 | } 114 | }; 115 | export default SysexBuffer; 116 | -------------------------------------------------------------------------------- /dist/zmidi.min.js: -------------------------------------------------------------------------------- 1 | (()=>{var t={228:t=>{t.exports=function(t,r){(null==r||r>t.length)&&(r=t.length);for(var e=0,n=new Array(r);e{t.exports=function(t){if(Array.isArray(t))return t}},926:t=>{function r(t,r,e,n,o,i,a){try{var c=t[i](a),u=c.value}catch(t){return void e(t)}c.done?r(u):Promise.resolve(u).then(n,o)}t.exports=function(t){return function(){var e=this,n=arguments;return new Promise((function(o,i){var a=t.apply(e,n);function c(t){r(a,o,i,c,u,"next",t)}function u(t){r(a,o,i,c,u,"throw",t)}c(void 0)}))}}},575:t=>{t.exports=function(t,r){if(!(t instanceof r))throw new TypeError("Cannot call a class as a function")}},913:t=>{function r(t,r){for(var e=0;e{t.exports=function(t,r){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(t)){var e=[],n=!0,o=!1,i=void 0;try{for(var a,c=t[Symbol.iterator]();!(n=(a=c.next()).done)&&(e.push(a.value),!r||e.length!==r);n=!0);}catch(t){o=!0,i=t}finally{try{n||null==c.return||c.return()}finally{if(o)throw i}}return e}}},521:t=>{t.exports=function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}},38:(t,r,e)=>{var n=e(858),o=e(884),i=e(379),a=e(521);t.exports=function(t,r){return n(t)||o(t,r)||i(t,r)||a()}},379:(t,r,e)=>{var n=e(228);t.exports=function(t,r){if(t){if("string"==typeof t)return n(t,r);var e=Object.prototype.toString.call(t).slice(8,-1);return"Object"===e&&t.constructor&&(e=t.constructor.name),"Map"===e||"Set"===e?Array.from(t):"Arguments"===e||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(e)?n(t,r):void 0}}},757:(t,r,e)=>{t.exports=e(666)},666:t=>{var r=function(t){"use strict";var r,e=Object.prototype,n=e.hasOwnProperty,o="function"==typeof Symbol?Symbol:{},i=o.iterator||"@@iterator",a=o.asyncIterator||"@@asyncIterator",c=o.toStringTag||"@@toStringTag";function u(t,r,e){return Object.defineProperty(t,r,{value:e,enumerable:!0,configurable:!0,writable:!0}),t[r]}try{u({},"")}catch(t){u=function(t,r,e){return t[r]=e}}function s(t,r,e,n){var o=r&&r.prototype instanceof d?r:d,i=Object.create(o.prototype),a=new S(n||[]);return i._invoke=function(t,r,e){var n=l;return function(o,i){if(n===p)throw new Error("Generator is already running");if(n===y){if("throw"===o)throw i;return T()}for(e.method=o,e.arg=i;;){var a=e.delegate;if(a){var c=_(a,e);if(c){if(c===v)continue;return c}}if("next"===e.method)e.sent=e._sent=e.arg;else if("throw"===e.method){if(n===l)throw n=y,e.arg;e.dispatchException(e.arg)}else"return"===e.method&&e.abrupt("return",e.arg);n=p;var u=f(t,r,e);if("normal"===u.type){if(n=e.done?y:h,u.arg===v)continue;return{value:u.arg,done:e.done}}"throw"===u.type&&(n=y,e.method="throw",e.arg=u.arg)}}}(t,e,a),i}function f(t,r,e){try{return{type:"normal",arg:t.call(r,e)}}catch(t){return{type:"throw",arg:t}}}t.wrap=s;var l="suspendedStart",h="suspendedYield",p="executing",y="completed",v={};function d(){}function g(){}function m(){}var w={};w[i]=function(){return this};var x=Object.getPrototypeOf,b=x&&x(x(A([])));b&&b!==e&&n.call(b,i)&&(w=b);var E=m.prototype=d.prototype=Object.create(w);function O(t){["next","throw","return"].forEach((function(r){u(t,r,(function(t){return this._invoke(r,t)}))}))}function L(t,r){function e(o,i,a,c){var u=f(t[o],t,i);if("throw"!==u.type){var s=u.arg,l=s.value;return l&&"object"==typeof l&&n.call(l,"__await")?r.resolve(l.__await).then((function(t){e("next",t,a,c)}),(function(t){e("throw",t,a,c)})):r.resolve(l).then((function(t){s.value=t,a(s)}),(function(t){return e("throw",t,a,c)}))}c(u.arg)}var o;this._invoke=function(t,n){function i(){return new r((function(r,o){e(t,n,r,o)}))}return o=o?o.then(i,i):i()}}function _(t,e){var n=t.iterator[e.method];if(n===r){if(e.delegate=null,"throw"===e.method){if(t.iterator.return&&(e.method="return",e.arg=r,_(t,e),"throw"===e.method))return v;e.method="throw",e.arg=new TypeError("The iterator does not provide a 'throw' method")}return v}var o=f(n,t.iterator,e.arg);if("throw"===o.type)return e.method="throw",e.arg=o.arg,e.delegate=null,v;var i=o.arg;return i?i.done?(e[t.resultName]=i.value,e.next=t.nextLoc,"return"!==e.method&&(e.method="next",e.arg=r),e.delegate=null,v):i:(e.method="throw",e.arg=new TypeError("iterator result is not an object"),e.delegate=null,v)}function N(t){var r={tryLoc:t[0]};1 in t&&(r.catchLoc=t[1]),2 in t&&(r.finallyLoc=t[2],r.afterLoc=t[3]),this.tryEntries.push(r)}function j(t){var r=t.completion||{};r.type="normal",delete r.arg,t.completion=r}function S(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(N,this),this.reset(!0)}function A(t){if(t){var e=t[i];if(e)return e.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var o=-1,a=function e(){for(;++o=0;--i){var a=this.tryEntries[i],c=a.completion;if("root"===a.tryLoc)return o("end");if(a.tryLoc<=this.prev){var u=n.call(a,"catchLoc"),s=n.call(a,"finallyLoc");if(u&&s){if(this.prev=0;--e){var o=this.tryEntries[e];if(o.tryLoc<=this.prev&&n.call(o,"finallyLoc")&&this.prev=0;--r){var e=this.tryEntries[r];if(e.finallyLoc===t)return this.complete(e.completion,e.afterLoc),j(e),v}},catch:function(t){for(var r=this.tryEntries.length-1;r>=0;--r){var e=this.tryEntries[r];if(e.tryLoc===t){var n=e.completion;if("throw"===n.type){var o=n.arg;j(e)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,e,n){return this.delegate={iterator:A(t),resultName:e,nextLoc:n},"next"===this.method&&(this.arg=r),v}},t}(t.exports);try{regeneratorRuntime=r}catch(t){Function("r","regeneratorRuntime = r")(r)}}},r={};function e(n){var o=r[n];if(void 0!==o)return o.exports;var i=r[n]={exports:{}};return t[n](i,i.exports,e),i.exports}e.n=t=>{var r=t&&t.__esModule?()=>t.default:()=>t;return e.d(r,{a:r}),r},e.d=(t,r)=>{for(var n in r)e.o(r,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:r[n]})},e.o=(t,r)=>Object.prototype.hasOwnProperty.call(t,r),(()=>{"use strict";e(38),e(757),e(926);var t=e(575),r=e.n(t),n=function t(e,n,o,i,a,c){var u=arguments.length>6&&void 0!==arguments[6]&&arguments[6];r()(this,t),this.type=e,this.velocity=o,this.number=i,this.channel=a,this.port=c,this.sysex=u,this.sysex?this.sysexValue=n:this.value=n};n.NOTE_ON=0,n.NOTE_OFF=1,n.AFTERTOUCH=2,n.CONTROL_CHANGE=3,n.CHANNEL_MODE=4,n.PROGRAM_CHANGE=5,n.CHANNEL_AFTERTOUCH=6,n.SYSEX=7,n.QUARTER_FRAME=8,n.SONG_SELECT=9,n.SONG_POSITION=10,e(913)})()})(); -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | zMIDI test 5 | 6 | 7 |
8 |

zMIDI example

9 | 10 | 11 | 12 |
13 |
14 |
15 | 16 | 17 | 18 | 184 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /dist/zmidi.js: -------------------------------------------------------------------------------- 1 | (()=>{var t={228:t=>{t.exports=function(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r{t.exports=function(t){if(Array.isArray(t))return t}},926:t=>{function e(t,e,r,n,o,i,a){try{var s=t[i](a),u=s.value}catch(t){return void r(t)}s.done?e(u):Promise.resolve(u).then(n,o)}t.exports=function(t){return function(){var r=this,n=arguments;return new Promise((function(o,i){var a=t.apply(r,n);function s(t){e(a,o,i,s,u,"next",t)}function u(t){e(a,o,i,s,u,"throw",t)}s(void 0)}))}}},575:t=>{t.exports=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}},913:t=>{function e(t,e){for(var r=0;r{t.exports=function(t,e){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(t)){var r=[],n=!0,o=!1,i=void 0;try{for(var a,s=t[Symbol.iterator]();!(n=(a=s.next()).done)&&(r.push(a.value),!e||r.length!==e);n=!0);}catch(t){o=!0,i=t}finally{try{n||null==s.return||s.return()}finally{if(o)throw i}}return r}}},521:t=>{t.exports=function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}},38:(t,e,r)=>{var n=r(858),o=r(884),i=r(379),a=r(521);t.exports=function(t,e){return n(t)||o(t,e)||i(t,e)||a()}},379:(t,e,r)=>{var n=r(228);t.exports=function(t,e){if(t){if("string"==typeof t)return n(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);return"Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r?Array.from(t):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?n(t,e):void 0}}},757:(t,e,r)=>{t.exports=r(666)},666:t=>{var e=function(t){"use strict";var e,r=Object.prototype,n=r.hasOwnProperty,o="function"==typeof Symbol?Symbol:{},i=o.iterator||"@@iterator",a=o.asyncIterator||"@@asyncIterator",s=o.toStringTag||"@@toStringTag";function u(t,e,r){return Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}),t[e]}try{u({},"")}catch(t){u=function(t,e,r){return t[e]=r}}function c(t,e,r,n){var o=e&&e.prototype instanceof d?e:d,i=Object.create(o.prototype),a=new I(n||[]);return i._invoke=function(t,e,r){var n=l;return function(o,i){if(n===p)throw new Error("Generator is already running");if(n===v){if("throw"===o)throw i;return T()}for(r.method=o,r.arg=i;;){var a=r.delegate;if(a){var s=_(a,r);if(s){if(s===y)continue;return s}}if("next"===r.method)r.sent=r._sent=r.arg;else if("throw"===r.method){if(n===l)throw n=v,r.arg;r.dispatchException(r.arg)}else"return"===r.method&&r.abrupt("return",r.arg);n=p;var u=f(t,e,r);if("normal"===u.type){if(n=r.done?v:h,u.arg===y)continue;return{value:u.arg,done:r.done}}"throw"===u.type&&(n=v,r.method="throw",r.arg=u.arg)}}}(t,r,a),i}function f(t,e,r){try{return{type:"normal",arg:t.call(e,r)}}catch(t){return{type:"throw",arg:t}}}t.wrap=c;var l="suspendedStart",h="suspendedYield",p="executing",v="completed",y={};function d(){}function g(){}function m(){}var b={};b[i]=function(){return this};var w=Object.getPrototypeOf,E=w&&w(w(A([])));E&&E!==r&&n.call(E,i)&&(b=E);var x=m.prototype=d.prototype=Object.create(b);function O(t){["next","throw","return"].forEach((function(e){u(t,e,(function(t){return this._invoke(e,t)}))}))}function L(t,e){function r(o,i,a,s){var u=f(t[o],t,i);if("throw"!==u.type){var c=u.arg,l=c.value;return l&&"object"==typeof l&&n.call(l,"__await")?e.resolve(l.__await).then((function(t){r("next",t,a,s)}),(function(t){r("throw",t,a,s)})):e.resolve(l).then((function(t){c.value=t,a(c)}),(function(t){return r("throw",t,a,s)}))}s(u.arg)}var o;this._invoke=function(t,n){function i(){return new e((function(e,o){r(t,n,e,o)}))}return o=o?o.then(i,i):i()}}function _(t,r){var n=t.iterator[r.method];if(n===e){if(r.delegate=null,"throw"===r.method){if(t.iterator.return&&(r.method="return",r.arg=e,_(t,r),"throw"===r.method))return y;r.method="throw",r.arg=new TypeError("The iterator does not provide a 'throw' method")}return y}var o=f(n,t.iterator,r.arg);if("throw"===o.type)return r.method="throw",r.arg=o.arg,r.delegate=null,y;var i=o.arg;return i?i.done?(r[t.resultName]=i.value,r.next=t.nextLoc,"return"!==r.method&&(r.method="next",r.arg=e),r.delegate=null,y):i:(r.method="throw",r.arg=new TypeError("iterator result is not an object"),r.delegate=null,y)}function C(t){var e={tryLoc:t[0]};1 in t&&(e.catchLoc=t[1]),2 in t&&(e.finallyLoc=t[2],e.afterLoc=t[3]),this.tryEntries.push(e)}function N(t){var e=t.completion||{};e.type="normal",delete e.arg,t.completion=e}function I(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(C,this),this.reset(!0)}function A(t){if(t){var r=t[i];if(r)return r.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var o=-1,a=function r(){for(;++o=0;--i){var a=this.tryEntries[i],s=a.completion;if("root"===a.tryLoc)return o("end");if(a.tryLoc<=this.prev){var u=n.call(a,"catchLoc"),c=n.call(a,"finallyLoc");if(u&&c){if(this.prev=0;--r){var o=this.tryEntries[r];if(o.tryLoc<=this.prev&&n.call(o,"finallyLoc")&&this.prev=0;--e){var r=this.tryEntries[e];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),N(r),y}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.tryLoc===t){var n=r.completion;if("throw"===n.type){var o=n.arg;N(r)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,r,n){return this.delegate={iterator:A(t),resultName:r,nextLoc:n},"next"===this.method&&(this.arg=e),y}},t}(t.exports);try{regeneratorRuntime=e}catch(t){Function("r","regeneratorRuntime = r")(e)}}},e={};function r(n){var o=e[n];if(void 0!==o)return o.exports;var i=e[n]={exports:{}};return t[n](i,i.exports,r),i.exports}r.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return r.d(e,{a:e}),e},r.d=(t,e)=>{for(var n in e)r.o(e,n)&&!r.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},r.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var n={};(()=>{"use strict";r.r(n),r.d(n,{MIDINotes:()=>O,zMIDI:()=>E,zMIDIEvent:()=>h});var t=r(38),e=r.n(t),o=r(757),a=r.n(o),s=r(926),u=r.n(s),c=r(575),f=r.n(c),l=function t(e,r,n,o,i,a){var s=arguments.length>6&&void 0!==arguments[6]&&arguments[6];f()(this,t),this.type=e,this.velocity=n,this.number=o,this.channel=i,this.port=a,this.sysex=s,this.sysex?this.sysexValue=r:this.value=r};l.NOTE_ON=0,l.NOTE_OFF=1,l.AFTERTOUCH=2,l.CONTROL_CHANGE=3,l.CHANNEL_MODE=4,l.PROGRAM_CHANGE=5,l.CHANNEL_AFTERTOUCH=6,l.SYSEX=7,l.QUARTER_FRAME=8,l.SONG_SELECT=9,l.SONG_POSITION=10;const h=l;var p=r(913),v=r.n(p);const y=function(){function t(){f()(this,t),this.processing=!1,this.completed=!1,this._buffer}return v()(t,[{key:"getMessage",value:function(){if(!this.completed||null===this._buffer)throw new Error("SysexBuffer empty or message broadcast incomplete");return this._buffer}},{key:"process",value:function(t,e){for(var r=e;r0&&void 0!==arguments[0]&&arguments[0];if(!w.isSupported())return b();var e={sysex:t};return new Promise(function(){var t=u()(a().mark((function t(r,n){return a().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return t.prev=0,t.next=3,navigator.requestMIDIAccess(e);case 3:d=t.sent,r(w.getInChannels(),w.getOutChannels()),t.next=10;break;case 7:t.prev=7,t.t0=t.catch(0),n(t.t0);case 10:case"end":return t.stop()}}),t,null,[[0,7]])})));return function(e,r){return t.apply(this,arguments)}}())},isSupported:function(){return"requestMIDIAccess"in navigator},isConnected:function(){return null!==d},addChangeListener:function(t){if(!w.isConnected())throw new Error("cannot add listener without connecting zMIDI first");m=function(){t(w.getInChannels(),w.getOutChannels())},d.addEventListener("statechange",m)},removeChangeListener:function(){m&&(d.removeEventListener("statechange",m),m=null)},destroy:function(){w.isConnected()&&(w.removeChangeListener(),Object.entries(g).forEach((function(t){var r=e()(t,2),n=r[0],o=r[1];if(o){var i=w.getInChannels()[n];i.close(),i.removeEventListener("midimessage",o,!0),delete g[n]}})),d=null)},addMessageListener:function(t,e){var r=new y,n=function(n){var o,a,s=n.data,u=!1,c=s[0]>>4,f=15&s[0],l=s[1],p=s.length>2?s[2]:0;if(8==c||9==c&&0===p)o=h.NOTE_OFF,a=l;else if(9==c)o=h.NOTE_ON,a=l;else if(11==c)o=h.CONTROL_CHANGE,a=p;else{for(var v=0,y=0;y(()=>{var t={228:t=>{t.exports=function(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r{t.exports=function(t){if(Array.isArray(t))return t}},926:t=>{function e(t,e,r,n,o,i,a){try{var s=t[i](a),u=s.value}catch(t){return void r(t)}s.done?e(u):Promise.resolve(u).then(n,o)}t.exports=function(t){return function(){var r=this,n=arguments;return new Promise((function(o,i){var a=t.apply(r,n);function s(t){e(a,o,i,s,u,"next",t)}function u(t){e(a,o,i,s,u,"throw",t)}s(void 0)}))}}},575:t=>{t.exports=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}},913:t=>{function e(t,e){for(var r=0;r{t.exports=function(t,e){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(t)){var r=[],n=!0,o=!1,i=void 0;try{for(var a,s=t[Symbol.iterator]();!(n=(a=s.next()).done)&&(r.push(a.value),!e||r.length!==e);n=!0);}catch(t){o=!0,i=t}finally{try{n||null==s.return||s.return()}finally{if(o)throw i}}return r}}},521:t=>{t.exports=function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}},38:(t,e,r)=>{var n=r(858),o=r(884),i=r(379),a=r(521);t.exports=function(t,e){return n(t)||o(t,e)||i(t,e)||a()}},379:(t,e,r)=>{var n=r(228);t.exports=function(t,e){if(t){if("string"==typeof t)return n(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);return"Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r?Array.from(t):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?n(t,e):void 0}}},757:(t,e,r)=>{t.exports=r(666)},666:t=>{var e=function(t){"use strict";var e,r=Object.prototype,n=r.hasOwnProperty,o="function"==typeof Symbol?Symbol:{},i=o.iterator||"@@iterator",a=o.asyncIterator||"@@asyncIterator",s=o.toStringTag||"@@toStringTag";function u(t,e,r){return Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}),t[e]}try{u({},"")}catch(t){u=function(t,e,r){return t[e]=r}}function c(t,e,r,n){var o=e&&e.prototype instanceof d?e:d,i=Object.create(o.prototype),a=new I(n||[]);return i._invoke=function(t,e,r){var n=l;return function(o,i){if(n===p)throw new Error("Generator is already running");if(n===v){if("throw"===o)throw i;return T()}for(r.method=o,r.arg=i;;){var a=r.delegate;if(a){var s=_(a,r);if(s){if(s===y)continue;return s}}if("next"===r.method)r.sent=r._sent=r.arg;else if("throw"===r.method){if(n===l)throw n=v,r.arg;r.dispatchException(r.arg)}else"return"===r.method&&r.abrupt("return",r.arg);n=p;var u=f(t,e,r);if("normal"===u.type){if(n=r.done?v:h,u.arg===y)continue;return{value:u.arg,done:r.done}}"throw"===u.type&&(n=v,r.method="throw",r.arg=u.arg)}}}(t,r,a),i}function f(t,e,r){try{return{type:"normal",arg:t.call(e,r)}}catch(t){return{type:"throw",arg:t}}}t.wrap=c;var l="suspendedStart",h="suspendedYield",p="executing",v="completed",y={};function d(){}function g(){}function m(){}var b={};b[i]=function(){return this};var w=Object.getPrototypeOf,E=w&&w(w(A([])));E&&E!==r&&n.call(E,i)&&(b=E);var x=m.prototype=d.prototype=Object.create(b);function O(t){["next","throw","return"].forEach((function(e){u(t,e,(function(t){return this._invoke(e,t)}))}))}function L(t,e){function r(o,i,a,s){var u=f(t[o],t,i);if("throw"!==u.type){var c=u.arg,l=c.value;return l&&"object"==typeof l&&n.call(l,"__await")?e.resolve(l.__await).then((function(t){r("next",t,a,s)}),(function(t){r("throw",t,a,s)})):e.resolve(l).then((function(t){c.value=t,a(c)}),(function(t){return r("throw",t,a,s)}))}s(u.arg)}var o;this._invoke=function(t,n){function i(){return new e((function(e,o){r(t,n,e,o)}))}return o=o?o.then(i,i):i()}}function _(t,r){var n=t.iterator[r.method];if(n===e){if(r.delegate=null,"throw"===r.method){if(t.iterator.return&&(r.method="return",r.arg=e,_(t,r),"throw"===r.method))return y;r.method="throw",r.arg=new TypeError("The iterator does not provide a 'throw' method")}return y}var o=f(n,t.iterator,r.arg);if("throw"===o.type)return r.method="throw",r.arg=o.arg,r.delegate=null,y;var i=o.arg;return i?i.done?(r[t.resultName]=i.value,r.next=t.nextLoc,"return"!==r.method&&(r.method="next",r.arg=e),r.delegate=null,y):i:(r.method="throw",r.arg=new TypeError("iterator result is not an object"),r.delegate=null,y)}function C(t){var e={tryLoc:t[0]};1 in t&&(e.catchLoc=t[1]),2 in t&&(e.finallyLoc=t[2],e.afterLoc=t[3]),this.tryEntries.push(e)}function N(t){var e=t.completion||{};e.type="normal",delete e.arg,t.completion=e}function I(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(C,this),this.reset(!0)}function A(t){if(t){var r=t[i];if(r)return r.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var o=-1,a=function r(){for(;++o=0;--i){var a=this.tryEntries[i],s=a.completion;if("root"===a.tryLoc)return o("end");if(a.tryLoc<=this.prev){var u=n.call(a,"catchLoc"),c=n.call(a,"finallyLoc");if(u&&c){if(this.prev=0;--r){var o=this.tryEntries[r];if(o.tryLoc<=this.prev&&n.call(o,"finallyLoc")&&this.prev=0;--e){var r=this.tryEntries[e];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),N(r),y}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.tryLoc===t){var n=r.completion;if("throw"===n.type){var o=n.arg;N(r)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,r,n){return this.delegate={iterator:A(t),resultName:r,nextLoc:n},"next"===this.method&&(this.arg=e),y}},t}(t.exports);try{regeneratorRuntime=e}catch(t){Function("r","regeneratorRuntime = r")(e)}}},e={};function r(n){var o=e[n];if(void 0!==o)return o.exports;var i=e[n]={exports:{}};return t[n](i,i.exports,r),i.exports}r.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return r.d(e,{a:e}),e},r.d=(t,e)=>{for(var n in e)r.o(e,n)&&!r.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},r.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var n={};return(()=>{"use strict";r.r(n),r.d(n,{MIDINotes:()=>O,zMIDI:()=>E,zMIDIEvent:()=>h});var t=r(38),e=r.n(t),o=r(757),a=r.n(o),s=r(926),u=r.n(s),c=r(575),f=r.n(c),l=function t(e,r,n,o,i,a){var s=arguments.length>6&&void 0!==arguments[6]&&arguments[6];f()(this,t),this.type=e,this.velocity=n,this.number=o,this.channel=i,this.port=a,this.sysex=s,this.sysex?this.sysexValue=r:this.value=r};l.NOTE_ON=0,l.NOTE_OFF=1,l.AFTERTOUCH=2,l.CONTROL_CHANGE=3,l.CHANNEL_MODE=4,l.PROGRAM_CHANGE=5,l.CHANNEL_AFTERTOUCH=6,l.SYSEX=7,l.QUARTER_FRAME=8,l.SONG_SELECT=9,l.SONG_POSITION=10;const h=l;var p=r(913),v=r.n(p);const y=function(){function t(){f()(this,t),this.processing=!1,this.completed=!1,this._buffer}return v()(t,[{key:"getMessage",value:function(){if(!this.completed||null===this._buffer)throw new Error("SysexBuffer empty or message broadcast incomplete");return this._buffer}},{key:"process",value:function(t,e){for(var r=e;r0&&void 0!==arguments[0]&&arguments[0];if(!w.isSupported())return b();var e={sysex:t};return new Promise(function(){var t=u()(a().mark((function t(r,n){return a().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return t.prev=0,t.next=3,navigator.requestMIDIAccess(e);case 3:d=t.sent,r(w.getInChannels(),w.getOutChannels()),t.next=10;break;case 7:t.prev=7,t.t0=t.catch(0),n(t.t0);case 10:case"end":return t.stop()}}),t,null,[[0,7]])})));return function(e,r){return t.apply(this,arguments)}}())},isSupported:function(){return"requestMIDIAccess"in navigator},isConnected:function(){return null!==d},addChangeListener:function(t){if(!w.isConnected())throw new Error("cannot add listener without connecting zMIDI first");m=function(){t(w.getInChannels(),w.getOutChannels())},d.addEventListener("statechange",m)},removeChangeListener:function(){m&&(d.removeEventListener("statechange",m),m=null)},destroy:function(){w.isConnected()&&(w.removeChangeListener(),Object.entries(g).forEach((function(t){var r=e()(t,2),n=r[0],o=r[1];if(o){var i=w.getInChannels()[n];i.close(),i.removeEventListener("midimessage",o,!0),delete g[n]}})),d=null)},addMessageListener:function(t,e){var r=new y,n=function(n){var o,a,s=n.data,u=!1,c=s[0]>>4,f=15&s[0],l=s[1],p=s.length>2?s[2]:0;if(8==c||9==c&&0===p)o=h.NOTE_OFF,a=l;else if(9==c)o=h.NOTE_ON,a=l;else if(11==c)o=h.CONTROL_CHANGE,a=p;else{for(var v=0,y=0;y, Array>} 58 | */ 59 | connect( aOptSysex = false ) { 60 | if ( !zMIDI.isSupported() ) { 61 | return handleConnectionFailure(); 62 | } 63 | const options = { sysex : aOptSysex }; 64 | return new Promise( async ( resolve, reject ) => { 65 | try { 66 | midiInterface = await navigator.requestMIDIAccess( /** @type {MIDIOptions} */ ( options )); 67 | resolve( zMIDI.getInChannels(), zMIDI.getOutChannels() ); 68 | } catch ( error ) { 69 | reject( error ); 70 | } 71 | }); 72 | }, 73 | 74 | /** 75 | * whether MIDI is available in the current application runtime environment 76 | * note this does NOT mean a successful connection can be established ! 77 | * (i.e. navigator supports MIDI, but no devices are attached) 78 | * 79 | * @return {boolean} 80 | */ 81 | isSupported() { 82 | return "requestMIDIAccess" in navigator; 83 | }, 84 | 85 | /** 86 | * whether zMIDI is connected to an interface / listening 87 | * to MIDI messages 88 | * 89 | * @return {boolean} 90 | */ 91 | isConnected() { 92 | return midiInterface !== null; 93 | }, 94 | 95 | /** 96 | * @param {function(Array, Array)} listener 97 | */ 98 | addChangeListener( listener ) { 99 | if ( !zMIDI.isConnected() ) { 100 | throw new Error( "cannot add listener without connecting zMIDI first" ); 101 | } 102 | changeListener = () => { 103 | listener( zMIDI.getInChannels(), zMIDI.getOutChannels() ); 104 | }; 105 | midiInterface.addEventListener( "statechange", changeListener ); 106 | }, 107 | 108 | removeChangeListener() { 109 | if ( changeListener ) { 110 | midiInterface.removeEventListener( "statechange", changeListener ); 111 | changeListener = null; 112 | } 113 | }, 114 | 115 | destroy() { 116 | if ( !zMIDI.isConnected() ) { 117 | return; 118 | } 119 | zMIDI.removeChangeListener(); 120 | Object.entries( listenerMap ).forEach(([ portNumber, listener ]) => { 121 | if ( listener ) { 122 | const inChannel = zMIDI.getInChannels()[ portNumber ]; 123 | inChannel.close(); 124 | inChannel.removeEventListener( "midimessage", listener, true ); 125 | delete listenerMap[ portNumber ]; 126 | } 127 | }); 128 | midiInterface = null; 129 | }, 130 | 131 | /** 132 | * attach a method to listen to MIDI message in events 133 | * 134 | * @param {number} portNumber index of the MIDI port to listen on 135 | * @param {function(ZMIDIEvent)} listener to receive {zMIDIEvent} 136 | */ 137 | addMessageListener( portNumber, listener ) { 138 | const sysexBuffer = new SysexBuffer(); // create a new sysex buffer for this port 139 | const proxiedListener = function( aEvent ) 140 | { 141 | const eventData = /** @type {Uint8Array} */ ( aEvent.data ); 142 | let isSysexMessage = false; 143 | 144 | const cmd = eventData[ 0 ] >> 4; 145 | const channel = eventData[ 0 ] & 0xf; 146 | const number = eventData[ 1 ]; 147 | const velocity = eventData.length > 2 ? eventData[ 2 ] : 0; 148 | 149 | let eventType, value; 150 | 151 | if ( cmd == 8 || (( cmd == 9 ) && ( velocity === 0 )) ) 152 | { 153 | eventType = zMIDIEvent.NOTE_OFF; 154 | value = number; 155 | } 156 | else if ( cmd == 9 ) { 157 | eventType = zMIDIEvent.NOTE_ON; 158 | value = number; 159 | } 160 | else if ( cmd == 11 ) { 161 | eventType = zMIDIEvent.CONTROL_CHANGE; 162 | value = velocity; 163 | } 164 | else 165 | { 166 | // everything else 167 | let length = 0; 168 | for ( let i = 0; i < eventData.length; i += length ) 169 | { 170 | if ( sysexBuffer.processing ) 171 | { 172 | i = sysexBuffer.process( eventData, i ); 173 | if ( eventData[ i - 1 ] != 0xf7 ) return; // Sysex message hasn't ended yet 174 | isSysexMessage = true; 175 | } 176 | else 177 | { 178 | isSysexMessage = false; 179 | 180 | switch ( eventData[ i ] & 0xF0 ) 181 | { 182 | /* // these could occur but should have been caught above! 183 | case 0x80: // note off 184 | case 0x90: // note on 185 | case 0xB0: // control change 186 | length = 3; 187 | break; 188 | */ 189 | case 0xA0: 190 | eventType = zMIDIEvent.AFTERTOUCH; 191 | length = 3; 192 | break; 193 | 194 | case 0xE0: 195 | eventType = zMIDIEvent.CHANNEL_MODE; 196 | length = 3; 197 | break; 198 | 199 | case 0xC0: 200 | eventType = zMIDIEvent.PROGRAM_CHANGE; 201 | length = 2; 202 | break; 203 | 204 | case 0xD0: 205 | eventType = zMIDIEvent.CHANNEL_AFTERTOUCH; 206 | length = 2; 207 | break; 208 | 209 | case 0xF0: 210 | 211 | switch ( eventData[ i ]) 212 | { 213 | case 0xf0: 214 | // implies incoming Sysex message 215 | i = sysexBuffer.process( eventData, i ); 216 | if ( eventData[ i - 1 ] != 0xf7 ) return; // Sysex message hasn't ended yet 217 | isSysexMessage = true; 218 | break; 219 | 220 | case 0xF1: 221 | eventType = zMIDIEvent.QUARTER_FRAME; 222 | length = 2; 223 | break; 224 | 225 | case 0xF3: 226 | eventType = zMIDIEvent.SONG_SELECT; 227 | length = 2; 228 | break; 229 | 230 | case 0xF2: 231 | eventType = zMIDIEvent.SONG_POSITION; 232 | length = 3; 233 | break; 234 | 235 | default: 236 | length = 1; 237 | break; 238 | } 239 | break; 240 | } 241 | } 242 | } 243 | if ( isSysexMessage && sysexBuffer.completed ) { 244 | value = sysexBuffer.getMessage(); 245 | } 246 | else { 247 | value = new Uint8Array( eventData.subarray( i, length + i )); 248 | } 249 | } 250 | // wrap it up, create zMIDIEvent and broadcast it to the listener 251 | const event = new zMIDIEvent( 252 | /** @type {number} */ ( eventType ), value, velocity, number, 253 | channel, portNumber, isSysexMessage 254 | ); 255 | listener( event ); 256 | 257 | // prepare for next Sysex message stream 258 | if ( isSysexMessage && sysexBuffer.completed ) { 259 | sysexBuffer.reset(); 260 | } 261 | }; 262 | 263 | // attach listener 264 | listenerMap[ portNumber ] = proxiedListener; 265 | const inChannel = zMIDI.getInChannels()[ portNumber ]; 266 | 267 | inChannel.addEventListener( "midimessage", proxiedListener, true ); 268 | inChannel.open(); 269 | }, 270 | 271 | /** 272 | * detach a method to listen to MIDI message in events 273 | * 274 | * @param {number} portNumber index of the MIDI port stop listening on 275 | */ 276 | removeMessageListener( portNumber ) 277 | { 278 | const listener = listenerMap[ portNumber]; 279 | if ( listener ) { 280 | const inChannel = zMIDI.getInChannels()[ portNumber ]; 281 | inChannel.close(); 282 | inChannel.removeEventListener( "midimessage", listener, true ); 283 | delete listenerMap[ portNumber ]; 284 | } 285 | }, 286 | 287 | /** 288 | * broadcast a message to a MIDI port 289 | * 290 | * @param {number} portNumber index of the MIDI port to broadcast to 291 | * @param {Array} message to send, is an Array with three slots : 292 | * event type (e.g. note on/note off), note (frequency) and velocity 293 | * @param {number=} timestamp optional time to send the message, by default 294 | * the message will be sent immediately 295 | */ 296 | sendMessage( portNumber, message, timestamp ) { 297 | zMIDI.getOutChannels()[ portNumber ].send( message, timestamp ); 298 | }, 299 | 300 | /** 301 | * retrieve all available MIDI inputs 302 | * 303 | * @return {Array} 304 | */ 305 | getInChannels() 306 | { 307 | if ( zMIDI.isConnected() ) 308 | { 309 | let inputs = /** @type {Array} */ ( [] ); 310 | 311 | if ( typeof midiInterface.inputs === "function" ) { 312 | inputs = midiInterface.inputs(); 313 | } 314 | else { 315 | const it = midiInterface.inputs.values(); 316 | for ( let o = it.next(); !o.done; o = it.next() ) { 317 | inputs.push( o.value ); 318 | } 319 | } 320 | return inputs; 321 | } 322 | handleConnectionFailure(); 323 | return null; 324 | }, 325 | 326 | /** 327 | * retrieve all available MIDI output ports 328 | * 329 | * @return {Array} 330 | */ 331 | getOutChannels() 332 | { 333 | if ( zMIDI.isConnected() ) 334 | { 335 | let outputs = /** @type {Array} */ ( [] ); 336 | 337 | if ( typeof midiInterface.outputs === "function" ) { 338 | outputs = midiInterface.outputs(); 339 | } 340 | else { 341 | const it = midiInterface.outputs.values(); 342 | for ( let o = it.next(); !o.done; o = it.next() ) { 343 | outputs.push( o.value ); 344 | } 345 | } 346 | return outputs; 347 | } 348 | handleConnectionFailure(); 349 | return null; 350 | }, 351 | 352 | /** 353 | * retrieve the current timestamp of the MIDI performance 354 | * (in milliseconds) this can be used to enqueue events relative 355 | * to the current time (e.g. getCurrentTime() + 1000 can enqueue 356 | * an event 1 second from now) 357 | * 358 | * @return {number} 359 | */ 360 | getCurrentTime() { 361 | return window.performance.now(); 362 | } 363 | }; 364 | export default zMIDI; 365 | --------------------------------------------------------------------------------